build.rs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. use std::{
  2. collections::{HashMap, HashSet},
  3. env,
  4. ffi::OsString,
  5. fmt::Write as _,
  6. fs,
  7. io::BufReader,
  8. path::PathBuf,
  9. process::{Child, Command, Stdio},
  10. };
  11. use cargo_metadata::{
  12. Artifact, CompilerMessage, Dependency, Message, Metadata, MetadataCommand, Package, Target,
  13. };
  14. use which::which;
  15. use xtask::{exec, LIBBPF_DIR};
  16. fn main() {
  17. const AYA_BUILD_INTEGRATION_BPF: &str = "AYA_BUILD_INTEGRATION_BPF";
  18. println!("cargo:rerun-if-env-changed={}", AYA_BUILD_INTEGRATION_BPF);
  19. let build_integration_bpf = match env::var_os(AYA_BUILD_INTEGRATION_BPF) {
  20. None => false,
  21. Some(s) => {
  22. let s = s.to_str().unwrap();
  23. s.parse::<bool>().unwrap()
  24. }
  25. };
  26. const INTEGRATION_EBPF_PACKAGE: &str = "integration-ebpf";
  27. let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
  28. let packages: HashMap<String, _> = packages
  29. .into_iter()
  30. .map(|package| {
  31. let Package { name, .. } = &package;
  32. (name.clone(), package)
  33. })
  34. .collect();
  35. let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
  36. let manifest_dir = PathBuf::from(manifest_dir);
  37. let out_dir = env::var_os("OUT_DIR").unwrap();
  38. let out_dir = PathBuf::from(out_dir);
  39. let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap();
  40. let target = if endian == "big" {
  41. "bpfeb"
  42. } else if endian == "little" {
  43. "bpfel"
  44. } else {
  45. panic!("unsupported endian={:?}", endian)
  46. };
  47. const C_BPF_PROBES: &[(&str, &str)] = &[
  48. ("ext.bpf.c", "ext.bpf.o"),
  49. ("main.bpf.c", "main.bpf.o"),
  50. ("multimap-btf.bpf.c", "multimap-btf.bpf.o"),
  51. ("text_64_64_reloc.c", "text_64_64_reloc.o"),
  52. ];
  53. let c_bpf_probes = C_BPF_PROBES
  54. .iter()
  55. .map(|(src, dst)| (src, out_dir.join(dst)));
  56. if build_integration_bpf {
  57. let libbpf_dir = manifest_dir
  58. .parent()
  59. .unwrap()
  60. .parent()
  61. .unwrap()
  62. .join(LIBBPF_DIR);
  63. println!("cargo:rerun-if-changed={}", libbpf_dir.to_str().unwrap());
  64. let libbpf_headers_dir = out_dir.join("libbpf_headers");
  65. let mut includedir = OsString::new();
  66. includedir.push("INCLUDEDIR=");
  67. includedir.push(&libbpf_headers_dir);
  68. exec(
  69. Command::new("make")
  70. .arg("-C")
  71. .arg(libbpf_dir.join("src"))
  72. .arg(includedir)
  73. .arg("install_headers"),
  74. )
  75. .unwrap();
  76. let bpf_dir = manifest_dir.join("bpf");
  77. let mut target_arch = OsString::new();
  78. target_arch.push("-D__TARGET_ARCH_");
  79. let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
  80. if arch == "x86_64" {
  81. target_arch.push("x86");
  82. } else if arch == "aarch64" {
  83. target_arch.push("arm64");
  84. } else {
  85. target_arch.push(arch);
  86. };
  87. for (src, dst) in c_bpf_probes {
  88. let src = bpf_dir.join(src);
  89. println!("cargo:rerun-if-changed={}", src.to_str().unwrap());
  90. exec(
  91. Command::new("clang")
  92. .arg("-I")
  93. .arg(&libbpf_headers_dir)
  94. .args(["-g", "-O2", "-target", target, "-c"])
  95. .arg(&target_arch)
  96. .arg(src)
  97. .arg("-o")
  98. .arg(dst),
  99. )
  100. .unwrap();
  101. }
  102. let target = format!("{target}-unknown-none");
  103. // Teach cargo about our dependencies.
  104. let mut visited = HashSet::new();
  105. let mut frontier = vec![INTEGRATION_EBPF_PACKAGE];
  106. while let Some(package) = frontier.pop() {
  107. if !visited.insert(package) {
  108. continue;
  109. }
  110. let Package { dependencies, .. } = packages.get(package).unwrap();
  111. for Dependency { name, path, .. } in dependencies {
  112. if let Some(path) = path {
  113. println!("cargo:rerun-if-changed={}", path.as_str());
  114. frontier.push(name);
  115. }
  116. }
  117. }
  118. // Create a symlink in the out directory to work around the fact that cargo ignores anything
  119. // in `$CARGO_HOME`, which is also where `cargo install` likes to place binaries. Cargo will
  120. // stat through the symlink and discover that bpf-linker has changed.
  121. //
  122. // This was introduced in https://github.com/rust-lang/cargo/commit/99f841c.
  123. {
  124. let bpf_linker = which("bpf-linker").unwrap();
  125. let bpf_linker_symlink = out_dir.join("bpf-linker");
  126. match fs::remove_file(&bpf_linker_symlink) {
  127. Ok(()) => {}
  128. Err(err) => {
  129. if err.kind() != std::io::ErrorKind::NotFound {
  130. panic!("failed to remove symlink: {err}")
  131. }
  132. }
  133. }
  134. std::os::unix::fs::symlink(&bpf_linker, &bpf_linker_symlink).unwrap();
  135. println!(
  136. "cargo:rerun-if-changed={}",
  137. bpf_linker_symlink.to_str().unwrap()
  138. );
  139. }
  140. let mut cmd = Command::new("cargo");
  141. cmd.args([
  142. "build",
  143. "-p",
  144. "integration-ebpf",
  145. "-Z",
  146. "build-std=core",
  147. "--release",
  148. "--message-format=json",
  149. "--target",
  150. &target,
  151. ]);
  152. // Workaround for https://github.com/rust-lang/cargo/issues/6412 where cargo flocks itself.
  153. let ebpf_target_dir = out_dir.join("integration-ebpf");
  154. cmd.arg("--target-dir").arg(&ebpf_target_dir);
  155. let mut child = cmd
  156. .stdout(Stdio::piped())
  157. .spawn()
  158. .unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
  159. let Child { stdout, .. } = &mut child;
  160. let stdout = stdout.take().unwrap();
  161. let reader = BufReader::new(stdout);
  162. let mut executables = Vec::new();
  163. let mut compiler_messages = String::new();
  164. for message in Message::parse_stream(reader) {
  165. #[allow(clippy::collapsible_match)]
  166. match message.expect("valid JSON") {
  167. Message::CompilerArtifact(Artifact {
  168. executable,
  169. target: Target { name, .. },
  170. ..
  171. }) => {
  172. if let Some(executable) = executable {
  173. executables.push((name, executable.into_std_path_buf()));
  174. }
  175. }
  176. Message::CompilerMessage(CompilerMessage { message, .. }) => {
  177. writeln!(&mut compiler_messages, "{message}").unwrap()
  178. }
  179. _ => {}
  180. }
  181. }
  182. let status = child
  183. .wait()
  184. .unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
  185. match status.code() {
  186. Some(code) => match code {
  187. 0 => {}
  188. code => panic!("{cmd:?} exited with status code {code}:\n{compiler_messages}"),
  189. },
  190. None => panic!("{cmd:?} terminated by signal"),
  191. }
  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, .. } = packages.get(INTEGRATION_EBPF_PACKAGE).unwrap();
  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. }