use std::env;
use std::ffi;
use std::fs;
use std::fs::read_dir;
use std::path;
use std::path::Path;
use std::process;
use nix::fcntl;
fn emit_rerun_directives_for_contents(dir: &Path) {
for result in read_dir(dir).unwrap() {
let file = result.unwrap();
println!("cargo:rerun-if-changed={}", file.path().display());
}
}
#[cfg(feature = "bindgen")]
fn generate_bindings(src_dir: path::PathBuf) {
use std::collections::HashSet;
#[derive(Debug)]
struct IgnoreMacros(HashSet<&'static str>);
impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}
let ignored_macros = IgnoreMacros(
vec![
"BTF_KIND_FUNC",
"BTF_KIND_FUNC_PROTO",
"BTF_KIND_VAR",
"BTF_KIND_DATASEC",
"BTF_KIND_FLOAT",
"BTF_KIND_DECL_TAG",
"BTF_KIND_TYPE_TAG",
"BTF_KIND_ENUM64",
]
.into_iter()
.collect(),
);
#[cfg(feature = "bindgen-source")]
let out_dir = &src_dir.join("src");
#[cfg(not(feature = "bindgen-source"))]
let out_dir =
&path::PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR should always be set"));
bindgen::Builder::default()
.derive_default(true)
.explicit_padding(true)
.default_enum_style(bindgen::EnumVariation::Consts)
.size_t_is_usize(false)
.prepend_enum_name(false)
.layout_tests(false)
.generate_comments(false)
.emit_builtins()
.allowlist_function("bpf_.+")
.allowlist_function("btf_.+")
.allowlist_function("libbpf_.+")
.allowlist_function("perf_.+")
.allowlist_function("ring_buffer_.+")
.allowlist_function("user_ring_buffer_.+")
.allowlist_function("vdprintf")
.allowlist_type("bpf_.+")
.allowlist_type("btf_.+")
.allowlist_type("xdp_.+")
.allowlist_type("perf_.+")
.allowlist_var("BPF_.+")
.allowlist_var("BTF_.+")
.allowlist_var("XDP_.+")
.allowlist_var("PERF_.+")
.parse_callbacks(Box::new(ignored_macros))
.header("bindings.h")
.clang_arg(format!("-I{}", src_dir.join("libbpf/include").display()))
.clang_arg(format!(
"-I{}",
src_dir.join("libbpf/include/uapi").display()
))
.generate()
.expect("Unable to generate bindings")
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
}
#[cfg(not(feature = "bindgen"))]
fn generate_bindings(_: path::PathBuf) {}
fn pkg_check(pkg: &str) {
if process::Command::new(pkg)
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.status()
.is_err()
{
panic!(
"{} is required to compile libbpf-sys with the selected set of features",
pkg
);
}
}
fn main() {
let src_dir = path::PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
generate_bindings(src_dir.clone());
let vendored_libbpf = cfg!(feature = "vendored-libbpf");
let vendored_libelf = cfg!(feature = "vendored-libelf");
let vendored_zlib = cfg!(feature = "vendored-zlib");
println!("Using feature vendored-libbpf={}", vendored_libbpf);
println!("Using feature vendored-libelf={}", vendored_libelf);
println!("Using feature vendored-zlib={}", vendored_zlib);
let static_libbpf = cfg!(feature = "static-libbpf");
let static_libelf = cfg!(feature = "static-libelf");
let static_zlib = cfg!(feature = "static-zlib");
println!("Using feature static-libbpf={}", static_libbpf);
println!("Using feature static-libelf={}", static_libelf);
println!("Using feature static-zlib={}", static_zlib);
if cfg!(feature = "novendor") {
println!("cargo:warning=the `novendor` feature of `libbpf-sys` is deprecated; build without features instead");
println!(
"cargo:rustc-link-lib={}bpf",
if static_libbpf { "static=" } else { "" }
);
return;
}
let out_dir = path::PathBuf::from(env::var_os("OUT_DIR").unwrap());
if vendored_libelf {
pkg_check("autoreconf");
pkg_check("autopoint");
pkg_check("flex");
pkg_check("bison");
pkg_check("gawk");
}
let (compiler, mut cflags) = if vendored_libbpf || vendored_libelf || vendored_zlib {
pkg_check("make");
pkg_check("pkg-config");
let compiler = cc::Build::new().try_get_compiler().expect(
"a C compiler is required to compile libbpf-sys using the vendored copy of libbpf",
);
let mut cflags = compiler.cflags_env();
println!("cargo:rerun-if-env-changed=LIBBPF_SYS_EXTRA_CFLAGS");
if let Some(extra_cflags) = env::var_os("LIBBPF_SYS_EXTRA_CFLAGS") {
cflags.push(" ");
cflags.push(extra_cflags);
}
(Some(compiler), cflags)
} else {
(None, ffi::OsString::new())
};
if vendored_zlib {
make_zlib(compiler.as_ref().unwrap(), &src_dir, &out_dir);
cflags.push(format!(" -I{}/zlib/", src_dir.display()));
}
if vendored_libelf {
make_elfutils(compiler.as_ref().unwrap(), &src_dir, &out_dir);
cflags.push(format!(" -I{}/elfutils/libelf/", src_dir.display()));
}
if vendored_libbpf {
make_libbpf(compiler.as_ref().unwrap(), &cflags, &src_dir, &out_dir);
}
println!(
"cargo:rustc-link-search=native={}",
out_dir.to_string_lossy()
);
println!(
"cargo:rustc-link-lib={}elf",
if static_libelf { "static=" } else { "" }
);
println!(
"cargo:rustc-link-lib={}z",
if static_zlib { "static=" } else { "" }
);
println!(
"cargo:rustc-link-lib={}bpf",
if static_libbpf { "static=" } else { "" }
);
println!("cargo:include={}/include", out_dir.to_string_lossy());
println!("cargo:rerun-if-env-changed=LIBBPF_SYS_LIBRARY_PATH");
if let Ok(lib_path) = env::var("LIBBPF_SYS_LIBRARY_PATH") {
for path in lib_path.split(':') {
if !path.is_empty() {
println!("cargo:rustc-link-search=native={}", path);
}
}
}
}
fn make_zlib(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) {
let src_dir = src_dir.join("zlib");
let file = std::fs::File::open(src_dir.join("README")).unwrap();
let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap();
let status = process::Command::new("./configure")
.arg("--static")
.arg("--prefix")
.arg(".")
.arg("--libdir")
.arg(out_dir)
.env("CC", compiler.path())
.env("CFLAGS", compiler.cflags_env())
.current_dir(&src_dir)
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("install")
.arg("-j")
.arg(format!("{}", num_cpus()))
.current_dir(&src_dir)
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("distclean")
.current_dir(&src_dir)
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
emit_rerun_directives_for_contents(&src_dir);
}
fn make_elfutils(compiler: &cc::Tool, src_dir: &path::Path, out_dir: &path::Path) {
let file = std::fs::File::open(src_dir.join("elfutils/README")).unwrap();
let _lock = fcntl::Flock::lock(file, fcntl::FlockArg::LockExclusive).unwrap();
let flags = compiler
.cflags_env()
.into_string()
.expect("failed to get cflags");
let mut cflags: String = flags
.split_whitespace()
.filter_map(|arg| {
if arg != "-static" {
Some(format!(" {arg}"))
} else {
None
}
})
.collect();
#[cfg(target_arch = "aarch64")]
cflags.push_str(" -Wno-error=stringop-overflow");
cflags.push_str(&format!(" -I{}/zlib/", src_dir.display()));
let status = process::Command::new("autoreconf")
.arg("--install")
.arg("--force")
.current_dir(src_dir.join("elfutils"))
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let out_lib = format!("-L{}", out_dir.display());
let status = process::Command::new("./configure")
.arg("--enable-maintainer-mode")
.arg("--disable-debuginfod")
.arg("--disable-libdebuginfod")
.arg("--disable-demangler")
.arg("--without-zstd")
.arg("--prefix")
.arg(src_dir.join("elfutils/prefix_dir"))
.arg("--host")
.arg({
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let arch = match arch.as_str() {
"riscv64gc" => "riscv64",
"riscv32gc" => "riscv32",
other => other,
};
let vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
format!("{arch}-{vendor}-{os}-{env}")
})
.arg("--libdir")
.arg(out_dir)
.env("CC", compiler.path())
.env("CXX", compiler.path())
.env("CFLAGS", &cflags)
.env("CXXFLAGS", &cflags)
.env("LDFLAGS", &out_lib)
.current_dir(src_dir.join("elfutils"))
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("-j")
.arg(format!("{}", num_cpus()))
.arg("BUILD_STATIC_ONLY=y")
.current_dir(src_dir.join("elfutils/lib"))
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("install")
.arg("-j")
.arg(format!("{}", num_cpus()))
.arg("BUILD_STATIC_ONLY=y")
.current_dir(src_dir.join("elfutils/libelf"))
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("distclean")
.current_dir(src_dir.join("elfutils"))
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
emit_rerun_directives_for_contents(&src_dir.join("elfutils").join("src"));
}
fn make_libbpf(
compiler: &cc::Tool,
cflags: &ffi::OsStr,
src_dir: &path::Path,
out_dir: &path::Path,
) {
let src_dir = src_dir.join("libbpf/src");
let obj_dir = path::PathBuf::from(&out_dir.join("obj").into_os_string());
let _ = fs::create_dir(&obj_dir);
let status = process::Command::new("make")
.arg("install")
.arg("-j")
.arg(format!("{}", num_cpus()))
.env("BUILD_STATIC_ONLY", "y")
.env("PREFIX", "/")
.env("LIBDIR", "")
.env("OBJDIR", &obj_dir)
.env("DESTDIR", out_dir)
.env("CC", compiler.path())
.env("CFLAGS", cflags)
.current_dir(&src_dir)
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
let status = process::Command::new("make")
.arg("clean")
.current_dir(&src_dir)
.status()
.expect("could not execute make");
assert!(status.success(), "make failed");
emit_rerun_directives_for_contents(&src_dir);
}
fn num_cpus() -> usize {
std::thread::available_parallelism().map_or(1, |count| count.get())
}