|  | extern crate bindgen; | 
|  |  | 
|  | use bindgen::callbacks::{ | 
|  | DeriveInfo, IntKind, ItemInfo, MacroParsingBehavior, ParseCallbacks, Token, | 
|  | TokenKind, | 
|  | }; | 
|  | use bindgen::{Builder, EnumVariation, Formatter}; | 
|  | use std::collections::HashSet; | 
|  | use std::env; | 
|  | use std::path::PathBuf; | 
|  | use std::sync::{Arc, Mutex, RwLock}; | 
|  |  | 
|  | #[derive(Debug)] | 
|  | struct MacroCallback { | 
|  | macros: Arc<RwLock<HashSet<String>>>, | 
|  | seen_hellos: Mutex<u32>, | 
|  | seen_funcs: Mutex<u32>, | 
|  | } | 
|  |  | 
|  | impl ParseCallbacks for MacroCallback { | 
|  | fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { | 
|  | self.macros.write().unwrap().insert(name.into()); | 
|  |  | 
|  | if name == "MY_ANNOYING_MACRO" { | 
|  | return MacroParsingBehavior::Ignore; | 
|  | } | 
|  |  | 
|  | MacroParsingBehavior::Default | 
|  | } | 
|  |  | 
|  | fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> { | 
|  | match name { | 
|  | "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom { | 
|  | name: "crate::MacroInteger", | 
|  | is_signed: true, | 
|  | }), | 
|  |  | 
|  | _ => None, | 
|  | } | 
|  | } | 
|  |  | 
|  | fn str_macro(&self, name: &str, value: &[u8]) { | 
|  | match name { | 
|  | "TESTMACRO_STRING_EXPR" => { | 
|  | assert_eq!(value, b"string"); | 
|  | *self.seen_hellos.lock().unwrap() += 1; | 
|  | } | 
|  | "TESTMACRO_STRING_EXPANDED" | | 
|  | "TESTMACRO_STRING" | | 
|  | "TESTMACRO_INTEGER" => { | 
|  | // The integer test macro is, actually, not expected to show up here at all -- but | 
|  | // should produce an error if it does. | 
|  | assert_eq!( | 
|  | value, b"Hello Preprocessor!", | 
|  | "str_macro handle received unexpected value" | 
|  | ); | 
|  | *self.seen_hellos.lock().unwrap() += 1; | 
|  | } | 
|  | _ => {} | 
|  | } | 
|  | } | 
|  |  | 
|  | fn func_macro(&self, name: &str, value: &[&[u8]]) { | 
|  | match name { | 
|  | "TESTMACRO_NONFUNCTIONAL" => { | 
|  | panic!("func_macro was called for a non-functional macro"); | 
|  | } | 
|  | "TESTMACRO_FUNCTIONAL_NONEMPTY(TESTMACRO_INTEGER)" => { | 
|  | // Spaces are inserted into the right-hand side of a functional | 
|  | // macro during reconstruction from the tokenization. This might | 
|  | // change in the future, but it is safe by the definition of a | 
|  | // token in C, whereas leaving the spaces out could change | 
|  | // tokenization. | 
|  | assert_eq!(value, &[b"-" as &[u8], b"TESTMACRO_INTEGER"]); | 
|  | *self.seen_funcs.lock().unwrap() += 1; | 
|  | } | 
|  | "TESTMACRO_FUNCTIONAL_EMPTY(TESTMACRO_INTEGER)" => { | 
|  | assert_eq!(value, &[] as &[&[u8]]); | 
|  | *self.seen_funcs.lock().unwrap() += 1; | 
|  | } | 
|  | "TESTMACRO_FUNCTIONAL_TOKENIZED(a,b,c,d,e)" => { | 
|  | assert_eq!( | 
|  | value, | 
|  | &[b"a" as &[u8], b"/", b"b", b"c", b"d", b"##", b"e"] | 
|  | ); | 
|  | *self.seen_funcs.lock().unwrap() += 1; | 
|  | } | 
|  | "TESTMACRO_FUNCTIONAL_SPLIT(a,b)" => { | 
|  | assert_eq!(value, &[b"b", b",", b"a"]); | 
|  | *self.seen_funcs.lock().unwrap() += 1; | 
|  | } | 
|  | "TESTMACRO_STRING_FUNC_NON_UTF8(x)" => { | 
|  | assert_eq!( | 
|  | value, | 
|  | &[b"(" as &[u8], b"x", b"\"\xff\xff\"", b")"] | 
|  | ); | 
|  | *self.seen_funcs.lock().unwrap() += 1; | 
|  | } | 
|  | _ => { | 
|  | // The system might provide lots of functional macros. | 
|  | // Ensure we did not miss handling one that we meant to handle. | 
|  | assert!(!name.starts_with("TESTMACRO_"), "name = {name}"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn item_name(&self, item_info: ItemInfo) -> Option<String> { | 
|  | if item_info.name.starts_with("my_prefixed_") { | 
|  | Some( | 
|  | item_info | 
|  | .name | 
|  | .trim_start_matches("my_prefixed_") | 
|  | .to_string(), | 
|  | ) | 
|  | } else if item_info.name.starts_with("MY_PREFIXED_") { | 
|  | Some( | 
|  | item_info | 
|  | .name | 
|  | .trim_start_matches("MY_PREFIXED_") | 
|  | .to_string(), | 
|  | ) | 
|  | } else { | 
|  | None | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test the "custom derives" capability by adding `PartialEq` to the `Test` struct. | 
|  | fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> { | 
|  | if info.name == "Test" { | 
|  | vec!["PartialEq".into()] | 
|  | } else if info.name == "MyOrderedEnum" { | 
|  | vec!["std::cmp::PartialOrd".into()] | 
|  | } else if info.name == "TestDeriveOnAlias" { | 
|  | vec!["std::cmp::PartialEq".into(), "std::cmp::PartialOrd".into()] | 
|  | } else { | 
|  | vec![] | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test the "custom attributes" capability. | 
|  | fn add_attributes( | 
|  | &self, | 
|  | info: &bindgen::callbacks::AttributeInfo<'_>, | 
|  | ) -> Vec<String> { | 
|  | if info.name == "Test" { | 
|  | vec!["#[cfg_attr(test, derive(PartialOrd))]".into()] | 
|  | } else { | 
|  | vec![] | 
|  | } | 
|  | } | 
|  |  | 
|  | fn modify_macro(&self, _name: &str, tokens: &mut Vec<Token>) { | 
|  | // Handle macros dealing with bit positions of the format HI:LO | 
|  | if tokens.len() == 4 && tokens[2].kind == TokenKind::Punctuation { | 
|  | if let Ok(colon) = std::str::from_utf8(&tokens[2].raw) { | 
|  | if colon != ":" { | 
|  | return; | 
|  | } | 
|  | let high = match std::str::from_utf8(&tokens[1].raw) { | 
|  | Ok(s) => { | 
|  | if let Ok(val) = s.parse::<u16>() { | 
|  | val | 
|  | } else { | 
|  | return; | 
|  | } | 
|  | } | 
|  | Err(_) => { | 
|  | return; | 
|  | } | 
|  | }; | 
|  |  | 
|  | let low = match std::str::from_utf8(&tokens[3].raw) { | 
|  | Ok(s) => { | 
|  | if let Ok(val) = s.parse::<u16>() { | 
|  | val | 
|  | } else { | 
|  | return; | 
|  | } | 
|  | } | 
|  | Err(_) => { | 
|  | return; | 
|  | } | 
|  | }; | 
|  | let value: u32 = ((high as u32) << 16) | low as u32; | 
|  | tokens[1] = Token::from(( | 
|  | TokenKind::Literal, | 
|  | value.to_string().as_bytes(), | 
|  | )); | 
|  | tokens.truncate(2); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl Drop for MacroCallback { | 
|  | fn drop(&mut self) { | 
|  | assert_eq!( | 
|  | *self.seen_hellos.lock().unwrap(), | 
|  | 3, | 
|  | "str_macro handle was not called once for all relevant macros" | 
|  | ); | 
|  | assert_eq!( | 
|  | *self.seen_funcs.lock().unwrap(), | 
|  | 5, | 
|  | "func_macro handle was not called once for all relevant macros" | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[derive(Debug)] | 
|  | struct WrappedVaListCallback; | 
|  |  | 
|  | impl ParseCallbacks for WrappedVaListCallback { | 
|  | fn wrap_as_variadic_fn(&self, name: &str) -> Option<String> { | 
|  | Some(name.to_owned() + "_wrapped") | 
|  | } | 
|  | } | 
|  |  | 
|  | fn setup_macro_test() { | 
|  | cc::Build::new() | 
|  | .cpp(true) | 
|  | .file("cpp/Test.cc") | 
|  | .include("include") | 
|  | .compile("libtest.a"); | 
|  |  | 
|  | let macros = Arc::new(RwLock::new(HashSet::new())); | 
|  |  | 
|  | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); | 
|  | let out_rust_file = out_path.join("test.rs"); | 
|  | let out_rust_file_relative = out_rust_file | 
|  | .strip_prefix(env::current_dir().unwrap().parent().unwrap()) | 
|  | .unwrap(); | 
|  | let out_dep_file = out_path.join("test.d"); | 
|  |  | 
|  | let bindings = Builder::default() | 
|  | .formatter(Formatter::None) | 
|  | .enable_cxx_namespaces() | 
|  | .default_enum_style(EnumVariation::Rust { | 
|  | non_exhaustive: false, | 
|  | }) | 
|  | .raw_line("pub use self::root::*;") | 
|  | .raw_line("extern { fn my_prefixed_function_to_remove(i: i32); }") | 
|  | .module_raw_line("root::testing", "pub type Bar = i32;") | 
|  | .header("cpp/Test.h") | 
|  | .clang_args(&["-x", "c++", "-std=c++11", "-I", "include"]) | 
|  | .parse_callbacks(Box::new(MacroCallback { | 
|  | macros: macros.clone(), | 
|  | seen_hellos: Mutex::new(0), | 
|  | seen_funcs: Mutex::new(0), | 
|  | })) | 
|  | .blocklist_function("my_prefixed_function_to_remove") | 
|  | .constified_enum("my_prefixed_enum_to_be_constified") | 
|  | .opaque_type("my_prefixed_templated_foo<my_prefixed_baz>") | 
|  | .new_type_alias("TestDeriveOnAlias") | 
|  | .depfile(out_rust_file_relative.display().to_string(), &out_dep_file) | 
|  | .generate() | 
|  | .expect("Unable to generate bindings"); | 
|  |  | 
|  | assert!(macros.read().unwrap().contains("TESTMACRO")); | 
|  | bindings | 
|  | .write_to_file(&out_rust_file) | 
|  | .expect("Couldn't write bindings!"); | 
|  |  | 
|  | let observed_deps = | 
|  | std::fs::read_to_string(out_dep_file).expect("Couldn't read depfile!"); | 
|  | let expected_deps = format!( | 
|  | "{}: cpp/Test.h include/stub.h", | 
|  | out_rust_file_relative.display() | 
|  | ); | 
|  | assert_eq!( | 
|  | observed_deps, expected_deps, | 
|  | "including stub via include dir must produce correct dep path", | 
|  | ); | 
|  | } | 
|  |  | 
|  | fn setup_wrap_static_fns_test() { | 
|  | // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090 | 
|  | // set output directory under /target so it is easy to clean generated files | 
|  | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); | 
|  | let out_rust_file = out_path.join("extern.rs"); | 
|  |  | 
|  | let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/") | 
|  | .canonicalize() | 
|  | .expect("Cannot canonicalize libdir path"); | 
|  | let input_header_file_path = input_header_dir.join("wrap-static-fns.h"); | 
|  | let input_header_file_path_str = input_header_file_path | 
|  | .to_str() | 
|  | .expect("Path could not be converted to a str"); | 
|  |  | 
|  | // generate external bindings with the external .c and .h files | 
|  | #[allow(unused_mut)] | 
|  | let mut builder = Builder::default() | 
|  | .header(input_header_file_path_str) | 
|  | .parse_callbacks(Box::new( | 
|  | bindgen::CargoCallbacks::new().rerun_on_header_files(true), | 
|  | )) | 
|  | .parse_callbacks(Box::new(WrappedVaListCallback)) | 
|  | .wrap_static_fns(true) | 
|  | .wrap_static_fns_path( | 
|  | out_path.join("wrap_static_fns").display().to_string(), | 
|  | ) | 
|  | .clang_arg("-DUSE_VA_HEADER"); | 
|  |  | 
|  | // aarch64-linux has a bug, remove again when it is solved: | 
|  | // https://github.com/rust-lang/rust-bindgen/issues/3234 | 
|  | #[cfg(all(target_arch = "aarch64", target_os = "linux"))] | 
|  | { | 
|  | builder = builder.clang_arg("-DDISABLE_VA"); | 
|  | } | 
|  |  | 
|  | let bindings = builder.generate().expect("Unable to generate bindings"); | 
|  |  | 
|  | println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern | 
|  | println!("bindings generated: {bindings}"); | 
|  |  | 
|  | let obj_path = out_path.join("wrap_static_fns.o"); | 
|  | let lib_path = out_path.join("libwrap_static_fns.a"); | 
|  |  | 
|  | // build the external files to check if they work | 
|  | let mut command = std::process::Command::new("clang"); | 
|  | command | 
|  | .arg("-c") | 
|  | .arg("-o") | 
|  | .arg(&obj_path) | 
|  | .arg(out_path.join("wrap_static_fns.c")) | 
|  | .arg("-DUSE_VA_HEADER"); | 
|  |  | 
|  | // aarch64-linux has a bug, remove again when it is solved: | 
|  | // https://github.com/rust-lang/rust-bindgen/issues/3234 | 
|  | #[cfg(all(target_arch = "aarch64", target_os = "linux"))] | 
|  | { | 
|  | command.arg("-DDISABLE_VA"); | 
|  | } | 
|  |  | 
|  | let clang_output = command.output().expect("`clang` command error"); | 
|  | if !clang_output.status.success() { | 
|  | panic!( | 
|  | "Could not compile object file:\n{}", | 
|  | String::from_utf8_lossy(&clang_output.stderr) | 
|  | ); | 
|  | } | 
|  |  | 
|  | let ar_output = std::process::Command::new("ar") | 
|  | .arg("rcs") | 
|  | .arg(lib_path) | 
|  | .arg(obj_path) | 
|  | .output() | 
|  | .expect("`ar` command error"); | 
|  |  | 
|  | if !ar_output.status.success() { | 
|  | panic!( | 
|  | "Could not emit library file:\n{}", | 
|  | String::from_utf8_lossy(&ar_output.stderr) | 
|  | ); | 
|  | } | 
|  |  | 
|  | bindings | 
|  | .write_to_file(out_rust_file) | 
|  | .expect("Could not write bindings to the Rust file"); | 
|  | } | 
|  |  | 
|  | fn main() { | 
|  | setup_macro_test(); | 
|  | setup_wrap_static_fns_test(); | 
|  | } |