• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 extern crate bindgen;
2 extern crate cc;
3 
4 use bindgen::callbacks::{
5     DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
6 };
7 use bindgen::{Builder, CargoCallbacks, EnumVariation};
8 use std::collections::HashSet;
9 use std::env;
10 use std::path::PathBuf;
11 use std::sync::{Arc, Mutex, RwLock};
12 
13 #[derive(Debug)]
14 struct MacroCallback {
15     macros: Arc<RwLock<HashSet<String>>>,
16     seen_hellos: Mutex<u32>,
17     seen_funcs: Mutex<u32>,
18 }
19 
20 impl ParseCallbacks for MacroCallback {
will_parse_macro(&self, name: &str) -> MacroParsingBehavior21     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 
int_macro(&self, name: &str, _value: i64) -> Option<IntKind>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 
str_macro(&self, name: &str, value: &[u8])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 
func_macro(&self, name: &str, value: &[&[u8]])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 
item_name(&self, original_item_name: &str) -> Option<String>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.
add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String>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 
137 impl Drop for MacroCallback {
drop(&mut self)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 
setup_macro_test()152 fn 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 
setup_wrap_static_fns_test()208 fn 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 
main()275 fn main() {
276     setup_macro_test();
277     setup_wrap_static_fns_test();
278 }
279