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