blob: 6b38454150f7a91846c90a9021abb0156cb82eb2 [file] [log] [blame] [edit]
#![allow(clippy::let_unit_value)]
use std::env;
use std::env::consts::ARCH;
use std::ffi::OsStr;
use std::fs::read_dir;
use std::io::Error;
use std::io::ErrorKind;
use std::io::Result;
use std::ops::Deref as _;
use std::path::Path;
use std::process::Command;
use std::process::Stdio;
/// Format a command with the given list of arguments as a string.
fn format_command<C, A, S>(command: C, args: A) -> String
where
C: AsRef<OsStr>,
A: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
args.into_iter().fold(
command.as_ref().to_string_lossy().into_owned(),
|mut cmd, arg| {
cmd += " ";
cmd += arg.as_ref().to_string_lossy().deref();
cmd
},
)
}
/// Run a command with the provided arguments.
fn run<C, A, S>(command: C, args: A) -> Result<()>
where
C: AsRef<OsStr>,
A: IntoIterator<Item = S> + Clone,
S: AsRef<OsStr>,
{
let instance = Command::new(command.as_ref())
.stdin(Stdio::null())
.stdout(Stdio::null())
.env_clear()
.envs(env::vars().filter(|(k, _)| k == "PATH"))
.args(args.clone())
.output()
.map_err(|err| {
Error::new(
ErrorKind::Other,
format!(
"failed to run `{}`: {err}",
format_command(command.as_ref(), args.clone())
),
)
})?;
if !instance.status.success() {
let code = if let Some(code) = instance.status.code() {
format!(" ({code})")
} else {
" (terminated by signal)".to_string()
};
let stderr = String::from_utf8_lossy(&instance.stderr);
let stderr = stderr.trim_end();
let stderr = if !stderr.is_empty() {
format!(": {stderr}")
} else {
String::new()
};
Err(Error::new(
ErrorKind::Other,
format!(
"`{}` reported non-zero exit-status{code}{stderr}",
format_command(command, args)
),
))
} else {
Ok(())
}
}
fn adjust_mtime(path: &Path) -> Result<()> {
// Note that `OUT_DIR` is only present at runtime.
let out_dir = env::var("OUT_DIR").unwrap();
// The $OUT_DIR/output file is (in current versions of Cargo [as of
// 1.69]) the file containing the reference time stamp that Cargo
// checks to determine whether something is considered outdated and
// in need to be rebuild. It's an implementation detail, yes, but we
// don't rely on it for anything essential.
let output = Path::new(&out_dir)
.parent()
.ok_or_else(|| Error::new(ErrorKind::Other, "OUT_DIR has no parent"))?
.join("output");
if !output.exists() {
// The file may not exist for legitimate reasons, e.g., when we
// build for the very first time. If there is not reference there
// is nothing for us to do, so just bail.
return Ok(())
}
let () = run(
"touch",
[
"-m".as_ref(),
"--reference".as_ref(),
output.as_os_str(),
path.as_os_str(),
],
)?;
Ok(())
}
/// Compile `src` into `dst` using the provided compiler.
fn compile(compiler: &str, src: &Path, dst: &Path, options: &[&str]) {
let dst = src.with_file_name(dst);
println!("cargo:rerun-if-changed={}", src.display());
println!("cargo:rerun-if-changed={}", dst.display());
let () = run(
compiler,
options
.iter()
.map(OsStr::new)
.chain([src.as_os_str(), "-o".as_ref(), dst.as_os_str()]),
)
.unwrap_or_else(|err| panic!("failed to run `{compiler}`: {err}"));
let () = adjust_mtime(&dst).unwrap();
}
/// Extract vendored libbpf header files into a directory.
#[cfg(feature = "generate-test-files")]
fn extract_libbpf_headers(target_dir: &Path) {
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
let dir = target_dir.join("bpf");
let () = fs::create_dir_all(&dir).unwrap();
for (filename, contents) in libbpf_sys::API_HEADERS.iter() {
let path = dir.as_path().join(filename);
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.write_all(contents.as_bytes()).unwrap();
}
}
#[cfg(feature = "generate-test-files")]
fn with_bpf_headers<F>(f: F)
where
F: FnOnce(&Path),
{
use tempfile::tempdir;
let header_parent_dir = tempdir().unwrap();
let () = extract_libbpf_headers(header_parent_dir.path());
let () = f(header_parent_dir.path());
}
#[cfg(not(feature = "generate-test-files"))]
fn with_bpf_headers<F>(_f: F)
where
F: FnOnce(&Path),
{
unimplemented!()
}
/// Prepare the various test files.
fn prepare_test_files(crate_root: &Path) {
let bin_dir = crate_root.join("tests").join("bin");
let src_dir = bin_dir.join("src");
let include = crate_root.join("../vmlinux/include").join(ARCH);
with_bpf_headers(|bpf_hdr_dir| {
for result in read_dir(&src_dir).unwrap() {
let entry = result.unwrap();
let src = entry.file_name();
let obj = Path::new(&src).with_extension("o");
let src = src_dir.join(&src);
let dst = bin_dir.join(obj);
let arch = option_env!("CARGO_CFG_TARGET_ARCH").unwrap_or(ARCH);
let arch = match arch {
"x86_64" => "x86",
"aarch64" => "arm64",
"powerpc64" => "powerpc",
"s390x" => "s390",
x => x,
};
compile(
"clang",
&src,
&dst,
&[
"-g",
"-O2",
"-target",
"bpf",
"-c",
"-I",
include.to_str().unwrap(),
"-I",
&format!("{}", bpf_hdr_dir.display()),
"-D",
&format!("__TARGET_ARCH_{arch}"),
],
);
}
})
}
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
if cfg!(feature = "generate-test-files") && !cfg!(feature = "dont-generate-test-files") {
prepare_test_files(crate_dir.as_ref());
}
}