build.rs 7.3 KB

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