Skip to main content

bindgen/
lib.rs

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