1extern crate bindgen; 2extern crate cc; 3 4use bindgen::callbacks::{ 5 DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks, 6}; 7use bindgen::{Builder, CargoCallbacks, EnumVariation}; 8use std::collections::HashSet; 9use std::env; 10use std::path::PathBuf; 11use std::sync::{Arc, Mutex, RwLock}; 12 13#[derive(Debug)] 14struct MacroCallback { 15 macros: Arc<RwLock<HashSet<String>>>, 16 seen_hellos: Mutex<u32>, 17 seen_funcs: Mutex<u32>, 18} 19 20impl ParseCallbacks for MacroCallback { 21 fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior { 22 self.macros.write().unwrap().insert(name.into()); 23 24 if name == "MY_ANNOYING_MACRO" { 25 return MacroParsingBehavior::Ignore; 26 } 27 28 MacroParsingBehavior::Default 29 } 30 31 fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> { 32 match name { 33 "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom { 34 name: "crate::MacroInteger", 35 is_signed: true, 36 }), 37 38 _ => None, 39 } 40 } 41 42 fn str_macro(&self, name: &str, value: &[u8]) { 43 match name { 44 "TESTMACRO_STRING_EXPR" => { 45 assert_eq!(value, b"string"); 46 *self.seen_hellos.lock().unwrap() += 1; 47 } 48 "TESTMACRO_STRING_EXPANDED" | 49 "TESTMACRO_STRING" | 50 "TESTMACRO_INTEGER" => { 51 // The integer test macro is, actually, not expected to show up here at all -- but 52 // should produce an error if it does. 53 assert_eq!( 54 value, b"Hello Preprocessor!", 55 "str_macro handle received unexpected value" 56 ); 57 *self.seen_hellos.lock().unwrap() += 1; 58 } 59 _ => {} 60 } 61 } 62 63 fn func_macro(&self, name: &str, value: &[&[u8]]) { 64 match name { 65 "TESTMACRO_NONFUNCTIONAL" => { 66 panic!("func_macro was called for a non-functional macro"); 67 } 68 "TESTMACRO_FUNCTIONAL_NONEMPTY(TESTMACRO_INTEGER)" => { 69 // Spaces are inserted into the right-hand side of a functional 70 // macro during reconstruction from the tokenization. This might 71 // change in the future, but it is safe by the definition of a 72 // token in C, whereas leaving the spaces out could change 73 // tokenization. 74 assert_eq!(value, &[b"-" as &[u8], b"TESTMACRO_INTEGER"]); 75 *self.seen_funcs.lock().unwrap() += 1; 76 } 77 "TESTMACRO_FUNCTIONAL_EMPTY(TESTMACRO_INTEGER)" => { 78 assert_eq!(value, &[] as &[&[u8]]); 79 *self.seen_funcs.lock().unwrap() += 1; 80 } 81 "TESTMACRO_FUNCTIONAL_TOKENIZED(a,b,c,d,e)" => { 82 assert_eq!( 83 value, 84 &[b"a" as &[u8], b"/", b"b", b"c", b"d", b"##", b"e"] 85 ); 86 *self.seen_funcs.lock().unwrap() += 1; 87 } 88 "TESTMACRO_FUNCTIONAL_SPLIT(a,b)" => { 89 assert_eq!(value, &[b"b", b",", b"a"]); 90 *self.seen_funcs.lock().unwrap() += 1; 91 } 92 "TESTMACRO_STRING_FUNC_NON_UTF8(x)" => { 93 assert_eq!( 94 value, 95 &[b"(" as &[u8], b"x", b"\"\xff\xff\"", b")"] 96 ); 97 *self.seen_funcs.lock().unwrap() += 1; 98 } 99 _ => { 100 // The system might provide lots of functional macros. 101 // Ensure we did not miss handling one that we meant to handle. 102 assert!(!name.starts_with("TESTMACRO_"), "name = {}", name); 103 } 104 } 105 } 106 107 fn item_name(&self, original_item_name: &str) -> Option<String> { 108 if original_item_name.starts_with("my_prefixed_") { 109 Some( 110 original_item_name 111 .trim_start_matches("my_prefixed_") 112 .to_string(), 113 ) 114 } else if original_item_name.starts_with("MY_PREFIXED_") { 115 Some( 116 original_item_name 117 .trim_start_matches("MY_PREFIXED_") 118 .to_string(), 119 ) 120 } else { 121 None 122 } 123 } 124 125 // Test the "custom derives" capability by adding `PartialEq` to the `Test` struct. 126 fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> { 127 if info.name == "Test" { 128 vec!["PartialEq".into()] 129 } else if info.name == "MyOrderedEnum" { 130 vec!["std::cmp::PartialOrd".into()] 131 } else { 132 vec![] 133 } 134 } 135} 136 137impl Drop for MacroCallback { 138 fn drop(&mut self) { 139 assert_eq!( 140 *self.seen_hellos.lock().unwrap(), 141 3, 142 "str_macro handle was not called once for all relevant macros" 143 ); 144 assert_eq!( 145 *self.seen_funcs.lock().unwrap(), 146 5, 147 "func_macro handle was not called once for all relevant macros" 148 ); 149 } 150} 151 152fn setup_macro_test() { 153 cc::Build::new() 154 .cpp(true) 155 .file("cpp/Test.cc") 156 .include("include") 157 .compile("libtest.a"); 158 159 let macros = Arc::new(RwLock::new(HashSet::new())); 160 161 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 162 let out_rust_file = out_path.join("test.rs"); 163 let out_rust_file_relative = out_rust_file 164 .strip_prefix(std::env::current_dir().unwrap().parent().unwrap()) 165 .unwrap(); 166 let out_dep_file = out_path.join("test.d"); 167 168 let bindings = Builder::default() 169 .rustfmt_bindings(false) 170 .enable_cxx_namespaces() 171 .default_enum_style(EnumVariation::Rust { 172 non_exhaustive: false, 173 }) 174 .raw_line("pub use self::root::*;") 175 .raw_line("extern { fn my_prefixed_function_to_remove(i: i32); }") 176 .module_raw_line("root::testing", "pub type Bar = i32;") 177 .header("cpp/Test.h") 178 .clang_args(&["-x", "c++", "-std=c++11", "-I", "include"]) 179 .parse_callbacks(Box::new(MacroCallback { 180 macros: macros.clone(), 181 seen_hellos: Mutex::new(0), 182 seen_funcs: Mutex::new(0), 183 })) 184 .blocklist_function("my_prefixed_function_to_remove") 185 .constified_enum("my_prefixed_enum_to_be_constified") 186 .opaque_type("my_prefixed_templated_foo<my_prefixed_baz>") 187 .depfile(out_rust_file_relative.display().to_string(), &out_dep_file) 188 .generate() 189 .expect("Unable to generate bindings"); 190 191 assert!(macros.read().unwrap().contains("TESTMACRO")); 192 bindings 193 .write_to_file(&out_rust_file) 194 .expect("Couldn't write bindings!"); 195 196 let observed_deps = 197 std::fs::read_to_string(out_dep_file).expect("Couldn't read depfile!"); 198 let expected_deps = format!( 199 "{}: cpp/Test.h include/stub.h", 200 out_rust_file_relative.display() 201 ); 202 assert_eq!( 203 observed_deps, expected_deps, 204 "including stub via include dir must produce correct dep path", 205 ); 206} 207 208fn setup_wrap_static_fns_test() { 209 // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090 210 // set output directory under /target so it is easy to clean generated files 211 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 212 let out_rust_file = out_path.join("extern.rs"); 213 214 let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/") 215 .canonicalize() 216 .expect("Cannot canonicalize libdir path"); 217 let input_header_file_path = input_header_dir.join("wrap-static-fns.h"); 218 let input_header_file_path_str = input_header_file_path 219 .to_str() 220 .expect("Path could not be converted to a str"); 221 222 // generate external bindings with the external .c and .h files 223 let bindings = Builder::default() 224 .header(input_header_file_path_str) 225 .parse_callbacks(Box::new(CargoCallbacks)) 226 .wrap_static_fns(true) 227 .wrap_static_fns_path( 228 out_path.join("wrap_static_fns").display().to_string(), 229 ) 230 .generate() 231 .expect("Unable to generate bindings"); 232 233 println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern 234 println!("bindings generated: {}", bindings); 235 236 let obj_path = out_path.join("wrap_static_fns.o"); 237 let lib_path = out_path.join("libwrap_static_fns.a"); 238 239 // build the external files to check if they work 240 let clang_output = std::process::Command::new("clang") 241 .arg("-c") 242 .arg("-o") 243 .arg(&obj_path) 244 .arg(out_path.join("wrap_static_fns.c")) 245 .arg("-include") 246 .arg(input_header_file_path) 247 .output() 248 .expect("`clang` command error"); 249 if !clang_output.status.success() { 250 panic!( 251 "Could not compile object file:\n{}", 252 String::from_utf8_lossy(&clang_output.stderr) 253 ); 254 } 255 256 let ar_output = std::process::Command::new("ar") 257 .arg("rcs") 258 .arg(lib_path) 259 .arg(obj_path) 260 .output() 261 .expect("`ar` command error"); 262 263 if !ar_output.status.success() { 264 panic!( 265 "Could not emit library file:\n{}", 266 String::from_utf8_lossy(&ar_output.stderr) 267 ); 268 } 269 270 bindings 271 .write_to_file(out_rust_file) 272 .expect("Cound not write bindings to the Rust file"); 273} 274 275fn main() { 276 setup_macro_test(); 277 setup_wrap_static_fns_test(); 278} 279