build.rs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. use std::{
  2. env,
  3. ffi::OsString,
  4. fs,
  5. path::PathBuf,
  6. process::{Child, Command, Output, Stdio},
  7. };
  8. use anyhow::{anyhow, Context as _, Ok, Result};
  9. use aya_build::cargo_metadata::{Metadata, MetadataCommand, Package, Target, TargetKind};
  10. use xtask::{exec, AYA_BUILD_INTEGRATION_BPF, LIBBPF_DIR};
  11. /// This file, along with the xtask crate, allows analysis tools such as `cargo check`, `cargo
  12. /// clippy`, and even `cargo build` to work as users expect. Prior to this file's existence, this
  13. /// crate's undeclared dependency on artifacts from `integration-ebpf` would cause build (and `cargo check`,
  14. /// and `cargo clippy`) failures until the user ran certain other commands in the workspace. Conversely,
  15. /// those same tools (e.g. cargo test --no-run) would produce stale results if run naively because
  16. /// they'd make use of artifacts from a previous build of `integration-ebpf`.
  17. ///
  18. /// Note that this solution is imperfect: in particular it has to balance correctness with
  19. /// performance; an environment variable is used to replace true builds of `integration-ebpf` with
  20. /// stubs to preserve the property that code generation and linking (in `integration-ebpf`) do not
  21. /// occur on metadata-only actions such as `cargo check` or `cargo clippy` of this crate. This means
  22. /// that naively attempting to `cargo test --no-run` this crate will produce binaries that fail at
  23. /// runtime because the stubs are inadequate for actually running the tests.
  24. fn main() -> Result<()> {
  25. println!("cargo:rerun-if-env-changed={AYA_BUILD_INTEGRATION_BPF}");
  26. // TODO(https://github.com/rust-lang/cargo/issues/4001): generalize this and move it to
  27. // aya-build if we can determine that we're in a check build.
  28. let build_integration_bpf = env::var_os(AYA_BUILD_INTEGRATION_BPF)
  29. .map(|build_integration_bpf| {
  30. let build_integration_bpf = std::str::from_utf8(
  31. build_integration_bpf.as_encoded_bytes(),
  32. )
  33. .with_context(|| format!("{AYA_BUILD_INTEGRATION_BPF}={build_integration_bpf:?}"))?;
  34. let build_integration_bpf = build_integration_bpf
  35. .parse()
  36. .with_context(|| format!("{AYA_BUILD_INTEGRATION_BPF}={build_integration_bpf}"))?;
  37. Ok(build_integration_bpf)
  38. })
  39. .transpose()?
  40. .unwrap_or_default();
  41. let Metadata {
  42. packages,
  43. workspace_root,
  44. ..
  45. } = MetadataCommand::new()
  46. .no_deps()
  47. .exec()
  48. .context("MetadataCommand::exec")?;
  49. let integration_ebpf_package = packages
  50. .into_iter()
  51. .find(|Package { name, .. }| name == "integration-ebpf")
  52. .ok_or_else(|| anyhow!("integration-ebpf package not found"))?;
  53. let manifest_dir =
  54. env::var_os("CARGO_MANIFEST_DIR").ok_or(anyhow!("CARGO_MANIFEST_DIR not set"))?;
  55. let manifest_dir = PathBuf::from(manifest_dir);
  56. let out_dir = env::var_os("OUT_DIR").ok_or(anyhow!("OUT_DIR not set"))?;
  57. let out_dir = PathBuf::from(out_dir);
  58. const C_BPF: &[(&str, bool)] = &[
  59. ("ext.bpf.c", false),
  60. ("iter.bpf.c", true),
  61. ("main.bpf.c", false),
  62. ("multimap-btf.bpf.c", false),
  63. ("reloc.bpf.c", true),
  64. ("text_64_64_reloc.c", false),
  65. ("variables_reloc.bpf.c", false),
  66. ];
  67. if build_integration_bpf {
  68. let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN")
  69. .ok_or(anyhow!("CARGO_CFG_TARGET_ENDIAN not set"))?;
  70. let target = if endian == "big" {
  71. "bpfeb"
  72. } else if endian == "little" {
  73. "bpfel"
  74. } else {
  75. return Err(anyhow!("unsupported endian={endian:?}"));
  76. };
  77. let libbpf_dir = workspace_root.join(LIBBPF_DIR);
  78. println!("cargo:rerun-if-changed={libbpf_dir}");
  79. let libbpf_headers_dir = out_dir.join("libbpf_headers");
  80. let mut includedir = OsString::new();
  81. includedir.push("INCLUDEDIR=");
  82. includedir.push(&libbpf_headers_dir);
  83. exec(
  84. Command::new("make")
  85. .arg("-C")
  86. .arg(libbpf_dir.join("src"))
  87. .arg(includedir)
  88. .arg("install_headers"),
  89. )?;
  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 =
  94. env::var_os("CARGO_CFG_TARGET_ARCH").ok_or(anyhow!("CARGO_CFG_TARGET_ARCH not set"))?;
  95. if arch == "x86_64" {
  96. target_arch.push("x86");
  97. } else if arch == "aarch64" {
  98. target_arch.push("arm64");
  99. } else {
  100. target_arch.push(&arch);
  101. };
  102. // NB: libbpf's documentation suggests that vmlinux.h be generated by running `bpftool btf
  103. // dump file /sys/kernel/btf/vmlinux format c`; this allows CO-RE to work.
  104. //
  105. // However in our tests we do not make use of kernel data structures, and so any vmlinux.h
  106. // which defines the constants we need (e.g. `__u8`, `__u64`, `BPF_MAP_TYPE_ARRAY`,
  107. // `BPF_ANY`, `XDP_PASS`, `XDP_DROP`, etc.) will suffice. Since we already have a libbpf
  108. // submodule which happens to include such a file, we use it.
  109. let libbpf_vmlinux_dir = libbpf_dir.join(".github/actions/build-selftests");
  110. let clang = || {
  111. let mut cmd = Command::new("clang");
  112. cmd.arg("-nostdlibinc")
  113. .arg("-I")
  114. .arg(&libbpf_headers_dir)
  115. .arg("-I")
  116. .arg(&libbpf_vmlinux_dir)
  117. .args(["-g", "-O2", "-target", target, "-c"])
  118. .arg(&target_arch);
  119. cmd
  120. };
  121. for (src, build_btf) in C_BPF {
  122. let dst = out_dir.join(src).with_extension("o");
  123. let src = bpf_dir.join(src);
  124. {
  125. let src = src.to_str().with_context(|| format!("{src:?}"))?;
  126. println!("cargo:rerun-if-changed={src}");
  127. }
  128. exec(clang().arg(&src).arg("-o").arg(&dst))?;
  129. if *build_btf {
  130. let mut cmd = clang();
  131. let mut child = cmd
  132. .arg("-DTARGET")
  133. .arg(&src)
  134. .args(["-o", "-"])
  135. .stdout(Stdio::piped())
  136. .spawn()
  137. .with_context(|| format!("failed to spawn {cmd:?}"))?;
  138. let Child { stdout, .. } = &mut child;
  139. let stdout = stdout.take().expect("stdout");
  140. let dst = dst.with_extension("target.o");
  141. let mut output = OsString::new();
  142. output.push(".BTF=");
  143. output.push(dst);
  144. exec(
  145. // NB: objcopy doesn't support reading from stdin, so we have to use llvm-objcopy.
  146. Command::new("llvm-objcopy")
  147. .arg("--dump-section")
  148. .arg(output)
  149. .arg("-")
  150. .stdin(stdout),
  151. )?;
  152. let output = child
  153. .wait_with_output()
  154. .with_context(|| format!("failed to wait for {cmd:?}"))?;
  155. let Output { status, .. } = &output;
  156. if !status.success() {
  157. return Err(anyhow!("{cmd:?} failed: {status:?}"));
  158. }
  159. }
  160. }
  161. aya_build::build_ebpf([integration_ebpf_package])?;
  162. } else {
  163. for (src, build_btf) in C_BPF {
  164. let dst = out_dir.join(src).with_extension("o");
  165. fs::write(&dst, []).with_context(|| format!("failed to create {dst:?}"))?;
  166. if *build_btf {
  167. let dst = dst.with_extension("target.o");
  168. fs::write(&dst, []).with_context(|| format!("failed to create {dst:?}"))?;
  169. }
  170. }
  171. let Package { targets, .. } = integration_ebpf_package;
  172. for Target { name, kind, .. } in targets {
  173. if *kind != [TargetKind::Bin] {
  174. continue;
  175. }
  176. let dst = out_dir.join(name);
  177. fs::write(&dst, []).with_context(|| format!("failed to create {dst:?}"))?;
  178. }
  179. }
  180. Ok(())
  181. }