#[cfg(feature = "build")]
use std::{
env, fs,
io::{BufRead as _, BufReader},
path::PathBuf,
process::{Child, Command, Stdio},
};
#[cfg(feature = "build")]
use anyhow::{anyhow, Context as _, Result};
#[cfg(feature = "build")]
use cargo_metadata::{Artifact, CompilerMessage, Message, Package, Target};
fn main() -> anyhow::Result<()> {
#[cfg(feature = "build")]
return build_ebpf();
#[allow(unreachable_code)]
Ok(())
}
#[cfg(feature = "build")]
fn build_ebpf() -> anyhow::Result<()> {
use anyhow::{anyhow, Context as _};
use aya_build::cargo_metadata;
let cargo_metadata::Metadata { packages, .. } = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.context("MetadataCommand::exec")?;
let ebpf_package = packages
.into_iter()
.find(|cargo_metadata::Package { name, .. }| name == "emd-ebpf-impl")
.ok_or_else(|| anyhow!("emd-ebpf-impl package not found"))?;
inner_build_ebpf([ebpf_package])
}
#[cfg(feature = "build")]
fn inner_build_ebpf(packages: impl IntoIterator<Item = Package>) -> Result<()> {
let out_dir = env::var_os("OUT_DIR").ok_or(anyhow!("OUT_DIR not set"))?;
let out_dir = PathBuf::from(out_dir);
let endian =
env::var_os("CARGO_CFG_TARGET_ENDIAN").ok_or(anyhow!("CARGO_CFG_TARGET_ENDIAN not set"))?;
let target = if endian == "big" {
"bpfeb"
} else if endian == "little" {
"bpfel"
} else {
return Err(anyhow!("unsupported endian={endian:?}"));
};
let arch =
env::var_os("CARGO_CFG_TARGET_ARCH").ok_or(anyhow!("CARGO_CFG_TARGET_ARCH not set"))?;
let target = format!("{target}-unknown-none");
for Package {
name,
manifest_path,
..
} in packages
{
let dir = manifest_path
.parent()
.ok_or(anyhow!("no parent for {manifest_path}"))?;
println!("cargo:rerun-if-changed={dir}");
let mut cmd = Command::new("cargo");
cmd.args([
"+nightly",
"build",
"--package",
&name,
"-Z",
"build-std=core",
"--bins",
"--message-format=json",
"--release",
"--target",
&target,
]);
#[cfg(feature = "log")]
cmd.arg("--features").arg("log");
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", &arch);
for key in ["RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
cmd.env_remove(key);
}
let target_dir = out_dir.join(name);
cmd.arg("--target-dir").arg(&target_dir);
let mut child = cmd
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("failed to spawn {cmd:?}"))?;
let Child { stdout, stderr, .. } = &mut child;
let stderr = stderr.take().expect("stderr");
let stderr = BufReader::new(stderr);
let stderr = std::thread::spawn(move || {
for line in stderr.lines() {
let line = line.expect("read line");
println!("cargo:warning={line}");
}
});
let stdout = stdout.take().expect("stdout");
let stdout = BufReader::new(stdout);
let mut executables = Vec::new();
for message in Message::parse_stream(stdout) {
#[allow(clippy::collapsible_match)]
match message.expect("valid JSON") {
Message::CompilerArtifact(Artifact {
executable,
target: Target { name, .. },
..
}) => {
if let Some(executable) = executable {
executables.push((name, executable.into_std_path_buf()));
}
}
Message::CompilerMessage(CompilerMessage { message, .. }) => {
for line in message.rendered.unwrap_or_default().split('\n') {
println!("cargo:warning={line}");
}
}
Message::TextLine(line) => {
println!("cargo:warning={line}");
}
_ => {}
}
}
let status = child
.wait()
.with_context(|| format!("failed to wait for {cmd:?}"))?;
if !status.success() {
return Err(anyhow!("{cmd:?} failed: {status:?}"));
}
match stderr.join().map_err(std::panic::resume_unwind) {
Ok(()) => {}
Err(err) => match err {},
}
for (name, binary) in executables {
let dst = out_dir.join(name);
let _: u64 = fs::copy(&binary, &dst)
.with_context(|| format!("failed to copy {binary:?} to {dst:?}"))?;
}
}
Ok(())
}