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