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