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