use std::env; use std::fs; use std::path::PathBuf; use bindgen::RustEdition; use bindgen::callbacks::{ItemKind, ParseCallbacks}; use heck::ToSnekCase; use heck::ToUpperCamelCase; use itertools::Itertools; #[derive(Debug)] struct DoxygenCallbacks; impl ParseCallbacks for DoxygenCallbacks { fn process_comment(&self, comment: &str) -> Option { match doxygen_bindgen::transform(comment) { Ok(res) => Some(res), Err(err) => { println!("cargo::warning=Problem processing doxygen comment: {comment}\n{err}"); None }, } } } #[inline] fn strip_variant_prefix( prefix: &'static str, enum_name: &str, variant: &str, ) -> Result { variant .strip_prefix(prefix) .map(str::to_owned) .ok_or(format!( "[bindfmt] enum {enum_name} expected prefix \"{prefix}\" but got {}", &variant )) } /// Bindfmt is just the name im giving to the callbacks /// that handle renaming C/C++ tokens. #[derive(Debug)] struct BindfmtCallbacks; impl ParseCallbacks for BindfmtCallbacks { fn enum_variant_name( &self, _enum_name: Option<&str>, _original_variant_name: &str, _variant_value: bindgen::callbacks::EnumVariantValue, ) -> Option { let variant = _original_variant_name.to_upper_camel_case(); _enum_name.map(|enum_name| match enum_name.to_upper_camel_case().as_ref() { "NixVerbosity" => strip_variant_prefix("NixLvl", enum_name, &variant).unwrap(), "NixErr" => strip_variant_prefix("NixErr", enum_name, &variant) .or_else(|_| strip_variant_prefix("Nix", enum_name, &variant)) .unwrap(), "ValueType" => strip_variant_prefix("NixType", enum_name, &variant) .or_else(|_| strip_variant_prefix("N", enum_name, &variant)) .unwrap(), _ => variant, }) } fn item_name(&self, _item_info: bindgen::callbacks::ItemInfo) -> Option { Some(match _item_info.kind { ItemKind::Type => _item_info.name.to_upper_camel_case(), _ => _item_info.name.to_snek_case(), }) } fn include_file(&self, _filename: &str) { eprintln!("[debug] including file: {}", _filename); } } const LIBS: &[&'static str] = &[ #[cfg(feature = "nix-util-c")] "nix-util-c", #[cfg(feature = "nix-store-c")] "nix-store-c", #[cfg(feature = "nix-expr-c")] "nix-expr-c", #[cfg(feature = "nix-fetchers-c")] "nix-fetchers-c", #[cfg(feature = "nix-flake-c")] "nix-flake-c", #[cfg(feature = "nix-main-c")] "nix-main-c", ]; fn main() { let include_paths: Vec = LIBS .iter() .map(|&name| { let lib = pkg_config::probe_library(name) .expect(&format!("Unable to find .pc file for {}", name)); for p in lib.link_files { println!("cargo::rustc-link-lib={}", p.display()); } lib.include_paths }) .flatten() .unique() .collect(); // let clang_target = format!( // "--target={}", // env::var("CARGO_BUILD_TARGET") // .expect("Environment variable `CARGO_BUILD_TARGET` not set! nixide/build.rs won't be able to set pointer sizes correctly!")); let clang_args: Vec = vec![ "-x", "c++", "-std=c++23", "--target=x86_64-unknown-linux-gnu", // DEBUG ] .into_iter() .map(|s: &str| s.to_owned()) .chain(include_paths.iter().map(|p| format!("-I{}", p.display()))) .collect(); dbg!(&clang_args); cc::Build::new() // .cargo_output(true) // .cargo_warnings(true) // .cargo_metadata(true) // .cargo_debug(cfg!(debug_assertions)) .cpp(true) .std("c++23") // libnix compiles against `-std=c++23` .cpp_link_stdlib("stdc++") // use libstdc++ .flags(["-fconcepts-diagnostics-depth=2"]) .file("libnixide-c/nixide_api_flake.cc") .file("libnixide-c/nixide_api_fetchers.cc") // .files(LIBS.iter().map(|s| (*s).strip_prefix("nix-").unwrap().strip_suffix("-c").unwrap())) .opt_level((!cfg!(debug_assertions)) as u32 * 3) .includes(&include_paths) .compile("libnixide"); let mut builder = bindgen::Builder::default() .rust_edition(RustEdition::Edition2024) .clang_args(clang_args) // Add `doxygen_bindgen` callbacks .parse_callbacks(Box::new(DoxygenCallbacks)) .parse_callbacks(Box::new(BindfmtCallbacks)) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) // Format generated bindings with rustfmt .formatter(bindgen::Formatter::Rustfmt) .rustfmt_configuration_file(std::fs::canonicalize("rustfmt.toml").ok()) // Control allow/block listing .allowlist_recursively(true) // DEBUG(TEMP): should remain `false` // .allowlist_file(r".*nix_api_[a-z]+(_internal)?\.h") .allowlist_file(r".*nix_api_[a-z]+\.h") // .allowlist_type("nix_.*") // .allowlist_function("nix_.*") // DEBUG // .allowlist_type(r"nix_locked_flake") // .allowlist_type(r"nix_flake_settings") // .allowlist_type(r"nix_flake_reference") // .allowlist_type(r"nix_flake_reference_parse_flags") // .allowlist_type(r"nix_flake_lock_flags") // DEBUG // .allowlist_file(r".+-gcc-14\.3\.0/include/c\+\+/14\.3\.0/optional") // allow optional type // .blocklist_file(r".*-gcc-[^/]+/include/c++/[0-9\.]+/^(optional).*") // allow optional type // .blocklist_file(r".*-(glibc|clang-wrapper|boost)-.*") // block stdlib (gcc|glibc|clang-wrapper|boost) // .blocklist_type(r".*Optional.*") .layout_tests(false) // DEBUG .use_core() // use ::core instead of ::std .ctypes_prefix("::core::ffi") // use ::core::ffi instead of ::std::os::raw .time_phases(true) // Wrap all unsafe operations in unsafe blocks .wrap_unsafe_ops(true) .trust_clang_mangling(true) .respect_cxx_access_specs(true) .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: false }) // .translate_enum_integer_types(false) .size_t_is_usize(true) .use_distinct_char16_t(false) // DEBUG (comment this) .generate_comments(false) .generate_cstr(true) // use &CStr instead of &[u8] .fit_macro_constants(true) // DEBUG (comment this) .explicit_padding(true) .enable_cxx_namespaces() .represent_cxx_operators(false) // DEBUG (comment this) .enable_function_attribute_detection() .raw_line("/** These bindings were auto-generated for the Nixide project (https://github.com/cry128/nixide) */"); // Register the input headers we would like to generate bindings for builder = LIBS .iter() .map(|lib| { let path = format!("include/{}.h", lib.strip_suffix("-c").unwrap()); assert!(fs::exists(&path).unwrap()); // Invalidate the built crate if the binding headers change println!("cargo::rerun-if-changed={path}"); path }) .fold(builder, |builder, path| builder.header(path)); // Write the bindings to the $OUT_DIR/bindings.rs file let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); let bindings = builder.generate().expect("Unable to generate bindings"); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); }