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 quote; |
23 | |
24 | #[cfg (feature = "logging" )] |
25 | #[macro_use ] |
26 | extern crate log; |
27 | |
28 | #[cfg (not(feature = "logging" ))] |
29 | #[macro_use ] |
30 | mod log_stubs; |
31 | |
32 | #[macro_use ] |
33 | mod extra_assertions; |
34 | |
35 | mod codegen; |
36 | mod deps; |
37 | mod options; |
38 | mod time; |
39 | |
40 | pub mod callbacks; |
41 | |
42 | mod clang; |
43 | #[cfg (feature = "experimental" )] |
44 | mod diagnostics; |
45 | mod features; |
46 | mod ir; |
47 | mod parse; |
48 | mod regex_set; |
49 | |
50 | pub use codegen::{ |
51 | AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle, |
52 | }; |
53 | #[cfg (feature = "__cli" )] |
54 | pub use features::RUST_TARGET_STRINGS; |
55 | pub use features::{RustTarget, LATEST_STABLE_RUST}; |
56 | pub use ir::annotations::FieldVisibilityKind; |
57 | pub use ir::function::Abi; |
58 | pub use regex_set::RegexSet; |
59 | |
60 | use codegen::CodegenError; |
61 | use features::RustFeatures; |
62 | use ir::comment; |
63 | use ir::context::{BindgenContext, ItemId}; |
64 | use ir::item::Item; |
65 | use options::BindgenOptions; |
66 | use parse::ParseError; |
67 | |
68 | use std::borrow::Cow; |
69 | use std::collections::hash_map::Entry; |
70 | use std::env; |
71 | use std::ffi::OsStr; |
72 | use std::fs::{File, OpenOptions}; |
73 | use std::io::{self, Write}; |
74 | use std::path::{Path, PathBuf}; |
75 | use std::process::{Command, Stdio}; |
76 | use std::rc::Rc; |
77 | use std::str::FromStr; |
78 | use std::sync::{Arc, OnceLock}; |
79 | |
80 | // Some convenient typedefs for a fast hash map and hash set. |
81 | type HashMap<K, V> = rustc_hash::FxHashMap<K, V>; |
82 | type HashSet<K> = rustc_hash::FxHashSet<K>; |
83 | |
84 | /// Default prefix for the anon fields. |
85 | pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_" ; |
86 | |
87 | const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern" ; |
88 | |
89 | fn file_is_cpp(name_file: &str) -> bool { |
90 | name_file.ends_with(".hpp" ) || |
91 | name_file.ends_with(".hxx" ) || |
92 | name_file.ends_with(".hh" ) || |
93 | name_file.ends_with(".h++" ) |
94 | } |
95 | |
96 | fn args_are_cpp(clang_args: &[Box<str>]) -> bool { |
97 | for w: &[Box] in clang_args.windows(size:2) { |
98 | if w[0].as_ref() == "-xc++" || w[1].as_ref() == "-xc++" { |
99 | return true; |
100 | } |
101 | if w[0].as_ref() == "-x" && w[1].as_ref() == "c++" { |
102 | return true; |
103 | } |
104 | if w[0].as_ref() == "-include" && file_is_cpp(name_file:w[1].as_ref()) { |
105 | return true; |
106 | } |
107 | } |
108 | false |
109 | } |
110 | |
111 | bitflags! { |
112 | /// A type used to indicate which kind of items we have to generate. |
113 | #[derive (Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
114 | pub struct CodegenConfig: u32 { |
115 | /// Whether to generate functions. |
116 | const FUNCTIONS = 1 << 0; |
117 | /// Whether to generate types. |
118 | const TYPES = 1 << 1; |
119 | /// Whether to generate constants. |
120 | const VARS = 1 << 2; |
121 | /// Whether to generate methods. |
122 | const METHODS = 1 << 3; |
123 | /// Whether to generate constructors |
124 | const CONSTRUCTORS = 1 << 4; |
125 | /// Whether to generate destructors. |
126 | const DESTRUCTORS = 1 << 5; |
127 | } |
128 | } |
129 | |
130 | impl CodegenConfig { |
131 | /// Returns true if functions should be generated. |
132 | pub fn functions(self) -> bool { |
133 | self.contains(CodegenConfig::FUNCTIONS) |
134 | } |
135 | |
136 | /// Returns true if types should be generated. |
137 | pub fn types(self) -> bool { |
138 | self.contains(CodegenConfig::TYPES) |
139 | } |
140 | |
141 | /// Returns true if constants should be generated. |
142 | pub fn vars(self) -> bool { |
143 | self.contains(CodegenConfig::VARS) |
144 | } |
145 | |
146 | /// Returns true if methods should be generated. |
147 | pub fn methods(self) -> bool { |
148 | self.contains(CodegenConfig::METHODS) |
149 | } |
150 | |
151 | /// Returns true if constructors should be generated. |
152 | pub fn constructors(self) -> bool { |
153 | self.contains(CodegenConfig::CONSTRUCTORS) |
154 | } |
155 | |
156 | /// Returns true if destructors should be generated. |
157 | pub fn destructors(self) -> bool { |
158 | self.contains(CodegenConfig::DESTRUCTORS) |
159 | } |
160 | } |
161 | |
162 | impl Default for CodegenConfig { |
163 | fn default() -> Self { |
164 | CodegenConfig::all() |
165 | } |
166 | } |
167 | |
168 | /// Formatting tools that can be used to format the bindings |
169 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
170 | #[non_exhaustive ] |
171 | pub enum Formatter { |
172 | /// Do not format the bindings. |
173 | None, |
174 | /// Use `rustfmt` to format the bindings. |
175 | Rustfmt, |
176 | #[cfg (feature = "prettyplease" )] |
177 | /// Use `prettyplease` to format the bindings. |
178 | Prettyplease, |
179 | } |
180 | |
181 | impl Default for Formatter { |
182 | fn default() -> Self { |
183 | Self::Rustfmt |
184 | } |
185 | } |
186 | |
187 | impl FromStr for Formatter { |
188 | type Err = String; |
189 | |
190 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
191 | match s { |
192 | "none" => Ok(Self::None), |
193 | "rustfmt" => Ok(Self::Rustfmt), |
194 | #[cfg (feature = "prettyplease" )] |
195 | "prettyplease" => Ok(Self::Prettyplease), |
196 | _ => Err(format!("` {}` is not a valid formatter" , s)), |
197 | } |
198 | } |
199 | } |
200 | |
201 | impl std::fmt::Display for Formatter { |
202 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
203 | let s: &'static str = match self { |
204 | Self::None => "none" , |
205 | Self::Rustfmt => "rustfmt" , |
206 | #[cfg (feature = "prettyplease" )] |
207 | Self::Prettyplease => "prettyplease" , |
208 | }; |
209 | |
210 | s.fmt(f) |
211 | } |
212 | } |
213 | |
214 | /// Configure and generate Rust bindings for a C/C++ header. |
215 | /// |
216 | /// This is the main entry point to the library. |
217 | /// |
218 | /// ```ignore |
219 | /// use bindgen::builder; |
220 | /// |
221 | /// // Configure and generate bindings. |
222 | /// let bindings = builder().header("path/to/input/header" ) |
223 | /// .allowlist_type("SomeCoolClass" ) |
224 | /// .allowlist_function("do_some_cool_thing" ) |
225 | /// .generate()?; |
226 | /// |
227 | /// // Write the generated bindings to an output file. |
228 | /// bindings.write_to_file("path/to/output.rs" )?; |
229 | /// ``` |
230 | /// |
231 | /// # Enums |
232 | /// |
233 | /// Bindgen can map C/C++ enums into Rust in different ways. The way bindgen maps enums depends on |
234 | /// the pattern passed to several methods: |
235 | /// |
236 | /// 1. [`constified_enum_module()`](#method.constified_enum_module) |
237 | /// 2. [`bitfield_enum()`](#method.bitfield_enum) |
238 | /// 3. [`newtype_enum()`](#method.newtype_enum) |
239 | /// 4. [`rustified_enum()`](#method.rustified_enum) |
240 | /// 5. [`rustified_non_exhaustive_enum()`](#method.rustified_non_exhaustive_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 | static LIBCLANG: OnceLock<Arc<clang_sys::SharedLibrary>> = OnceLock::new(); |
621 | let libclang: &Arc = LIBCLANG.get_or_init(|| { |
622 | clang_sys::load().expect(msg:"Unable to find libclang" ); |
623 | clang_sys::get_library() |
624 | .expect(msg:"We just loaded libclang and it had better still be here!" ) |
625 | }); |
626 | |
627 | clang_sys::set_library(Some(libclang.clone())); |
628 | } |
629 | |
630 | #[cfg (not(feature = "runtime" ))] |
631 | fn ensure_libclang_is_loaded() {} |
632 | |
633 | /// Error type for rust-bindgen. |
634 | #[derive (Debug, Clone, PartialEq, Eq, Hash)] |
635 | #[non_exhaustive ] |
636 | pub enum BindgenError { |
637 | /// The header was a folder. |
638 | FolderAsHeader(PathBuf), |
639 | /// Permissions to read the header is insufficient. |
640 | InsufficientPermissions(PathBuf), |
641 | /// The header does not exist. |
642 | NotExist(PathBuf), |
643 | /// Clang diagnosed an error. |
644 | ClangDiagnostic(String), |
645 | /// Code generation reported an error. |
646 | Codegen(CodegenError), |
647 | } |
648 | |
649 | impl std::fmt::Display for BindgenError { |
650 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
651 | match self { |
652 | BindgenError::FolderAsHeader(h: &PathBuf) => { |
653 | write!(f, "' {}' is a folder" , h.display()) |
654 | } |
655 | BindgenError::InsufficientPermissions(h: &PathBuf) => { |
656 | write!(f, "insufficient permissions to read ' {}'" , h.display()) |
657 | } |
658 | BindgenError::NotExist(h: &PathBuf) => { |
659 | write!(f, "header ' {}' does not exist." , h.display()) |
660 | } |
661 | BindgenError::ClangDiagnostic(message: &String) => { |
662 | write!(f, "clang diagnosed error: {}" , message) |
663 | } |
664 | BindgenError::Codegen(err: &CodegenError) => { |
665 | write!(f, "codegen error: {}" , err) |
666 | } |
667 | } |
668 | } |
669 | } |
670 | |
671 | impl std::error::Error for BindgenError {} |
672 | |
673 | /// Generated Rust bindings. |
674 | #[derive (Debug)] |
675 | pub struct Bindings { |
676 | options: BindgenOptions, |
677 | module: proc_macro2::TokenStream, |
678 | } |
679 | |
680 | pub(crate) const HOST_TARGET: &str = |
681 | include_str!(concat!(env!("OUT_DIR" ) , "/host-target.txt" )); |
682 | |
683 | // Some architecture triplets are different between rust and libclang, see #1211 |
684 | // and duplicates. |
685 | fn rust_to_clang_target(rust_target: &str) -> Box<str> { |
686 | const TRIPLE_HYPHENS_MESSAGE: &str = "Target triple should contain hyphens" ; |
687 | |
688 | let mut clang_target = rust_target.to_owned(); |
689 | |
690 | if clang_target.starts_with("riscv32" ) { |
691 | let idx = clang_target.find('-' ).expect(TRIPLE_HYPHENS_MESSAGE); |
692 | |
693 | clang_target.replace_range(..idx, "riscv32" ); |
694 | } else if clang_target.starts_with("riscv64" ) { |
695 | let idx = clang_target.find('-' ).expect(TRIPLE_HYPHENS_MESSAGE); |
696 | |
697 | clang_target.replace_range(..idx, "riscv64" ); |
698 | } else if clang_target.starts_with("aarch64-apple-" ) { |
699 | let idx = clang_target.find('-' ).expect(TRIPLE_HYPHENS_MESSAGE); |
700 | |
701 | clang_target.replace_range(..idx, "arm64" ); |
702 | } |
703 | |
704 | if clang_target.ends_with("-espidf" ) { |
705 | let idx = clang_target.rfind('-' ).expect(TRIPLE_HYPHENS_MESSAGE); |
706 | |
707 | clang_target.replace_range((idx + 1).., "elf" ); |
708 | } |
709 | |
710 | clang_target.into() |
711 | } |
712 | |
713 | /// Returns the effective target, and whether it was explicitly specified on the |
714 | /// clang flags. |
715 | fn find_effective_target(clang_args: &[Box<str>]) -> (Box<str>, bool) { |
716 | let mut args: Iter<'_, Box> = clang_args.iter(); |
717 | while let Some(opt: &Box) = args.next() { |
718 | if opt.starts_with("--target=" ) { |
719 | let mut split: Split<'_, char> = opt.split('=' ); |
720 | split.next(); |
721 | return (split.next().unwrap().into(), true); |
722 | } |
723 | |
724 | if opt.as_ref() == "-target" { |
725 | if let Some(target: &Box) = args.next() { |
726 | return (target.clone(), true); |
727 | } |
728 | } |
729 | } |
730 | |
731 | // If we're running from a build script, try to find the cargo target. |
732 | if let Ok(t: String) = env::var(key:"TARGET" ) { |
733 | return (rust_to_clang_target(&t), false); |
734 | } |
735 | |
736 | (rust_to_clang_target(HOST_TARGET), false) |
737 | } |
738 | |
739 | impl Bindings { |
740 | /// Generate bindings for the given options. |
741 | pub(crate) fn generate( |
742 | mut options: BindgenOptions, |
743 | input_unsaved_files: Vec<clang::UnsavedFile>, |
744 | ) -> Result<Bindings, BindgenError> { |
745 | ensure_libclang_is_loaded(); |
746 | |
747 | #[cfg (feature = "runtime" )] |
748 | debug!( |
749 | "Generating bindings, libclang at {}" , |
750 | clang_sys::get_library().unwrap().path().display() |
751 | ); |
752 | #[cfg (not(feature = "runtime" ))] |
753 | debug!("Generating bindings, libclang linked" ); |
754 | |
755 | options.build(); |
756 | |
757 | let (effective_target, explicit_target) = |
758 | find_effective_target(&options.clang_args); |
759 | |
760 | let is_host_build = |
761 | rust_to_clang_target(HOST_TARGET) == effective_target; |
762 | |
763 | // NOTE: The is_host_build check wouldn't be sound normally in some |
764 | // cases if we were to call a binary (if you have a 32-bit clang and are |
765 | // building on a 64-bit system for example). But since we rely on |
766 | // opening libclang.so, it has to be the same architecture and thus the |
767 | // check is fine. |
768 | if !explicit_target && !is_host_build { |
769 | options.clang_args.insert( |
770 | 0, |
771 | format!("--target= {}" , effective_target).into_boxed_str(), |
772 | ); |
773 | }; |
774 | |
775 | fn detect_include_paths(options: &mut BindgenOptions) { |
776 | if !options.detect_include_paths { |
777 | return; |
778 | } |
779 | |
780 | // Filter out include paths and similar stuff, so we don't incorrectly |
781 | // promote them to `-isystem`. |
782 | let clang_args_for_clang_sys = { |
783 | let mut last_was_include_prefix = false; |
784 | options |
785 | .clang_args |
786 | .iter() |
787 | .filter(|arg| { |
788 | if last_was_include_prefix { |
789 | last_was_include_prefix = false; |
790 | return false; |
791 | } |
792 | |
793 | let arg = arg.as_ref(); |
794 | |
795 | // https://clang.llvm.org/docs/ClangCommandLineReference.html |
796 | // -isystem and -isystem-after are harmless. |
797 | if arg == "-I" || arg == "--include-directory" { |
798 | last_was_include_prefix = true; |
799 | return false; |
800 | } |
801 | |
802 | if arg.starts_with("-I" ) || |
803 | arg.starts_with("--include-directory=" ) |
804 | { |
805 | return false; |
806 | } |
807 | |
808 | true |
809 | }) |
810 | .map(|arg| arg.clone().into()) |
811 | .collect::<Vec<_>>() |
812 | }; |
813 | |
814 | debug!( |
815 | "Trying to find clang with flags: {:?}" , |
816 | clang_args_for_clang_sys |
817 | ); |
818 | |
819 | let clang = match clang_sys::support::Clang::find( |
820 | None, |
821 | &clang_args_for_clang_sys, |
822 | ) { |
823 | None => return, |
824 | Some(clang) => clang, |
825 | }; |
826 | |
827 | debug!("Found clang: {:?}" , clang); |
828 | |
829 | // Whether we are working with C or C++ inputs. |
830 | let is_cpp = args_are_cpp(&options.clang_args) || |
831 | options.input_headers.iter().any(|h| file_is_cpp(h)); |
832 | |
833 | let search_paths = if is_cpp { |
834 | clang.cpp_search_paths |
835 | } else { |
836 | clang.c_search_paths |
837 | }; |
838 | |
839 | if let Some(search_paths) = search_paths { |
840 | for path in search_paths.into_iter() { |
841 | if let Ok(path) = path.into_os_string().into_string() { |
842 | options.clang_args.push("-isystem" .into()); |
843 | options.clang_args.push(path.into_boxed_str()); |
844 | } |
845 | } |
846 | } |
847 | } |
848 | |
849 | detect_include_paths(&mut options); |
850 | |
851 | #[cfg (unix)] |
852 | fn can_read(perms: &std::fs::Permissions) -> bool { |
853 | use std::os::unix::fs::PermissionsExt; |
854 | perms.mode() & 0o444 > 0 |
855 | } |
856 | |
857 | #[cfg (not(unix))] |
858 | fn can_read(_: &std::fs::Permissions) -> bool { |
859 | true |
860 | } |
861 | |
862 | if let Some(h) = options.input_headers.last() { |
863 | let path = Path::new(h.as_ref()); |
864 | if let Ok(md) = std::fs::metadata(path) { |
865 | if md.is_dir() { |
866 | return Err(BindgenError::FolderAsHeader(path.into())); |
867 | } |
868 | if !can_read(&md.permissions()) { |
869 | return Err(BindgenError::InsufficientPermissions( |
870 | path.into(), |
871 | )); |
872 | } |
873 | options.clang_args.push(h.clone()); |
874 | } else { |
875 | return Err(BindgenError::NotExist(path.into())); |
876 | } |
877 | } |
878 | |
879 | for (idx, f) in input_unsaved_files.iter().enumerate() { |
880 | if idx != 0 || !options.input_headers.is_empty() { |
881 | options.clang_args.push("-include" .into()); |
882 | } |
883 | options.clang_args.push(f.name.to_str().unwrap().into()) |
884 | } |
885 | |
886 | debug!("Fixed-up options: {:?}" , options); |
887 | |
888 | let time_phases = options.time_phases; |
889 | let mut context = BindgenContext::new(options, &input_unsaved_files); |
890 | |
891 | if is_host_build { |
892 | debug_assert_eq!( |
893 | context.target_pointer_size(), |
894 | std::mem::size_of::<*mut ()>(), |
895 | " {:?} {:?}" , |
896 | effective_target, |
897 | HOST_TARGET |
898 | ); |
899 | } |
900 | |
901 | { |
902 | let _t = time::Timer::new("parse" ).with_output(time_phases); |
903 | parse(&mut context)?; |
904 | } |
905 | |
906 | let (module, options) = |
907 | codegen::codegen(context).map_err(BindgenError::Codegen)?; |
908 | |
909 | Ok(Bindings { options, module }) |
910 | } |
911 | |
912 | /// Write these bindings as source text to a file. |
913 | pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { |
914 | let file = OpenOptions::new() |
915 | .write(true) |
916 | .truncate(true) |
917 | .create(true) |
918 | .open(path.as_ref())?; |
919 | self.write(Box::new(file))?; |
920 | Ok(()) |
921 | } |
922 | |
923 | /// Write these bindings as source text to the given `Write`able. |
924 | pub fn write<'a>(&self, mut writer: Box<dyn Write + 'a>) -> io::Result<()> { |
925 | const NL: &str = if cfg!(windows) { " \r\n" } else { " \n" }; |
926 | |
927 | if !self.options.disable_header_comment { |
928 | let version = |
929 | option_env!("CARGO_PKG_VERSION" ).unwrap_or("(unknown version)" ); |
930 | writeln!( |
931 | writer, |
932 | "/* automatically generated by rust-bindgen {version} */ {NL}" , |
933 | )?; |
934 | } |
935 | |
936 | for line in self.options.raw_lines.iter() { |
937 | writer.write_all(line.as_bytes())?; |
938 | writer.write_all(NL.as_bytes())?; |
939 | } |
940 | |
941 | if !self.options.raw_lines.is_empty() { |
942 | writer.write_all(NL.as_bytes())?; |
943 | } |
944 | |
945 | match self.format_tokens(&self.module) { |
946 | Ok(formatted_bindings) => { |
947 | writer.write_all(formatted_bindings.as_bytes())?; |
948 | } |
949 | Err(err) => { |
950 | eprintln!( |
951 | "Failed to run rustfmt: {} (non-fatal, continuing)" , |
952 | err |
953 | ); |
954 | writer.write_all(self.module.to_string().as_bytes())?; |
955 | } |
956 | } |
957 | Ok(()) |
958 | } |
959 | |
960 | /// Gets the rustfmt path to rustfmt the generated bindings. |
961 | fn rustfmt_path(&self) -> io::Result<Cow<PathBuf>> { |
962 | debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); |
963 | if let Some(ref p) = self.options.rustfmt_path { |
964 | return Ok(Cow::Borrowed(p)); |
965 | } |
966 | if let Ok(rustfmt) = env::var("RUSTFMT" ) { |
967 | return Ok(Cow::Owned(rustfmt.into())); |
968 | } |
969 | // No rustfmt binary was specified, so assume that the binary is called |
970 | // "rustfmt" and that it is in the user's PATH. |
971 | Ok(Cow::Owned("rustfmt" .into())) |
972 | } |
973 | |
974 | /// Formats a token stream with the formatter set up in `BindgenOptions`. |
975 | fn format_tokens( |
976 | &self, |
977 | tokens: &proc_macro2::TokenStream, |
978 | ) -> io::Result<String> { |
979 | let _t = time::Timer::new("rustfmt_generated_string" ) |
980 | .with_output(self.options.time_phases); |
981 | |
982 | match self.options.formatter { |
983 | Formatter::None => return Ok(tokens.to_string()), |
984 | #[cfg (feature = "prettyplease" )] |
985 | Formatter::Prettyplease => { |
986 | return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens))); |
987 | } |
988 | Formatter::Rustfmt => (), |
989 | } |
990 | |
991 | let rustfmt = self.rustfmt_path()?; |
992 | let mut cmd = Command::new(&*rustfmt); |
993 | |
994 | cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); |
995 | |
996 | if let Some(path) = self |
997 | .options |
998 | .rustfmt_configuration_file |
999 | .as_ref() |
1000 | .and_then(|f| f.to_str()) |
1001 | { |
1002 | cmd.args(["--config-path" , path]); |
1003 | } |
1004 | |
1005 | let mut child = cmd.spawn()?; |
1006 | let mut child_stdin = child.stdin.take().unwrap(); |
1007 | let mut child_stdout = child.stdout.take().unwrap(); |
1008 | |
1009 | let source = tokens.to_string(); |
1010 | |
1011 | // Write to stdin in a new thread, so that we can read from stdout on this |
1012 | // thread. This keeps the child from blocking on writing to its stdout which |
1013 | // might block us from writing to its stdin. |
1014 | let stdin_handle = ::std::thread::spawn(move || { |
1015 | let _ = child_stdin.write_all(source.as_bytes()); |
1016 | source |
1017 | }); |
1018 | |
1019 | let mut output = vec![]; |
1020 | io::copy(&mut child_stdout, &mut output)?; |
1021 | |
1022 | let status = child.wait()?; |
1023 | let source = stdin_handle.join().expect( |
1024 | "The thread writing to rustfmt's stdin doesn't do \ |
1025 | anything that could panic" , |
1026 | ); |
1027 | |
1028 | match String::from_utf8(output) { |
1029 | Ok(bindings) => match status.code() { |
1030 | Some(0) => Ok(bindings), |
1031 | Some(2) => Err(io::Error::new( |
1032 | io::ErrorKind::Other, |
1033 | "Rustfmt parsing errors." .to_string(), |
1034 | )), |
1035 | Some(3) => { |
1036 | rustfmt_non_fatal_error_diagnostic( |
1037 | "Rustfmt could not format some lines" , |
1038 | &self.options, |
1039 | ); |
1040 | Ok(bindings) |
1041 | } |
1042 | _ => Err(io::Error::new( |
1043 | io::ErrorKind::Other, |
1044 | "Internal rustfmt error" .to_string(), |
1045 | )), |
1046 | }, |
1047 | _ => Ok(source), |
1048 | } |
1049 | } |
1050 | } |
1051 | |
1052 | fn rustfmt_non_fatal_error_diagnostic(msg: &str, _options: &BindgenOptions) { |
1053 | warn!(" {}" , msg); |
1054 | |
1055 | #[cfg (feature = "experimental" )] |
1056 | if _options.emit_diagnostics { |
1057 | use crate::diagnostics::{Diagnostic, Level}; |
1058 | |
1059 | Diagnostic::default() |
1060 | .with_title(msg, Level::Warn) |
1061 | .add_annotation( |
1062 | "The bindings will be generated but not formatted." , |
1063 | Level::Note, |
1064 | ) |
1065 | .display(); |
1066 | } |
1067 | } |
1068 | |
1069 | impl std::fmt::Display for Bindings { |
1070 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
1071 | let mut bytes: Vec = vec![]; |
1072 | self.write(Box::new(&mut bytes) as Box<dyn Write>) |
1073 | .expect(msg:"writing to a vec cannot fail" ); |
1074 | f.write_str( |
1075 | data:std::str::from_utf8(&bytes) |
1076 | .expect(msg:"we should only write bindings that are valid utf-8" ), |
1077 | ) |
1078 | } |
1079 | } |
1080 | |
1081 | /// Determines whether the given cursor is in any of the files matched by the |
1082 | /// options. |
1083 | fn filter_builtins(ctx: &BindgenContext, cursor: &clang::Cursor) -> bool { |
1084 | ctx.options().builtins || !cursor.is_builtin() |
1085 | } |
1086 | |
1087 | /// Parse one `Item` from the Clang cursor. |
1088 | fn parse_one( |
1089 | ctx: &mut BindgenContext, |
1090 | cursor: clang::Cursor, |
1091 | parent: Option<ItemId>, |
1092 | ) { |
1093 | if !filter_builtins(ctx, &cursor) { |
1094 | return; |
1095 | } |
1096 | |
1097 | match Item::parse(cursor, parent, ctx) { |
1098 | Ok(..) => {} |
1099 | Err(ParseError::Continue) => {} |
1100 | Err(ParseError::Recurse) => { |
1101 | cursor |
1102 | .visit_sorted(ctx, |ctx: &mut BindgenContext, child: Cursor| parse_one(ctx, cursor:child, parent)); |
1103 | } |
1104 | } |
1105 | } |
1106 | |
1107 | /// Parse the Clang AST into our `Item` internal representation. |
1108 | fn parse(context: &mut BindgenContext) -> Result<(), BindgenError> { |
1109 | use clang_sys::*; |
1110 | |
1111 | let mut error = None; |
1112 | for d in context.translation_unit().diags().iter() { |
1113 | let msg = d.format(); |
1114 | let is_err = d.severity() >= CXDiagnostic_Error; |
1115 | if is_err { |
1116 | let error = error.get_or_insert_with(String::new); |
1117 | error.push_str(&msg); |
1118 | error.push(' \n' ); |
1119 | } else { |
1120 | eprintln!("clang diag: {}" , msg); |
1121 | } |
1122 | } |
1123 | |
1124 | if let Some(message) = error { |
1125 | return Err(BindgenError::ClangDiagnostic(message)); |
1126 | } |
1127 | |
1128 | let cursor = context.translation_unit().cursor(); |
1129 | |
1130 | if context.options().emit_ast { |
1131 | fn dump_if_not_builtin(cur: &clang::Cursor) -> CXChildVisitResult { |
1132 | if !cur.is_builtin() { |
1133 | clang::ast_dump(cur, 0) |
1134 | } else { |
1135 | CXChildVisit_Continue |
1136 | } |
1137 | } |
1138 | cursor.visit(|cur| dump_if_not_builtin(&cur)); |
1139 | } |
1140 | |
1141 | let root = context.root_module(); |
1142 | context.with_module(root, |ctx| { |
1143 | cursor.visit_sorted(ctx, |ctx, child| parse_one(ctx, child, None)) |
1144 | }); |
1145 | |
1146 | assert!( |
1147 | context.current_module() == context.root_module(), |
1148 | "How did this happen?" |
1149 | ); |
1150 | Ok(()) |
1151 | } |
1152 | |
1153 | /// Extracted Clang version data |
1154 | #[derive (Debug)] |
1155 | pub struct ClangVersion { |
1156 | /// Major and minor semver, if parsing was successful |
1157 | pub parsed: Option<(u32, u32)>, |
1158 | /// full version string |
1159 | pub full: String, |
1160 | } |
1161 | |
1162 | /// Get the major and the minor semver numbers of Clang's version |
1163 | pub fn clang_version() -> ClangVersion { |
1164 | ensure_libclang_is_loaded(); |
1165 | |
1166 | //Debian clang version 11.0.1-2 |
1167 | let raw_v: String = clang::extract_clang_version(); |
1168 | let split_v: Option<Vec<&str>> = raw_v |
1169 | .split_whitespace() |
1170 | .find(|t| t.chars().next().map_or(false, |v| v.is_ascii_digit())) |
1171 | .map(|v| v.split('.' ).collect()); |
1172 | if let Some(v) = split_v { |
1173 | if v.len() >= 2 { |
1174 | let maybe_major = v[0].parse::<u32>(); |
1175 | let maybe_minor = v[1].parse::<u32>(); |
1176 | if let (Ok(major), Ok(minor)) = (maybe_major, maybe_minor) { |
1177 | return ClangVersion { |
1178 | parsed: Some((major, minor)), |
1179 | full: raw_v.clone(), |
1180 | }; |
1181 | } |
1182 | } |
1183 | }; |
1184 | ClangVersion { |
1185 | parsed: None, |
1186 | full: raw_v.clone(), |
1187 | } |
1188 | } |
1189 | |
1190 | fn env_var<K: AsRef<str> + AsRef<OsStr>>( |
1191 | parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], |
1192 | key: K, |
1193 | ) -> Result<String, std::env::VarError> { |
1194 | for callback: &Rc in parse_callbacks { |
1195 | callback.read_env_var(key.as_ref()); |
1196 | } |
1197 | std::env::var(key) |
1198 | } |
1199 | |
1200 | /// Looks for the env var `var_${TARGET}`, and falls back to just `var` when it is not found. |
1201 | fn get_target_dependent_env_var( |
1202 | parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], |
1203 | var: &str, |
1204 | ) -> Option<String> { |
1205 | if let Ok(target: String) = env_var(parse_callbacks, key:"TARGET" ) { |
1206 | if let Ok(v: String) = env_var(parse_callbacks, key:format!(" {}_ {}" , var, target)) { |
1207 | return Some(v); |
1208 | } |
1209 | if let Ok(v: String) = env_var( |
1210 | parse_callbacks, |
1211 | key:format!(" {}_ {}" , var, target.replace('-' , "_" )), |
1212 | ) { |
1213 | return Some(v); |
1214 | } |
1215 | } |
1216 | |
1217 | env_var(parse_callbacks, key:var).ok() |
1218 | } |
1219 | |
1220 | /// A ParseCallbacks implementation that will act on file includes by echoing a rerun-if-changed |
1221 | /// line and on env variable usage by echoing a rerun-if-env-changed line |
1222 | /// |
1223 | /// When running inside a `build.rs` script, this can be used to make cargo invalidate the |
1224 | /// generated bindings whenever any of the files included from the header change: |
1225 | /// ``` |
1226 | /// use bindgen::builder; |
1227 | /// let bindings = builder() |
1228 | /// .header("path/to/input/header" ) |
1229 | /// .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) |
1230 | /// .generate(); |
1231 | /// ``` |
1232 | #[derive (Debug)] |
1233 | pub struct CargoCallbacks { |
1234 | rerun_on_header_files: bool, |
1235 | } |
1236 | |
1237 | /// Create a new `CargoCallbacks` value with [`CargoCallbacks::rerun_on_header_files`] disabled. |
1238 | /// |
1239 | /// This constructor has been deprecated in favor of [`CargoCallbacks::new`] where |
1240 | /// [`CargoCallbacks::rerun_on_header_files`] is enabled by default. |
1241 | #[deprecated = "Use `CargoCallbacks::new()` instead. Please, check the documentation for further information." ] |
1242 | pub const CargoCallbacks: CargoCallbacks = CargoCallbacks { |
1243 | rerun_on_header_files: false, |
1244 | }; |
1245 | |
1246 | impl CargoCallbacks { |
1247 | /// Create a new `CargoCallbacks` value. |
1248 | pub fn new() -> Self { |
1249 | Self { |
1250 | rerun_on_header_files: true, |
1251 | } |
1252 | } |
1253 | |
1254 | /// Whether Cargo should re-run the build script if any of the input header files has changed. |
1255 | /// |
1256 | /// This option is enabled by default unless the deprecated [`const@CargoCallbacks`] |
1257 | /// constructor is used. |
1258 | pub fn rerun_on_header_files(mut self, doit: bool) -> Self { |
1259 | self.rerun_on_header_files = doit; |
1260 | self |
1261 | } |
1262 | } |
1263 | |
1264 | impl Default for CargoCallbacks { |
1265 | fn default() -> Self { |
1266 | Self::new() |
1267 | } |
1268 | } |
1269 | |
1270 | impl callbacks::ParseCallbacks for CargoCallbacks { |
1271 | fn header_file(&self, filename: &str) { |
1272 | if self.rerun_on_header_files { |
1273 | println!("cargo:rerun-if-changed= {}" , filename); |
1274 | } |
1275 | } |
1276 | |
1277 | fn include_file(&self, filename: &str) { |
1278 | println!("cargo:rerun-if-changed= {}" , filename); |
1279 | } |
1280 | |
1281 | fn read_env_var(&self, key: &str) { |
1282 | println!("cargo:rerun-if-env-changed= {}" , key); |
1283 | } |
1284 | } |
1285 | |
1286 | /// Test command_line_flag function. |
1287 | #[test ] |
1288 | fn commandline_flag_unit_test_function() { |
1289 | //Test 1 |
1290 | let bindings = crate::builder(); |
1291 | let command_line_flags = bindings.command_line_flags(); |
1292 | |
1293 | let test_cases = [ |
1294 | "--rust-target" , |
1295 | "--no-derive-default" , |
1296 | "--generate" , |
1297 | "functions,types,vars,methods,constructors,destructors" , |
1298 | ] |
1299 | .iter() |
1300 | .map(|&x| x.into()) |
1301 | .collect::<Vec<String>>(); |
1302 | |
1303 | assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); |
1304 | |
1305 | //Test 2 |
1306 | let bindings = crate::builder() |
1307 | .header("input_header" ) |
1308 | .allowlist_type("Distinct_Type" ) |
1309 | .allowlist_function("safe_function" ); |
1310 | |
1311 | let command_line_flags = bindings.command_line_flags(); |
1312 | let test_cases = [ |
1313 | "--rust-target" , |
1314 | "input_header" , |
1315 | "--no-derive-default" , |
1316 | "--generate" , |
1317 | "functions,types,vars,methods,constructors,destructors" , |
1318 | "--allowlist-type" , |
1319 | "Distinct_Type" , |
1320 | "--allowlist-function" , |
1321 | "safe_function" , |
1322 | ] |
1323 | .iter() |
1324 | .map(|&x| x.into()) |
1325 | .collect::<Vec<String>>(); |
1326 | println!("{:?}" , command_line_flags); |
1327 | |
1328 | assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); |
1329 | } |
1330 | |
1331 | #[test ] |
1332 | fn test_rust_to_clang_target() { |
1333 | assert_eq!( |
1334 | rust_to_clang_target("aarch64-apple-ios" ).as_ref(), |
1335 | "arm64-apple-ios" |
1336 | ); |
1337 | } |
1338 | |
1339 | #[test ] |
1340 | fn test_rust_to_clang_target_riscv() { |
1341 | assert_eq!( |
1342 | rust_to_clang_target("riscv64gc-unknown-linux-gnu" ).as_ref(), |
1343 | "riscv64-unknown-linux-gnu" |
1344 | ); |
1345 | assert_eq!( |
1346 | rust_to_clang_target("riscv64imac-unknown-none-elf" ).as_ref(), |
1347 | "riscv64-unknown-none-elf" |
1348 | ); |
1349 | assert_eq!( |
1350 | rust_to_clang_target("riscv32imc-unknown-none-elf" ).as_ref(), |
1351 | "riscv32-unknown-none-elf" |
1352 | ); |
1353 | assert_eq!( |
1354 | rust_to_clang_target("riscv32imac-unknown-none-elf" ).as_ref(), |
1355 | "riscv32-unknown-none-elf" |
1356 | ); |
1357 | assert_eq!( |
1358 | rust_to_clang_target("riscv32imafc-unknown-none-elf" ).as_ref(), |
1359 | "riscv32-unknown-none-elf" |
1360 | ); |
1361 | assert_eq!( |
1362 | rust_to_clang_target("riscv32i-unknown-none-elf" ).as_ref(), |
1363 | "riscv32-unknown-none-elf" |
1364 | ); |
1365 | } |
1366 | |
1367 | #[test ] |
1368 | fn test_rust_to_clang_target_espidf() { |
1369 | assert_eq!( |
1370 | rust_to_clang_target("riscv32imc-esp-espidf" ).as_ref(), |
1371 | "riscv32-esp-elf" |
1372 | ); |
1373 | assert_eq!( |
1374 | rust_to_clang_target("xtensa-esp32-espidf" ).as_ref(), |
1375 | "xtensa-esp32-elf" |
1376 | ); |
1377 | } |
1378 | |