build.rs 8.9 KB

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