diff options
Diffstat (limited to 'build.rs')
-rw-r--r-- | build.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7983299 --- /dev/null +++ b/build.rs @@ -0,0 +1,180 @@ +//! This build script leverages `bindgen` to generate rust sys files that link to the libnftnl +//! library. It retrieves the includes needed by `bindgen` using `pkg_config`, and tells cargo +//! the directives needed by the linker to link against the exported symbols. + +use bindgen; +use lazy_static::lazy_static; +use pkg_config; +use regex::{Captures, Regex}; +use std::borrow::Cow; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +const SYS_HEADER_FILE: &str = "include/wrapper.h"; +const SYS_BINDINGS_FILE: &str = "src/sys.rs"; +const TESTS_HEADER_FILE: &str = "include/tests_wrapper.h"; +const TESTS_BINDINGS_FILE: &str = "tests/sys.rs"; +const MIN_LIBNFTNL_VERSION: &str = "1.0.6"; +const MIN_LIBMNL_VERSION: &str = "1.0.0"; + + +fn main() { + pkg_config_mnl(); + let clang_args = pkg_config_nftnl(); + generate_sys(clang_args.into_iter()); + generate_tests_sys(); +} + +/// Setup rust linking directives for libnftnl, and return the include directory list. +fn pkg_config_nftnl() -> Vec<String> { + let mut res = vec![]; + + if let Some(lib_dir) = get_env("LIBNFTNL_LIB_DIR") { + if !lib_dir.is_dir() { + panic!( + "libnftnl library directory does not exist: {}", + lib_dir.display() + ); + } + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-lib=nftnl"); + } else { + // Trying with pkg-config instead + println!("Minimum libnftnl version: {}", MIN_LIBNFTNL_VERSION); + let pkg_config_res = pkg_config::Config::new() + .atleast_version(MIN_LIBNFTNL_VERSION) + .probe("libnftnl") + .unwrap(); + for path in pkg_config_res.include_paths { + res.push(format!("-I{}", path.to_str().unwrap())); + } + } + + res +} + +/// Setup rust linking directives for libmnl. +fn pkg_config_mnl() { + if let Some(lib_dir) = get_env("LIBMNL_LIB_DIR") { + if !lib_dir.is_dir() { + panic!( + "libmnl library directory does not exist: {}", + lib_dir.display() + ); + } + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-lib=mnl"); + } else { + // Trying with pkg-config instead + pkg_config::Config::new() + .atleast_version(MIN_LIBMNL_VERSION) + .probe("libmnl") + .unwrap(); + } +} + +fn get_env(var: &'static str) -> Option<PathBuf> { + println!("cargo:rerun-if-env-changed={}", var); + env::var_os(var).map(PathBuf::from) +} + +/// `bindgen`erate a rust sys file from the C headers of the nftnl library. +fn generate_sys(clang_args: impl Iterator<Item = String>) { + // Tell cargo to invalidate the built crate whenever the headers change. + println!("cargo:rerun-if-changed={}", SYS_HEADER_FILE); + + let bindings = bindgen::Builder::default() + .header(SYS_HEADER_FILE) + .clang_args(clang_args) + .generate_comments(false) + .prepend_enum_name(false) + .use_core() + .whitelist_function("^nftnl_.+$") + .whitelist_type("^nftnl_.+$") + .whitelist_var("^nftnl_.+$") + .whitelist_var("^NFTNL_.+$") + .blacklist_type("(FILE|iovec)") + .blacklist_type("^_IO_.+$") + .blacklist_type("^__.+$") + .blacklist_type("nlmsghdr") + .raw_line("#![allow(non_camel_case_types)]\n\n") + .raw_line("pub use libc;") + .raw_line("use libc::{c_char, c_int, c_ulong, c_void, iovec, nlmsghdr, FILE};") + .raw_line("use core::option::Option;") + .ctypes_prefix("libc") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Error: unable to generate bindings"); + + let mut s = bindings.to_string() + // Add newlines because in alpine bindgen doesn't add them after + // statements. + .replace(" ; ", ";\n") + .replace("#[derive(Debug, Copy, Clone)]", ""); + let re = Regex::new(r"libc::(c_[a-z]*)").unwrap(); + s = re.replace_all(&s, "$1").into(); + let re = Regex::new(r"::core::option::(Option)").unwrap(); + s = re.replace_all(&s, "$1").into(); + let re = Regex::new(r"_bindgen_ty_[0-9]+").unwrap(); + s = re.replace_all(&s, "u32").into(); + // Change struct bodies to c_void. + let re = Regex::new(r"(pub struct .*) \{\n *_unused: \[u8; 0\],\n\}\n").unwrap(); + s = re.replace_all(&s, "$1(c_void);\n").into(); + let re = Regex::new(r"pub type u32 = u32;\n").unwrap(); + s = re.replace_all(&s, "").into(); + + // Write the bindings to the rust header file. + let out_path = PathBuf::from(SYS_BINDINGS_FILE); + File::create(out_path) + .expect("Error: could not create rust header file.") + .write_all(&s.as_bytes()) + .expect("Error: could not write to the rust header file."); +} + +/// `bindgen`erate a rust sys file from the C kernel headers of the nf_tables capabilities. +/// Used in the rustables tests. +fn generate_tests_sys() { + // Tell cargo to invalidate the built crate whenever the headers change. + println!("cargo:rerun-if-changed={}", TESTS_HEADER_FILE); + + let bindings = bindgen::Builder::default() + .header(TESTS_HEADER_FILE) + .generate_comments(false) + .prepend_enum_name(false) + .raw_line("#![allow(non_camel_case_types, dead_code)]\n\n") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Error: unable to generate bindings needed for tests."); + + // Add newlines because in alpine bindgen doesn't add them after statements. + let s = bindings.to_string().replace(" ; ", ";\n"); + let s = reformat_units(&s); + + // Write the bindings to the rust header file. + let out_path = PathBuf::from(TESTS_BINDINGS_FILE); + File::create(out_path) + .expect("Error: could not create rust header file.") + .write_all(&s.as_bytes()) + .expect("Error: could not write to the rust header file."); +} + +/// Recast nft_*_attributes from u32 to u16 in header string `header`. +fn reformat_units(header: &str) -> Cow<str> { + lazy_static! { + static ref RE: Regex = Regex::new(r"(pub type nft[a-zA-Z_]*_attributes) = u32;").unwrap(); + } + RE.replace_all(header, |captures: &Captures| { + format!("{} = u16;", &captures[1]) + }) +} + |