4
0

build.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. use std::{
  2. env,
  3. ffi::OsString,
  4. fs,
  5. io::{BufRead as _, BufReader},
  6. path::PathBuf,
  7. process::{Child, Command, Output, Stdio},
  8. };
  9. use cargo_metadata::{
  10. Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target, TargetKind,
  11. };
  12. use xtask::{exec, AYA_BUILD_INTEGRATION_BPF, LIBBPF_DIR};
  13. /// This crate has a runtime dependency on artifacts produced by the `integration-ebpf` crate. This
  14. /// would be better expressed as one or more [artifact-dependencies][bindeps] but issues such as:
  15. ///
  16. /// * https://github.com/rust-lang/cargo/issues/12374
  17. /// * https://github.com/rust-lang/cargo/issues/12375
  18. /// * https://github.com/rust-lang/cargo/issues/12385
  19. ///
  20. /// prevent their use for the time being.
  21. ///
  22. /// This file, along with the xtask crate, allows analysis tools such as `cargo check`, `cargo
  23. /// clippy`, and even `cargo build` to work as users expect. Prior to this file's existence, this
  24. /// crate's undeclared dependency on artifacts from `integration-ebpf` would cause build (and `cargo check`,
  25. /// and `cargo clippy`) failures until the user ran certain other commands in the workspace. Conversely,
  26. /// those same tools (e.g. cargo test --no-run) would produce stale results if run naively because
  27. /// they'd make use of artifacts from a previous build of `integration-ebpf`.
  28. ///
  29. /// Note that this solution is imperfect: in particular it has to balance correctness with
  30. /// performance; an environment variable is used to replace true builds of `integration-ebpf` with
  31. /// stubs to preserve the property that code generation and linking (in `integration-ebpf`) do not
  32. /// occur on metadata-only actions such as `cargo check` or `cargo clippy` of this crate. This means
  33. /// that naively attempting to `cargo test --no-run` this crate will produce binaries that fail at
  34. /// runtime because the stubs are inadequate for actually running the tests.
  35. ///
  36. /// [bindeps]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html?highlight=feature#artifact-dependencies
  37. fn main() {
  38. println!("cargo:rerun-if-env-changed={}", AYA_BUILD_INTEGRATION_BPF);
  39. let build_integration_bpf = env::var(AYA_BUILD_INTEGRATION_BPF)
  40. .as_deref()
  41. .map(str::parse)
  42. .map(Result::unwrap)
  43. .unwrap_or_default();
  44. let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
  45. let integration_ebpf_package = packages
  46. .into_iter()
  47. .find(|Package { name, .. }| name == "integration-ebpf")
  48. .unwrap();
  49. let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
  50. let manifest_dir = PathBuf::from(manifest_dir);
  51. let out_dir = env::var_os("OUT_DIR").unwrap();
  52. let out_dir = PathBuf::from(out_dir);
  53. let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap();
  54. let target = if endian == "big" {
  55. "bpfeb"
  56. } else if endian == "little" {
  57. "bpfel"
  58. } else {
  59. panic!("unsupported endian={:?}", endian)
  60. };
  61. const C_BPF: &[(&str, bool)] = &[
  62. ("ext.bpf.c", false),
  63. ("iter.bpf.c", true),
  64. ("main.bpf.c", false),
  65. ("multimap-btf.bpf.c", false),
  66. ("reloc.bpf.c", true),
  67. ("text_64_64_reloc.c", false),
  68. ("variables_reloc.bpf.c", false),
  69. ];
  70. if build_integration_bpf {
  71. let libbpf_dir = manifest_dir
  72. .parent()
  73. .unwrap()
  74. .parent()
  75. .unwrap()
  76. .join(LIBBPF_DIR);
  77. println!("cargo:rerun-if-changed={}", libbpf_dir.to_str().unwrap());
  78. let libbpf_headers_dir = out_dir.join("libbpf_headers");
  79. let mut includedir = OsString::new();
  80. includedir.push("INCLUDEDIR=");
  81. includedir.push(&libbpf_headers_dir);
  82. exec(
  83. Command::new("make")
  84. .arg("-C")
  85. .arg(libbpf_dir.join("src"))
  86. .arg(includedir)
  87. .arg("install_headers"),
  88. )
  89. .unwrap();
  90. let bpf_dir = manifest_dir.join("bpf");
  91. let mut target_arch = OsString::new();
  92. target_arch.push("-D__TARGET_ARCH_");
  93. let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
  94. if arch == "x86_64" {
  95. target_arch.push("x86");
  96. } else if arch == "aarch64" {
  97. target_arch.push("arm64");
  98. } else {
  99. target_arch.push(&arch);
  100. };
  101. // NB: libbpf's documentation suggests that vmlinux.h be generated by running `bpftool btf
  102. // dump file /sys/kernel/btf/vmlinux format c`; this allows CO-RE to work.
  103. //
  104. // However in our tests we do not make use of kernel data structures, and so any vmlinux.h
  105. // which defines the constants we need (e.g. `__u8`, `__u64`, `BPF_MAP_TYPE_ARRAY`,
  106. // `BPF_ANY`, `XDP_PASS`, `XDP_DROP`, etc.) will suffice. Since we already have a libbpf
  107. // submodule which happens to include such a file, we use it.
  108. let libbpf_vmlinux_dir = libbpf_dir.join(".github/actions/build-selftests");
  109. let clang = || {
  110. let mut cmd = Command::new("clang");
  111. cmd.arg("-nostdlibinc")
  112. .arg("-I")
  113. .arg(&libbpf_headers_dir)
  114. .arg("-I")
  115. .arg(&libbpf_vmlinux_dir)
  116. .args(["-g", "-O2", "-target", target, "-c"])
  117. .arg(&target_arch);
  118. cmd
  119. };
  120. for (src, build_btf) in C_BPF {
  121. let dst = out_dir.join(src).with_extension("o");
  122. let src = bpf_dir.join(src);
  123. println!("cargo:rerun-if-changed={}", src.to_str().unwrap());
  124. exec(clang().arg(&src).arg("-o").arg(&dst)).unwrap();
  125. if *build_btf {
  126. let mut cmd = clang();
  127. let mut child = cmd
  128. .arg("-DTARGET")
  129. .arg(&src)
  130. .args(["-o", "-"])
  131. .stdout(Stdio::piped())
  132. .spawn()
  133. .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
  134. let Child { stdout, .. } = &mut child;
  135. let stdout = stdout.take().unwrap();
  136. let dst = dst.with_extension("target.o");
  137. let mut output = OsString::new();
  138. output.push(".BTF=");
  139. output.push(dst);
  140. exec(
  141. // NB: objcopy doesn't support reading from stdin, so we have to use llvm-objcopy.
  142. Command::new("llvm-objcopy")
  143. .arg("--dump-section")
  144. .arg(output)
  145. .arg("-")
  146. .stdin(stdout),
  147. )
  148. .unwrap();
  149. let output = child
  150. .wait_with_output()
  151. .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
  152. let Output { status, .. } = &output;
  153. assert_eq!(status.code(), Some(0), "{cmd:?} failed: {output:?}");
  154. }
  155. }
  156. let target = format!("{target}-unknown-none");
  157. let Package { manifest_path, .. } = integration_ebpf_package;
  158. let integration_ebpf_dir = manifest_path.parent().unwrap();
  159. // We have a build-dependency on `integration-ebpf`, so cargo will automatically rebuild us
  160. // if `integration-ebpf`'s *library* target or any of its dependencies change. Since we
  161. // depend on `integration-ebpf`'s *binary* targets, that only gets us half of the way. This
  162. // stanza ensures cargo will rebuild us on changes to the binaries too, which gets us the
  163. // rest of the way.
  164. println!("cargo:rerun-if-changed={}", integration_ebpf_dir.as_str());
  165. let mut cmd = Command::new("cargo");
  166. cmd.args([
  167. "build",
  168. "-Z",
  169. "build-std=core",
  170. "--bins",
  171. "--message-format=json",
  172. "--release",
  173. "--target",
  174. &target,
  175. ]);
  176. cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
  177. // Workaround to make sure that the rust-toolchain.toml is respected.
  178. for key in ["RUSTUP_TOOLCHAIN", "RUSTC"] {
  179. cmd.env_remove(key);
  180. }
  181. cmd.current_dir(integration_ebpf_dir);
  182. // Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself.
  183. let ebpf_target_dir = out_dir.join("integration-ebpf");
  184. cmd.arg("--target-dir").arg(&ebpf_target_dir);
  185. let mut child = cmd
  186. .stdout(Stdio::piped())
  187. .stderr(Stdio::piped())
  188. .spawn()
  189. .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
  190. let Child { stdout, stderr, .. } = &mut child;
  191. // Trampoline stdout to cargo warnings.
  192. let stderr = stderr.take().unwrap();
  193. let stderr = BufReader::new(stderr);
  194. let stderr = std::thread::spawn(move || {
  195. for line in stderr.lines() {
  196. let line = line.unwrap();
  197. println!("cargo:warning={line}");
  198. }
  199. });
  200. let stdout = stdout.take().unwrap();
  201. let stdout = BufReader::new(stdout);
  202. let mut executables = Vec::new();
  203. for message in Message::parse_stream(stdout) {
  204. #[allow(clippy::collapsible_match)]
  205. match message.expect("valid JSON") {
  206. Message::CompilerArtifact(Artifact {
  207. executable,
  208. target: Target { name, .. },
  209. ..
  210. }) => {
  211. if let Some(executable) = executable {
  212. executables.push((name, executable.into_std_path_buf()));
  213. }
  214. }
  215. Message::CompilerMessage(CompilerMessage { message, .. }) => {
  216. for line in message.rendered.unwrap_or_default().split('\n') {
  217. println!("cargo:warning={line}");
  218. }
  219. }
  220. Message::TextLine(line) => {
  221. println!("cargo:warning={line}");
  222. }
  223. _ => {}
  224. }
  225. }
  226. let status = child
  227. .wait()
  228. .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
  229. assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
  230. stderr.join().map_err(std::panic::resume_unwind).unwrap();
  231. for (name, binary) in executables {
  232. let dst = out_dir.join(name);
  233. let _: u64 = fs::copy(&binary, &dst)
  234. .unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
  235. }
  236. } else {
  237. for (src, build_btf) in C_BPF {
  238. let dst = out_dir.join(src).with_extension("o");
  239. fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
  240. if *build_btf {
  241. let dst = dst.with_extension("target.o");
  242. fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
  243. }
  244. }
  245. let Package { targets, .. } = integration_ebpf_package;
  246. for Target { name, kind, .. } in targets {
  247. if *kind != [TargetKind::Bin] {
  248. continue;
  249. }
  250. let dst = out_dir.join(name);
  251. fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
  252. }
  253. }
  254. }