1 | //! Generate Rust bindings for C and C++ libraries. |
2 | //! |
3 | //! Provide a C/C++ header file, receive Rust FFI code to call into C/C++ |
4 | //! functions and use types defined in the header. |
5 | //! |
6 | //! See the [`Builder`](./struct.Builder.html) struct for usage. |
7 | //! |
8 | //! See the [Users Guide](https://rust-lang.github.io/rust-bindgen/) for |
9 | //! additional documentation. |
10 | #![deny (missing_docs)] |
11 | #![deny (unused_extern_crates)] |
12 | #![deny (clippy::disallowed_methods)] |
13 | // To avoid rather annoying warnings when matching with CXCursor_xxx as a |
14 | // constant. |
15 | #![allow (non_upper_case_globals)] |
16 | // `quote!` nests quite deeply. |
17 | #![recursion_limit = "128" ] |
18 | |
19 | #[macro_use ] |
20 | extern crate bitflags; |
21 | #[macro_use ] |
22 | extern crate lazy_static; |
23 | #[macro_use ] |
24 | extern crate quote; |
25 | |
26 | #[cfg (feature = "logging" )] |
27 | #[macro_use ] |
28 | extern crate log; |
29 | |
30 | #[cfg (not(feature = "logging" ))] |
31 | #[macro_use ] |
32 | mod log_stubs; |
33 | |
34 | #[macro_use ] |
35 | mod extra_assertions; |
36 | |
37 | mod codegen; |
38 | mod deps; |
39 | mod options; |
40 | mod time; |
41 | |
42 | pub mod callbacks; |
43 | |
44 | mod clang; |
45 | #[cfg (feature = "experimental" )] |
46 | mod diagnostics; |
47 | mod features; |
48 | mod ir; |
49 | mod parse; |
50 | mod regex_set; |
51 | |
52 | pub use codegen::{ |
53 | AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle, |
54 | }; |
55 | #[cfg (feature = "__cli" )] |
56 | pub use features::RUST_TARGET_STRINGS; |
57 | pub use features::{RustTarget, LATEST_STABLE_RUST}; |
58 | pub use ir::annotations::FieldVisibilityKind; |
59 | pub use ir::function::Abi; |
60 | pub use regex_set::RegexSet; |
61 | |
62 | use codegen::CodegenError; |
63 | use features::RustFeatures; |
64 | use ir::comment; |
65 | use ir::context::{BindgenContext, ItemId}; |
66 | use ir::item::Item; |
67 | use options::BindgenOptions; |
68 | use parse::ParseError; |
69 | |
70 | use std::borrow::Cow; |
71 | use std::collections::hash_map::Entry; |
72 | use std::env; |
73 | use std::ffi::OsStr; |
74 | use std::fs::{File, OpenOptions}; |
75 | use std::io::{self, Write}; |
76 | use std::path::{Path, PathBuf}; |
77 | use std::process::{Command, Stdio}; |
78 | use std::rc::Rc; |
79 | use std::str::FromStr; |
80 | |
81 | // Some convenient typedefs for a fast hash map and hash set. |
82 | type HashMap<K, V> = rustc_hash::FxHashMap<K, V>; |
83 | type HashSet<K> = rustc_hash::FxHashSet<K>; |
84 | |
85 | /// Default prefix for the anon fields. |
86 | pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_" ; |
87 | |
88 | const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern" ; |
89 | |
90 | fn file_is_cpp(name_file: &str) -> bool { |
91 | name_file.ends_with(".hpp" ) || |
92 | name_file.ends_with(".hxx" ) || |
93 | name_file.ends_with(".hh" ) || |
94 | name_file.ends_with(".h++" ) |
95 | } |
96 | |
97 | fn args_are_cpp(clang_args: &[Box<str>]) -> bool { |
98 | for w: &[Box] in clang_args.windows(size:2) { |
99 | if w[0].as_ref() == "-xc++" || w[1].as_ref() == "-xc++" { |
100 | return true; |
101 | } |
102 | if w[0].as_ref() == "-x" && w[1].as_ref() == "c++" { |
103 | return true; |
104 | } |
105 | if w[0].as_ref() == "-include" && file_is_cpp(name_file:w[1].as_ref()) { |
106 | return true; |
107 | } |
108 | } |
109 | false |
110 | } |
111 | |
112 | bitflags! { |
113 | /// A type used to indicate which kind of items we have to generate. |
114 | #[derive (Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
115 | pub struct CodegenConfig: u32 { |
116 | /// Whether to generate functions. |
117 | const FUNCTIONS = 1 << 0; |
118 | /// Whether to generate types. |
119 | const TYPES = 1 << 1; |
120 | /// Whether to generate constants. |
121 | const VARS = 1 << 2; |
122 | /// Whether to generate methods. |
123 | const METHODS = 1 << 3; |
124 | /// Whether to generate constructors |
125 | const CONSTRUCTORS = 1 << 4; |
126 | /// Whether to generate destructors. |
127 | const DESTRUCTORS = 1 << 5; |
128 | } |
129 | } |
130 | |
131 | impl CodegenConfig { |
132 | /// Returns true if functions should be generated. |
133 | pub fn functions(self) -> bool { |
134 | self.contains(CodegenConfig::FUNCTIONS) |
135 | } |
136 | |
137 | /// Returns true if types should be generated. |
138 | pub fn types(self) -> bool { |
139 | self.contains(CodegenConfig::TYPES) |
140 | } |
141 | |
142 | /// Returns true if constants should be generated. |
143 | pub fn vars(self) -> bool { |
144 | self.contains(CodegenConfig::VARS) |
145 | } |
146 | |
147 | /// Returns true if methds should be generated. |
148 | pub fn methods(self) -> bool { |
149 | self.contains(CodegenConfig::METHODS) |
150 | } |
151 | |
152 | /// Returns true if constructors should be generated. |
153 | pub fn constructors(self) -> bool { |
154 | self.contains(CodegenConfig::CONSTRUCTORS) |
155 | } |
156 | |
157 | /// Returns true if destructors should be generated. |
158 | pub fn destructors(self) -> bool { |
159 | self.contains(CodegenConfig::DESTRUCTORS) |
160 | } |
161 | } |
162 | |
163 | impl Default for CodegenConfig { |
164 | fn default() -> Self { |
165 | CodegenConfig::all() |
166 | } |
167 | } |
168 | |
169 | /// Formatting tools that can be used to format the bindings |
170 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
171 | #[non_exhaustive ] |
172 | pub enum Formatter { |
173 | /// Do not format the bindings. |
174 | None, |
175 | /// Use `rustfmt` to format the bindings. |
176 | Rustfmt, |
177 | #[cfg (feature = "prettyplease" )] |
178 | /// Use `prettyplease` to format the bindings. |
179 | Prettyplease, |
180 | } |
181 | |
182 | impl Default for Formatter { |
183 | fn default() -> Self { |
184 | Self::Rustfmt |
185 | } |
186 | } |
187 | |
188 | impl FromStr for Formatter { |
189 | type Err = String; |
190 | |
191 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
192 | match s { |
193 | "none" => Ok(Self::None), |
194 | "rustfmt" => Ok(Self::Rustfmt), |
195 | #[cfg (feature = "prettyplease" )] |
196 | "prettyplease" => Ok(Self::Prettyplease), |
197 | _ => Err(format!("` {}` is not a valid formatter" , s)), |
198 | } |
199 | } |
200 | } |
201 | |
202 | impl std::fmt::Display for Formatter { |
203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
204 | let s: &str = match self { |
205 | Self::None => "none" , |
206 | Self::Rustfmt => "rustfmt" , |
207 | #[cfg (feature = "prettyplease" )] |
208 | Self::Prettyplease => "prettyplease" , |
209 | }; |
210 | |
211 | s.fmt(f) |
212 | } |
213 | } |
214 | |
215 | /// Configure and generate Rust bindings for a C/C++ header. |
216 | /// |
217 | /// This is the main entry point to the library. |
218 | /// |
219 | /// ```ignore |
220 | /// use bindgen::builder; |
221 | /// |
222 | /// // Configure and generate bindings. |
223 | /// let bindings = builder().header("path/to/input/header" ) |
224 | /// .allowlist_type("SomeCoolClass" ) |
225 | /// .allowlist_function("do_some_cool_thing" ) |
226 | /// .generate()?; |
227 | /// |
228 | /// // Write the generated bindings to an output file. |
229 | /// bindings.write_to_file("path/to/output.rs" )?; |
230 | /// ``` |
231 | /// |
232 | /// # Enums |
233 | /// |
234 | /// Bindgen can map C/C++ enums into Rust in different ways. The way bindgen maps enums depends on |
235 | /// the pattern passed to several methods: |
236 | /// |
237 | /// 1. [`constified_enum_module()`](#method.constified_enum_module) |
238 | /// 2. [`bitfield_enum()`](#method.bitfield_enum) |
239 | /// 3. [`newtype_enum()`](#method.newtype_enum) |
240 | /// 4. [`rustified_enum()`](#method.rustified_enum) |
241 | /// |
242 | /// For each C enum, bindgen tries to match the pattern in the following order: |
243 | /// |
244 | /// 1. Constified enum module |
245 | /// 2. Bitfield enum |
246 | /// 3. Newtype enum |
247 | /// 4. Rustified enum |
248 | /// |
249 | /// If none of the above patterns match, then bindgen will generate a set of Rust constants. |
250 | /// |
251 | /// # Clang arguments |
252 | /// |
253 | /// Extra arguments can be passed to with clang: |
254 | /// 1. [`clang_arg()`](#method.clang_arg): takes a single argument |
255 | /// 2. [`clang_args()`](#method.clang_args): takes an iterator of arguments |
256 | /// 3. `BINDGEN_EXTRA_CLANG_ARGS` environment variable: whitespace separate |
257 | /// environment variable of arguments |
258 | /// |
259 | /// Clang arguments specific to your crate should be added via the |
260 | /// `clang_arg()`/`clang_args()` methods. |
261 | /// |
262 | /// End-users of the crate may need to set the `BINDGEN_EXTRA_CLANG_ARGS` environment variable to |
263 | /// add additional arguments. For example, to build against a different sysroot a user could set |
264 | /// `BINDGEN_EXTRA_CLANG_ARGS` to `--sysroot=/path/to/sysroot`. |
265 | /// |
266 | /// # Regular expression arguments |
267 | /// |
268 | /// Some [`Builder`] methods, such as `allowlist_*` and `blocklist_*`, allow regular |
269 | /// expressions as arguments. These regular expressions will be enclosed in parentheses and |
270 | /// anchored with `^` and `$`. So, if the argument passed is `<regex>`, the regular expression to be |
271 | /// stored will be `^(<regex>)$`. |
272 | /// |
273 | /// As a consequence, regular expressions passed to `bindgen` will try to match the whole name of |
274 | /// an item instead of a section of it, which means that to match any items with the prefix |
275 | /// `prefix`, the `prefix.*` regular expression must be used. |
276 | /// |
277 | /// Certain methods, like [`Builder::allowlist_function`], use regular expressions over function |
278 | /// names. To match C++ methods, prefix the name of the type where they belong, followed by an |
279 | /// underscore. So, if the type `Foo` has a method `bar`, it can be matched with the `Foo_bar` |
280 | /// regular expression. |
281 | /// |
282 | /// Additionally, Objective-C interfaces can be matched by prefixing the regular expression with |
283 | /// `I`. For example, the `IFoo` regular expression matches the `Foo` interface, and the `IFoo_foo` |
284 | /// regular expression matches the `foo` method of the `Foo` interface. |
285 | /// |
286 | /// Releases of `bindgen` with a version lesser or equal to `0.62.0` used to accept the wildcard |
287 | /// pattern `*` as a valid regular expression. This behavior has been deprecated, and the `.*` |
288 | /// regular expression must be used instead. |
289 | #[derive (Debug, Default, Clone)] |
290 | pub struct Builder { |
291 | options: BindgenOptions, |
292 | } |
293 | |
294 | /// Construct a new [`Builder`](./struct.Builder.html). |
295 | pub fn builder() -> Builder { |
296 | Default::default() |
297 | } |
298 | |
299 | fn get_extra_clang_args( |
300 | parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], |
301 | ) -> Vec<String> { |
302 | // Add any extra arguments from the environment to the clang command line. |
303 | let extra_clang_args: String = match get_target_dependent_env_var( |
304 | parse_callbacks, |
305 | var:"BINDGEN_EXTRA_CLANG_ARGS" , |
306 | ) { |
307 | None => return vec![], |
308 | Some(s: String) => s, |
309 | }; |
310 | |
311 | // Try to parse it with shell quoting. If we fail, make it one single big argument. |
312 | if let Some(strings: Vec) = shlex::split(&extra_clang_args) { |
313 | return strings; |
314 | } |
315 | vec![extra_clang_args] |
316 | } |
317 | |
318 | impl Builder { |
319 | /// Generate the Rust bindings using the options built up thus far. |
320 | pub fn generate(mut self) -> Result<Bindings, BindgenError> { |
321 | // Add any extra arguments from the environment to the clang command line. |
322 | self.options.clang_args.extend( |
323 | get_extra_clang_args(&self.options.parse_callbacks) |
324 | .into_iter() |
325 | .map(String::into_boxed_str), |
326 | ); |
327 | |
328 | for header in &self.options.input_headers { |
329 | self.options |
330 | .for_each_callback(|cb| cb.header_file(header.as_ref())); |
331 | } |
332 | |
333 | // Transform input headers to arguments on the clang command line. |
334 | self.options.clang_args.extend( |
335 | self.options.input_headers |
336 | [..self.options.input_headers.len().saturating_sub(1)] |
337 | .iter() |
338 | .flat_map(|header| ["-include" .into(), header.clone()]), |
339 | ); |
340 | |
341 | let input_unsaved_files = |
342 | std::mem::take(&mut self.options.input_header_contents) |
343 | .into_iter() |
344 | .map(|(name, contents)| { |
345 | clang::UnsavedFile::new(name.as_ref(), contents.as_ref()) |
346 | }) |
347 | .collect::<Vec<_>>(); |
348 | |
349 | Bindings::generate(self.options, input_unsaved_files) |
350 | } |
351 | |
352 | /// Preprocess and dump the input header files to disk. |
353 | /// |
354 | /// This is useful when debugging bindgen, using C-Reduce, or when filing |
355 | /// issues. The resulting file will be named something like `__bindgen.i` or |
356 | /// `__bindgen.ii` |
357 | pub fn dump_preprocessed_input(&self) -> io::Result<()> { |
358 | let clang = |
359 | clang_sys::support::Clang::find(None, &[]).ok_or_else(|| { |
360 | io::Error::new( |
361 | io::ErrorKind::Other, |
362 | "Cannot find clang executable" , |
363 | ) |
364 | })?; |
365 | |
366 | // The contents of a wrapper file that includes all the input header |
367 | // files. |
368 | let mut wrapper_contents = String::new(); |
369 | |
370 | // Whether we are working with C or C++ inputs. |
371 | let mut is_cpp = args_are_cpp(&self.options.clang_args); |
372 | |
373 | // For each input header, add `#include "$header"`. |
374 | for header in &self.options.input_headers { |
375 | is_cpp |= file_is_cpp(header); |
376 | |
377 | wrapper_contents.push_str("#include \"" ); |
378 | wrapper_contents.push_str(header); |
379 | wrapper_contents.push_str(" \"\n" ); |
380 | } |
381 | |
382 | // For each input header content, add a prefix line of `#line 0 "$name"` |
383 | // followed by the contents. |
384 | for (name, contents) in &self.options.input_header_contents { |
385 | is_cpp |= file_is_cpp(name); |
386 | |
387 | wrapper_contents.push_str("#line 0 \"" ); |
388 | wrapper_contents.push_str(name); |
389 | wrapper_contents.push_str(" \"\n" ); |
390 | wrapper_contents.push_str(contents); |
391 | } |
392 | |
393 | let wrapper_path = PathBuf::from(if is_cpp { |
394 | "__bindgen.cpp" |
395 | } else { |
396 | "__bindgen.c" |
397 | }); |
398 | |
399 | { |
400 | let mut wrapper_file = File::create(&wrapper_path)?; |
401 | wrapper_file.write_all(wrapper_contents.as_bytes())?; |
402 | } |
403 | |
404 | let mut cmd = Command::new(clang.path); |
405 | cmd.arg("-save-temps" ) |
406 | .arg("-E" ) |
407 | .arg("-C" ) |
408 | .arg("-c" ) |
409 | .arg(&wrapper_path) |
410 | .stdout(Stdio::piped()); |
411 | |
412 | for a in &self.options.clang_args { |
413 | cmd.arg(a.as_ref()); |
414 | } |
415 | |
416 | for a in get_extra_clang_args(&self.options.parse_callbacks) { |
417 | cmd.arg(a); |
418 | } |
419 | |
420 | let mut child = cmd.spawn()?; |
421 | |
422 | let mut preprocessed = child.stdout.take().unwrap(); |
423 | let mut file = File::create(if is_cpp { |
424 | "__bindgen.ii" |
425 | } else { |
426 | "__bindgen.i" |
427 | })?; |
428 | io::copy(&mut preprocessed, &mut file)?; |
429 | |
430 | if child.wait()?.success() { |
431 | Ok(()) |
432 | } else { |
433 | Err(io::Error::new( |
434 | io::ErrorKind::Other, |
435 | "clang exited with non-zero status" , |
436 | )) |
437 | } |
438 | } |
439 | } |
440 | |
441 | impl BindgenOptions { |
442 | fn build(&mut self) { |
443 | const REGEX_SETS_LEN: usize = 29; |
444 | |
445 | let regex_sets: [_; REGEX_SETS_LEN] = [ |
446 | &mut self.blocklisted_types, |
447 | &mut self.blocklisted_functions, |
448 | &mut self.blocklisted_items, |
449 | &mut self.blocklisted_files, |
450 | &mut self.blocklisted_vars, |
451 | &mut self.opaque_types, |
452 | &mut self.allowlisted_vars, |
453 | &mut self.allowlisted_types, |
454 | &mut self.allowlisted_functions, |
455 | &mut self.allowlisted_files, |
456 | &mut self.allowlisted_items, |
457 | &mut self.bitfield_enums, |
458 | &mut self.constified_enums, |
459 | &mut self.constified_enum_modules, |
460 | &mut self.newtype_enums, |
461 | &mut self.newtype_global_enums, |
462 | &mut self.rustified_enums, |
463 | &mut self.rustified_non_exhaustive_enums, |
464 | &mut self.type_alias, |
465 | &mut self.new_type_alias, |
466 | &mut self.new_type_alias_deref, |
467 | &mut self.bindgen_wrapper_union, |
468 | &mut self.manually_drop_union, |
469 | &mut self.no_partialeq_types, |
470 | &mut self.no_copy_types, |
471 | &mut self.no_debug_types, |
472 | &mut self.no_default_types, |
473 | &mut self.no_hash_types, |
474 | &mut self.must_use_types, |
475 | ]; |
476 | |
477 | let record_matches = self.record_matches; |
478 | #[cfg (feature = "experimental" )] |
479 | { |
480 | let sets_len = REGEX_SETS_LEN + self.abi_overrides.len(); |
481 | let names = if self.emit_diagnostics { |
482 | <[&str; REGEX_SETS_LEN]>::into_iter([ |
483 | "--blocklist-type" , |
484 | "--blocklist-function" , |
485 | "--blocklist-item" , |
486 | "--blocklist-file" , |
487 | "--blocklist-var" , |
488 | "--opaque-type" , |
489 | "--allowlist-type" , |
490 | "--allowlist-function" , |
491 | "--allowlist-var" , |
492 | "--allowlist-file" , |
493 | "--allowlist-item" , |
494 | "--bitfield-enum" , |
495 | "--newtype-enum" , |
496 | "--newtype-global-enum" , |
497 | "--rustified-enum" , |
498 | "--rustified-enum-non-exhaustive" , |
499 | "--constified-enum-module" , |
500 | "--constified-enum" , |
501 | "--type-alias" , |
502 | "--new-type-alias" , |
503 | "--new-type-alias-deref" , |
504 | "--bindgen-wrapper-union" , |
505 | "--manually-drop-union" , |
506 | "--no-partialeq" , |
507 | "--no-copy" , |
508 | "--no-debug" , |
509 | "--no-default" , |
510 | "--no-hash" , |
511 | "--must-use" , |
512 | ]) |
513 | .chain((0..self.abi_overrides.len()).map(|_| "--override-abi" )) |
514 | .map(Some) |
515 | .collect() |
516 | } else { |
517 | vec![None; sets_len] |
518 | }; |
519 | |
520 | for (regex_set, name) in |
521 | self.abi_overrides.values_mut().chain(regex_sets).zip(names) |
522 | { |
523 | regex_set.build_with_diagnostics(record_matches, name); |
524 | } |
525 | } |
526 | #[cfg (not(feature = "experimental" ))] |
527 | for regex_set in self.abi_overrides.values_mut().chain(regex_sets) { |
528 | regex_set.build(record_matches); |
529 | } |
530 | |
531 | let rust_target = self.rust_target; |
532 | #[allow (deprecated)] |
533 | if rust_target <= RustTarget::Stable_1_30 { |
534 | deprecated_target_diagnostic(rust_target, self); |
535 | } |
536 | |
537 | // Disable `untagged_union` if the target does not support it. |
538 | if !self.rust_features.untagged_union { |
539 | self.untagged_union = false; |
540 | } |
541 | } |
542 | |
543 | /// Update rust target version |
544 | pub fn set_rust_target(&mut self, rust_target: RustTarget) { |
545 | self.rust_target = rust_target; |
546 | |
547 | // Keep rust_features synced with rust_target |
548 | self.rust_features = rust_target.into(); |
549 | } |
550 | |
551 | /// Get features supported by target Rust version |
552 | pub fn rust_features(&self) -> RustFeatures { |
553 | self.rust_features |
554 | } |
555 | |
556 | fn last_callback<T>( |
557 | &self, |
558 | f: impl Fn(&dyn callbacks::ParseCallbacks) -> Option<T>, |
559 | ) -> Option<T> { |
560 | self.parse_callbacks |
561 | .iter() |
562 | .filter_map(|cb| f(cb.as_ref())) |
563 | .last() |
564 | } |
565 | |
566 | fn all_callbacks<T>( |
567 | &self, |
568 | f: impl Fn(&dyn callbacks::ParseCallbacks) -> Vec<T>, |
569 | ) -> Vec<T> { |
570 | self.parse_callbacks |
571 | .iter() |
572 | .flat_map(|cb| f(cb.as_ref())) |
573 | .collect() |
574 | } |
575 | |
576 | fn for_each_callback(&self, f: impl Fn(&dyn callbacks::ParseCallbacks)) { |
577 | self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); |
578 | } |
579 | |
580 | fn process_comment(&self, comment: &str) -> String { |
581 | let comment = comment::preprocess(comment); |
582 | self.parse_callbacks |
583 | .last() |
584 | .and_then(|cb| cb.process_comment(&comment)) |
585 | .unwrap_or(comment) |
586 | } |
587 | } |
588 | |
589 | fn deprecated_target_diagnostic(target: RustTarget, _options: &BindgenOptions) { |
590 | warn!("The {} Rust target is deprecated. If you have a need to use this target please report it at https://github.com/rust-lang/rust-bindgen/issues" , target); |
591 | |
592 | #[cfg (feature = "experimental" )] |
593 | if _options.emit_diagnostics { |
594 | use crate::diagnostics::{Diagnostic, Level}; |
595 | |
596 | let mut diagnostic = Diagnostic::default(); |
597 | diagnostic.with_title( |
598 | format!("The {} Rust target is deprecated." , target), |
599 | Level::Warn, |
600 | ); |
601 | diagnostic.add_annotation( |
602 | "This Rust target was passed to `--rust-target`" , |
603 | Level::Info, |
604 | ); |
605 | diagnostic.add_annotation("If you have a good reason to use this target please report it at https://github.com/rust-lang/rust-bindgen/issues" , Level::Help); |
606 | diagnostic.display(); |
607 | } |
608 | } |
609 | |
610 | #[cfg (feature = "runtime" )] |
611 | fn ensure_libclang_is_loaded() { |
612 | if clang_sys::is_loaded() { |
613 | return; |
614 | } |
615 | |
616 | // XXX (issue #350): Ensure that our dynamically loaded `libclang` |
617 | // doesn't get dropped prematurely, nor is loaded multiple times |
618 | // across different threads. |
619 | |
620 | lazy_static! { |
621 | static ref LIBCLANG: std::sync::Arc<clang_sys::SharedLibrary> = { |
622 | clang_sys::load().expect("Unable to find libclang" ); |
623 | clang_sys::get_library().expect( |
624 | "We just loaded libclang and it had better still be \ |
625 | here!" , |
626 | ) |
627 | }; |
628 | } |
629 | |
630 | clang_sys::set_library(Some(LIBCLANG.clone())); |
631 | } |
632 | |
633 | #[cfg (not(feature = "runtime" ))] |
634 | fn ensure_libclang_is_loaded() {} |
635 | |
636 | /// Error type for rust-bindgen. |
637 | #[derive (Debug, Clone, PartialEq, Eq, Hash)] |
638 | #[non_exhaustive ] |
639 | pub enum BindgenError { |
640 | /// The header was a folder. |
641 | FolderAsHeader(PathBuf), |
642 | /// Permissions to read the header is insufficient. |
643 | InsufficientPermissions(PathBuf), |
644 | /// The header does not exist. |
645 | NotExist(PathBuf), |
646 | /// Clang diagnosed an error. |
647 | ClangDiagnostic(String), |
648 | /// Code generation reported an error. |
649 | Codegen(CodegenError), |
650 | } |
651 | |
652 | impl std::fmt::Display for BindgenError { |
653 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
654 | match self { |
655 | BindgenError::FolderAsHeader(h: &PathBuf) => { |
656 | write!(f, "' {}' is a folder" , h.display()) |
657 | } |
658 | BindgenError::InsufficientPermissions(h: &PathBuf) => { |
659 | write!(f, "insufficient permissions to read ' {}'" , h.display()) |
660 | } |
661 | BindgenError::NotExist(h: &PathBuf) => { |
662 | write!(f, "header ' {}' does not exist." , h.display()) |
663 | } |
664 | BindgenError::ClangDiagnostic(message: &String) => { |
665 | write!(f, "clang diagnosed error: {}" , message) |
666 | } |
667 | BindgenError::Codegen(err: &CodegenError) => { |
668 | write!(f, "codegen error: {}" , err) |
669 | } |
670 | } |
671 | } |
672 | } |
673 | |
674 | impl std::error::Error for BindgenError {} |
675 | |
676 | /// Generated Rust bindings. |
677 | #[derive (Debug)] |
678 | pub struct Bindings { |
679 | options: BindgenOptions, |
680 | module: proc_macro2::TokenStream, |
681 | } |
682 | |
683 | pub(crate) const HOST_TARGET: &str = |
684 | include_str!(concat!(env!("OUT_DIR" ), "/host-target.txt" )); |
685 | |
686 | // Some architecture triplets are different between rust and libclang, see #1211 |
687 | // and duplicates. |
688 | fn rust_to_clang_target(rust_target: &str) -> Box<str> { |
689 | if rust_target.starts_with("aarch64-apple-" ) { |
690 | let mut clang_target = "arm64-apple-" .to_owned(); |
691 | clang_target |
692 | .push_str(rust_target.strip_prefix("aarch64-apple-" ).unwrap()); |
693 | return clang_target.into(); |
694 | } else if rust_target.starts_with("riscv64gc-" ) { |
695 | let mut clang_target = "riscv64-" .to_owned(); |
696 | clang_target.push_str(rust_target.strip_prefix("riscv64gc-" ).unwrap()); |
697 | return clang_target.into(); |
698 | } else if rust_target.ends_with("-espidf" ) { |
699 | let mut clang_target = |
700 | rust_target.strip_suffix("-espidf" ).unwrap().to_owned(); |
701 | clang_target.push_str("-elf" ); |
702 | if clang_target.starts_with("riscv32imc-" ) { |
703 | clang_target = "riscv32-" .to_owned() + |
704 | clang_target.strip_prefix("riscv32imc-" ).unwrap(); |
705 | } |
706 | return clang_target.into(); |
707 | } else if rust_target.starts_with("riscv32imc-" ) { |
708 | let mut clang_target = "riscv32-" .to_owned(); |
709 | clang_target.push_str(rust_target.strip_prefix("riscv32imc-" ).unwrap()); |
710 | return clang_target.into(); |
711 | } else if rust_target.starts_with("riscv32imac-" ) { |
712 | let mut clang_target = "riscv32-" .to_owned(); |
713 | clang_target |
714 | .push_str(rust_target.strip_prefix("riscv32imac-" ).unwrap()); |
715 | return clang_target.into(); |
716 | } |
717 | rust_target.into() |
718 | } |
719 | |
720 | /// Returns the effective target, and whether it was explicitly specified on the |
721 | /// clang flags. |
722 | fn find_effective_target(clang_args: &[Box<str>]) -> (Box<str>, bool) { |
723 | let mut args: Iter<'_, Box> = clang_args.iter(); |
724 | while let Some(opt: &Box) = args.next() { |
725 | if opt.starts_with("--target=" ) { |
726 | let mut split: Split<'_, char> = opt.split('=' ); |
727 | split.next(); |
728 | return (split.next().unwrap().into(), true); |
729 | } |
730 | |
731 | if opt.as_ref() == "-target" { |
732 | if let Some(target: &Box) = args.next() { |
733 | return (target.clone(), true); |
734 | } |
735 | } |
736 | } |
737 | |
738 | // If we're running from a build script, try to find the cargo target. |
739 | if let Ok(t: String) = env::var(key:"TARGET" ) { |
740 | return (rust_to_clang_target(&t), false); |
741 | } |
742 | |
743 | (rust_to_clang_target(HOST_TARGET), false) |
744 | } |
745 | |
746 | impl Bindings { |
747 | /// Generate bindings for the given options. |
748 | pub(crate) fn generate( |
749 | mut options: BindgenOptions, |
750 | input_unsaved_files: Vec<clang::UnsavedFile>, |
751 | ) -> Result<Bindings, BindgenError> { |
752 | ensure_libclang_is_loaded(); |
753 | |
754 | #[cfg (feature = "runtime" )] |
755 | debug!( |
756 | "Generating bindings, libclang at {}" , |
757 | clang_sys::get_library().unwrap().path().display() |
758 | ); |
759 | #[cfg (not(feature = "runtime" ))] |
760 | debug!("Generating bindings, libclang linked" ); |
761 | |
762 | options.build(); |
763 | |
764 | let (effective_target, explicit_target) = |
765 | find_effective_target(&options.clang_args); |
766 | |
767 | let is_host_build = |
768 | rust_to_clang_target(HOST_TARGET) == effective_target; |
769 | |
770 | // NOTE: The is_host_build check wouldn't be sound normally in some |
771 | // cases if we were to call a binary (if you have a 32-bit clang and are |
772 | // building on a 64-bit system for example). But since we rely on |
773 | // opening libclang.so, it has to be the same architecture and thus the |
774 | // check is fine. |
775 | if !explicit_target && !is_host_build { |
776 | options.clang_args.insert( |
777 | 0, |
778 | format!("--target= {}" , effective_target).into_boxed_str(), |
779 | ); |
780 | }; |
781 | |
782 | fn detect_include_paths(options: &mut BindgenOptions) { |
783 | if !options.detect_include_paths { |
784 | return; |
785 | } |
786 | |
787 | // Filter out include paths and similar stuff, so we don't incorrectly |
788 | // promote them to `-isystem`. |
789 | let clang_args_for_clang_sys = { |
790 | let mut last_was_include_prefix = false; |
791 | options |
792 | .clang_args |
793 | .iter() |
794 | .filter(|arg| { |
795 | if last_was_include_prefix { |
796 | last_was_include_prefix = false; |
797 | return false; |
798 | } |
799 | |
800 | let arg = arg.as_ref(); |
801 | |
802 | // https://clang.llvm.org/docs/ClangCommandLineReference.html |
803 | // -isystem and -isystem-after are harmless. |
804 | if arg == "-I" || arg == "--include-directory" { |
805 | last_was_include_prefix = true; |
806 | return false; |
807 | } |
808 | |
809 | if arg.starts_with("-I" ) || |
810 | arg.starts_with("--include-directory=" ) |
811 | { |
812 | return false; |
813 | } |
814 | |
815 | true |
816 | }) |
817 | .map(|arg| arg.clone().into()) |
818 | .collect::<Vec<_>>() |
819 | }; |
820 | |
821 | debug!( |
822 | "Trying to find clang with flags: {:?}" , |
823 | clang_args_for_clang_sys |
824 | ); |
825 | |
826 | let clang = match clang_sys::support::Clang::find( |
827 | None, |
828 | &clang_args_for_clang_sys, |
829 | ) { |
830 | None => return, |
831 | Some(clang) => clang, |
832 | }; |
833 | |
834 | debug!("Found clang: {:?}" , clang); |
835 | |
836 | // Whether we are working with C or C++ inputs. |
837 | let is_cpp = args_are_cpp(&options.clang_args) || |
838 | options.input_headers.iter().any(|h| file_is_cpp(h)); |
839 | |
840 | let search_paths = if is_cpp { |
841 | clang.cpp_search_paths |
842 | } else { |
843 | clang.c_search_paths |
844 | }; |
845 | |
846 | if let Some(search_paths) = search_paths { |
847 | for path in search_paths.into_iter() { |
848 | if let Ok(path) = path.into_os_string().into_string() { |
849 | options.clang_args.push("-isystem" .into()); |
850 | options.clang_args.push(path.into_boxed_str()); |
851 | } |
852 | } |
853 | } |
854 | } |
855 | |
856 | detect_include_paths(&mut options); |
857 | |
858 | #[cfg (unix)] |
859 | fn can_read(perms: &std::fs::Permissions) -> bool { |
860 | use std::os::unix::fs::PermissionsExt; |
861 | perms.mode() & 0o444 > 0 |
862 | } |
863 | |
864 | #[cfg (not(unix))] |
865 | fn can_read(_: &std::fs::Permissions) -> bool { |
866 | true |
867 | } |
868 | |
869 | if let Some(h) = options.input_headers.last() { |
870 | let path = Path::new(h.as_ref()); |
871 | if let Ok(md) = std::fs::metadata(path) { |
872 | if md.is_dir() { |
873 | return Err(BindgenError::FolderAsHeader(path.into())); |
874 | } |
875 | if !can_read(&md.permissions()) { |
876 | return Err(BindgenError::InsufficientPermissions( |
877 | path.into(), |
878 | )); |
879 | } |
880 | options.clang_args.push(h.clone()); |
881 | } else { |
882 | return Err(BindgenError::NotExist(path.into())); |
883 | } |
884 | } |
885 | |
886 | for (idx, f) in input_unsaved_files.iter().enumerate() { |
887 | if idx != 0 || !options.input_headers.is_empty() { |
888 | options.clang_args.push("-include" .into()); |
889 | } |
890 | options.clang_args.push(f.name.to_str().unwrap().into()) |
891 | } |
892 | |
893 | debug!("Fixed-up options: {:?}" , options); |
894 | |
895 | let time_phases = options.time_phases; |
896 | let mut context = BindgenContext::new(options, &input_unsaved_files); |
897 | |
898 | if is_host_build { |
899 | debug_assert_eq!( |
900 | context.target_pointer_size(), |
901 | std::mem::size_of::<*mut ()>(), |
902 | " {:?} {:?}" , |
903 | effective_target, |
904 | HOST_TARGET |
905 | ); |
906 | } |
907 | |
908 | { |
909 | let _t = time::Timer::new("parse" ).with_output(time_phases); |
910 | parse(&mut context)?; |
911 | } |
912 | |
913 | let (module, options) = |
914 | codegen::codegen(context).map_err(BindgenError::Codegen)?; |
915 | |
916 | Ok(Bindings { options, module }) |
917 | } |
918 | |
919 | /// Write these bindings as source text to a file. |
920 | pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { |
921 | let file = OpenOptions::new() |
922 | .write(true) |
923 | .truncate(true) |
924 | .create(true) |
925 | .open(path.as_ref())?; |
926 | self.write(Box::new(file))?; |
927 | Ok(()) |
928 | } |
929 | |
930 | /// Write these bindings as source text to the given `Write`able. |
931 | pub fn write<'a>(&self, mut writer: Box<dyn Write + 'a>) -> io::Result<()> { |
932 | const NL: &str = if cfg!(windows) { " \r\n" } else { " \n" }; |
933 | |
934 | if !self.options.disable_header_comment { |
935 | let version = |
936 | option_env!("CARGO_PKG_VERSION" ).unwrap_or("(unknown version)" ); |
937 | writeln!( |
938 | writer, |
939 | "/* automatically generated by rust-bindgen {version} */ {NL}" , |
940 | )?; |
941 | } |
942 | |
943 | for line in self.options.raw_lines.iter() { |
944 | writer.write_all(line.as_bytes())?; |
945 | writer.write_all(NL.as_bytes())?; |
946 | } |
947 | |
948 | if !self.options.raw_lines.is_empty() { |
949 | writer.write_all(NL.as_bytes())?; |
950 | } |
951 | |
952 | match self.format_tokens(&self.module) { |
953 | Ok(formatted_bindings) => { |
954 | writer.write_all(formatted_bindings.as_bytes())?; |
955 | } |
956 | Err(err) => { |
957 | eprintln!( |
958 | "Failed to run rustfmt: {} (non-fatal, continuing)" , |
959 | err |
960 | ); |
961 | writer.write_all(self.module.to_string().as_bytes())?; |
962 | } |
963 | } |
964 | Ok(()) |
965 | } |
966 | |
967 | /// Gets the rustfmt path to rustfmt the generated bindings. |
968 | fn rustfmt_path(&self) -> io::Result<Cow<PathBuf>> { |
969 | debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); |
970 | if let Some(ref p) = self.options.rustfmt_path { |
971 | return Ok(Cow::Borrowed(p)); |
972 | } |
973 | if let Ok(rustfmt) = env::var("RUSTFMT" ) { |
974 | return Ok(Cow::Owned(rustfmt.into())); |
975 | } |
976 | #[cfg (feature = "which-rustfmt" )] |
977 | match which::which("rustfmt" ) { |
978 | Ok(p) => Ok(Cow::Owned(p)), |
979 | Err(e) => { |
980 | Err(io::Error::new(io::ErrorKind::Other, format!(" {}" , e))) |
981 | } |
982 | } |
983 | #[cfg (not(feature = "which-rustfmt" ))] |
984 | // No rustfmt binary was specified, so assume that the binary is called |
985 | // "rustfmt" and that it is in the user's PATH. |
986 | Ok(Cow::Owned("rustfmt" .into())) |
987 | } |
988 | |
989 | /// Formats a token stream with the formatter set up in `BindgenOptions`. |
990 | fn format_tokens( |
991 | &self, |
992 | tokens: &proc_macro2::TokenStream, |
993 | ) -> io::Result<String> { |
994 | let _t = time::Timer::new("rustfmt_generated_string" ) |
995 | .with_output(self.options.time_phases); |
996 | |
997 | match self.options.formatter { |
998 | Formatter::None => return Ok(tokens.to_string()), |
999 | #[cfg (feature = "prettyplease" )] |
1000 | Formatter::Prettyplease => { |
1001 | return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens))); |
1002 | } |
1003 | Formatter::Rustfmt => (), |
1004 | } |
1005 | |
1006 | let rustfmt = self.rustfmt_path()?; |
1007 | let mut cmd = Command::new(&*rustfmt); |
1008 | |
1009 | cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); |
1010 | |
1011 | if let Some(path) = self |
1012 | .options |
1013 | .rustfmt_configuration_file |
1014 | .as_ref() |
1015 | .and_then(|f| f.to_str()) |
1016 | { |
1017 | cmd.args(["--config-path" , path]); |
1018 | } |
1019 | |
1020 | let mut child = cmd.spawn()?; |
1021 | let mut child_stdin = child.stdin.take().unwrap(); |
1022 | let mut child_stdout = child.stdout.take().unwrap(); |
1023 | |
1024 | let source = tokens.to_string(); |
1025 | |
1026 | // Write to stdin in a new thread, so that we can read from stdout on this |
1027 | // thread. This keeps the child from blocking on writing to its stdout which |
1028 | // might block us from writing to its stdin. |
1029 | let stdin_handle = ::std::thread::spawn(move || { |
1030 | let _ = child_stdin.write_all(source.as_bytes()); |
1031 | source |
1032 | }); |
1033 | |
1034 | let mut output = vec![]; |
1035 | io::copy(&mut child_stdout, &mut output)?; |
1036 | |
1037 | let status = child.wait()?; |
1038 | let source = stdin_handle.join().expect( |
1039 | "The thread writing to rustfmt's stdin doesn't do \ |
1040 | anything that could panic" , |
1041 | ); |
1042 | |
1043 | match String::from_utf8(output) { |
1044 | Ok(bindings) => match status.code() { |
1045 | Some(0) => Ok(bindings), |
1046 | Some(2) => Err(io::Error::new( |
1047 | io::ErrorKind::Other, |
1048 | "Rustfmt parsing errors." .to_string(), |
1049 | )), |
1050 | Some(3) => { |
1051 | rustfmt_non_fatal_error_diagnostic( |
1052 | "Rustfmt could not format some lines" , |
1053 | &self.options, |
1054 | ); |
1055 | Ok(bindings) |
1056 | } |
1057 | _ => Err(io::Error::new( |
1058 | io::ErrorKind::Other, |
1059 | "Internal rustfmt error" .to_string(), |
1060 | )), |
1061 | }, |
1062 | _ => Ok(source), |
1063 | } |
1064 | } |
1065 | } |
1066 | |
1067 | fn rustfmt_non_fatal_error_diagnostic(msg: &str, _options: &BindgenOptions) { |
1068 | warn!(" {}" , msg); |
1069 | |
1070 | #[cfg (feature = "experimental" )] |
1071 | if _options.emit_diagnostics { |
1072 | use crate::diagnostics::{Diagnostic, Level}; |
1073 | |
1074 | Diagnostic::default() |
1075 | .with_title(msg, Level::Warn) |
1076 | .add_annotation( |
1077 | "The bindings will be generated but not formatted." , |
1078 | Level::Note, |
1079 | ) |
1080 | .display(); |
1081 | } |
1082 | } |
1083 | |
1084 | impl std::fmt::Display for Bindings { |
1085 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
1086 | let mut bytes: Vec = vec![]; |
1087 | self.write(Box::new(&mut bytes) as Box<dyn Write>) |
1088 | .expect(msg:"writing to a vec cannot fail" ); |
1089 | f.write_str( |
1090 | data:std::str::from_utf8(&bytes) |
1091 | .expect(msg:"we should only write bindings that are valid utf-8" ), |
1092 | ) |
1093 | } |
1094 | } |
1095 | |
1096 | /// Determines whether the given cursor is in any of the files matched by the |
1097 | /// options. |
1098 | fn filter_builtins(ctx: &BindgenContext, cursor: &clang::Cursor) -> bool { |
1099 | ctx.options().builtins || !cursor.is_builtin() |
1100 | } |
1101 | |
1102 | /// Parse one `Item` from the Clang cursor. |
1103 | fn parse_one( |
1104 | ctx: &mut BindgenContext, |
1105 | cursor: clang::Cursor, |
1106 | parent: Option<ItemId>, |
1107 | ) { |
1108 | if !filter_builtins(ctx, &cursor) { |
1109 | return; |
1110 | } |
1111 | |
1112 | match Item::parse(cursor, parent_id:parent, ctx) { |
1113 | Ok(..) => {} |
1114 | Err(ParseError::Continue) => {} |
1115 | Err(ParseError::Recurse) => { |
1116 | cursor |
1117 | .visit_sorted(ctx, |ctx: &mut BindgenContext, child: Cursor| parse_one(ctx, cursor:child, parent)); |
1118 | } |
1119 | } |
1120 | } |
1121 | |
1122 | /// Parse the Clang AST into our `Item` internal representation. |
1123 | fn parse(context: &mut BindgenContext) -> Result<(), BindgenError> { |
1124 | use clang_sys::*; |
1125 | |
1126 | let mut error = None; |
1127 | for d in context.translation_unit().diags().iter() { |
1128 | let msg = d.format(); |
1129 | let is_err = d.severity() >= CXDiagnostic_Error; |
1130 | if is_err { |
1131 | let error = error.get_or_insert_with(String::new); |
1132 | error.push_str(&msg); |
1133 | error.push(' \n' ); |
1134 | } else { |
1135 | eprintln!("clang diag: {}" , msg); |
1136 | } |
1137 | } |
1138 | |
1139 | if let Some(message) = error { |
1140 | return Err(BindgenError::ClangDiagnostic(message)); |
1141 | } |
1142 | |
1143 | let cursor = context.translation_unit().cursor(); |
1144 | |
1145 | if context.options().emit_ast { |
1146 | fn dump_if_not_builtin(cur: &clang::Cursor) -> CXChildVisitResult { |
1147 | if !cur.is_builtin() { |
1148 | clang::ast_dump(cur, 0) |
1149 | } else { |
1150 | CXChildVisit_Continue |
1151 | } |
1152 | } |
1153 | cursor.visit(|cur| dump_if_not_builtin(&cur)); |
1154 | } |
1155 | |
1156 | let root = context.root_module(); |
1157 | context.with_module(root, |ctx| { |
1158 | cursor.visit_sorted(ctx, |ctx, child| parse_one(ctx, child, None)) |
1159 | }); |
1160 | |
1161 | assert!( |
1162 | context.current_module() == context.root_module(), |
1163 | "How did this happen?" |
1164 | ); |
1165 | Ok(()) |
1166 | } |
1167 | |
1168 | /// Extracted Clang version data |
1169 | #[derive (Debug)] |
1170 | pub struct ClangVersion { |
1171 | /// Major and minor semver, if parsing was successful |
1172 | pub parsed: Option<(u32, u32)>, |
1173 | /// full version string |
1174 | pub full: String, |
1175 | } |
1176 | |
1177 | /// Get the major and the minor semver numbers of Clang's version |
1178 | pub fn clang_version() -> ClangVersion { |
1179 | ensure_libclang_is_loaded(); |
1180 | |
1181 | //Debian clang version 11.0.1-2 |
1182 | let raw_v: String = clang::extract_clang_version(); |
1183 | let split_v: Option<Vec<&str>> = raw_v |
1184 | .split_whitespace() |
1185 | .find(|t| t.chars().next().map_or(false, |v| v.is_ascii_digit())) |
1186 | .map(|v| v.split('.' ).collect()); |
1187 | if let Some(v) = split_v { |
1188 | if v.len() >= 2 { |
1189 | let maybe_major = v[0].parse::<u32>(); |
1190 | let maybe_minor = v[1].parse::<u32>(); |
1191 | if let (Ok(major), Ok(minor)) = (maybe_major, maybe_minor) { |
1192 | return ClangVersion { |
1193 | parsed: Some((major, minor)), |
1194 | full: raw_v.clone(), |
1195 | }; |
1196 | } |
1197 | } |
1198 | }; |
1199 | ClangVersion { |
1200 | parsed: None, |
1201 | full: raw_v.clone(), |
1202 | } |
1203 | } |
1204 | |
1205 | fn env_var<K: AsRef<str> + AsRef<OsStr>>( |
1206 | parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], |
1207 | key: K, |
1208 | ) -> Result<String, std::env::VarError> { |
1209 | for callback: &Rc in parse_callbacks { |
1210 | callback.read_env_var(key.as_ref()); |
1211 | } |
1212 | std::env::var(key) |
1213 | } |
1214 | |
1215 | /// Looks for the env var `var_${TARGET}`, and falls back to just `var` when it is not found. |
1216 | fn get_target_dependent_env_var( |
1217 | parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], |
1218 | var: &str, |
1219 | ) -> Option<String> { |
1220 | if let Ok(target: String) = env_var(parse_callbacks, key:"TARGET" ) { |
1221 | if let Ok(v: String) = env_var(parse_callbacks, key:format!(" {}_ {}" , var, target)) { |
1222 | return Some(v); |
1223 | } |
1224 | if let Ok(v: String) = env_var( |
1225 | parse_callbacks, |
1226 | key:format!(" {}_ {}" , var, target.replace('-' , "_" )), |
1227 | ) { |
1228 | return Some(v); |
1229 | } |
1230 | } |
1231 | |
1232 | env_var(parse_callbacks, key:var).ok() |
1233 | } |
1234 | |
1235 | /// A ParseCallbacks implementation that will act on file includes by echoing a rerun-if-changed |
1236 | /// line and on env variable usage by echoing a rerun-if-env-changed line |
1237 | /// |
1238 | /// When running inside a `build.rs` script, this can be used to make cargo invalidate the |
1239 | /// generated bindings whenever any of the files included from the header change: |
1240 | /// ``` |
1241 | /// use bindgen::builder; |
1242 | /// let bindings = builder() |
1243 | /// .header("path/to/input/header" ) |
1244 | /// .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) |
1245 | /// .generate(); |
1246 | /// ``` |
1247 | #[derive (Debug)] |
1248 | pub struct CargoCallbacks { |
1249 | rerun_on_header_files: bool, |
1250 | } |
1251 | |
1252 | /// Create a new `CargoCallbacks` value with [`CargoCallbacks::rerun_on_header_files`] disabled. |
1253 | /// |
1254 | /// This constructor has been deprecated in favor of [`CargoCallbacks::new`] where |
1255 | /// [`CargoCallbacks::rerun_on_header_files`] is enabled by default. |
1256 | #[deprecated = "Use `CargoCallbacks::new()` instead. Please, check the documentation for further information." ] |
1257 | pub const CargoCallbacks: CargoCallbacks = CargoCallbacks { |
1258 | rerun_on_header_files: false, |
1259 | }; |
1260 | |
1261 | impl CargoCallbacks { |
1262 | /// Create a new `CargoCallbacks` value. |
1263 | pub fn new() -> Self { |
1264 | Self { |
1265 | rerun_on_header_files: true, |
1266 | } |
1267 | } |
1268 | |
1269 | /// Whether Cargo should re-run the build script if any of the input header files has changed. |
1270 | /// |
1271 | /// This option is enabled by default unless the deprecated [`const@CargoCallbacks`] |
1272 | /// constructor is used. |
1273 | pub fn rerun_on_header_files(mut self, doit: bool) -> Self { |
1274 | self.rerun_on_header_files = doit; |
1275 | self |
1276 | } |
1277 | } |
1278 | |
1279 | impl Default for CargoCallbacks { |
1280 | fn default() -> Self { |
1281 | Self::new() |
1282 | } |
1283 | } |
1284 | |
1285 | impl callbacks::ParseCallbacks for CargoCallbacks { |
1286 | fn header_file(&self, filename: &str) { |
1287 | if self.rerun_on_header_files { |
1288 | println!("cargo:rerun-if-changed= {}" , filename); |
1289 | } |
1290 | } |
1291 | |
1292 | fn include_file(&self, filename: &str) { |
1293 | println!("cargo:rerun-if-changed= {}" , filename); |
1294 | } |
1295 | |
1296 | fn read_env_var(&self, key: &str) { |
1297 | println!("cargo:rerun-if-env-changed= {}" , key); |
1298 | } |
1299 | } |
1300 | |
1301 | /// Test command_line_flag function. |
1302 | #[test ] |
1303 | fn commandline_flag_unit_test_function() { |
1304 | //Test 1 |
1305 | let bindings = crate::builder(); |
1306 | let command_line_flags = bindings.command_line_flags(); |
1307 | |
1308 | let test_cases = [ |
1309 | "--rust-target" , |
1310 | "--no-derive-default" , |
1311 | "--generate" , |
1312 | "functions,types,vars,methods,constructors,destructors" , |
1313 | ] |
1314 | .iter() |
1315 | .map(|&x| x.into()) |
1316 | .collect::<Vec<String>>(); |
1317 | |
1318 | assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); |
1319 | |
1320 | //Test 2 |
1321 | let bindings = crate::builder() |
1322 | .header("input_header" ) |
1323 | .allowlist_type("Distinct_Type" ) |
1324 | .allowlist_function("safe_function" ); |
1325 | |
1326 | let command_line_flags = bindings.command_line_flags(); |
1327 | let test_cases = [ |
1328 | "--rust-target" , |
1329 | "input_header" , |
1330 | "--no-derive-default" , |
1331 | "--generate" , |
1332 | "functions,types,vars,methods,constructors,destructors" , |
1333 | "--allowlist-type" , |
1334 | "Distinct_Type" , |
1335 | "--allowlist-function" , |
1336 | "safe_function" , |
1337 | ] |
1338 | .iter() |
1339 | .map(|&x| x.into()) |
1340 | .collect::<Vec<String>>(); |
1341 | println!(" {:?}" , command_line_flags); |
1342 | |
1343 | assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); |
1344 | } |
1345 | |
1346 | #[test ] |
1347 | fn test_rust_to_clang_target() { |
1348 | assert_eq!( |
1349 | rust_to_clang_target("aarch64-apple-ios" ).as_ref(), |
1350 | "arm64-apple-ios" |
1351 | ); |
1352 | } |
1353 | |
1354 | #[test ] |
1355 | fn test_rust_to_clang_target_riscv() { |
1356 | assert_eq!( |
1357 | rust_to_clang_target("riscv64gc-unknown-linux-gnu" ).as_ref(), |
1358 | "riscv64-unknown-linux-gnu" |
1359 | ); |
1360 | assert_eq!( |
1361 | rust_to_clang_target("riscv32imc-unknown-none-elf" ).as_ref(), |
1362 | "riscv32-unknown-none-elf" |
1363 | ); |
1364 | assert_eq!( |
1365 | rust_to_clang_target("riscv32imac-unknown-none-elf" ).as_ref(), |
1366 | "riscv32-unknown-none-elf" |
1367 | ); |
1368 | } |
1369 | |
1370 | #[test ] |
1371 | fn test_rust_to_clang_target_espidf() { |
1372 | assert_eq!( |
1373 | rust_to_clang_target("riscv32imc-esp-espidf" ).as_ref(), |
1374 | "riscv32-esp-elf" |
1375 | ); |
1376 | assert_eq!( |
1377 | rust_to_clang_target("xtensa-esp32-espidf" ).as_ref(), |
1378 | "xtensa-esp32-elf" |
1379 | ); |
1380 | } |
1381 | |