From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/bindgen/lib.rs | 1380 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1380 insertions(+) create mode 100644 third_party/rust/bindgen/lib.rs (limited to 'third_party/rust/bindgen/lib.rs') diff --git a/third_party/rust/bindgen/lib.rs b/third_party/rust/bindgen/lib.rs new file mode 100644 index 0000000000..c4ab11486c --- /dev/null +++ b/third_party/rust/bindgen/lib.rs @@ -0,0 +1,1380 @@ +//! Generate Rust bindings for C and C++ libraries. +//! +//! Provide a C/C++ header file, receive Rust FFI code to call into C/C++ +//! functions and use types defined in the header. +//! +//! See the [`Builder`](./struct.Builder.html) struct for usage. +//! +//! See the [Users Guide](https://rust-lang.github.io/rust-bindgen/) for +//! additional documentation. +#![deny(missing_docs)] +#![deny(unused_extern_crates)] +#![deny(clippy::disallowed_methods)] +// To avoid rather annoying warnings when matching with CXCursor_xxx as a +// constant. +#![allow(non_upper_case_globals)] +// `quote!` nests quite deeply. +#![recursion_limit = "128"] + +#[macro_use] +extern crate bitflags; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate quote; + +#[cfg(feature = "logging")] +#[macro_use] +extern crate log; + +#[cfg(not(feature = "logging"))] +#[macro_use] +mod log_stubs; + +#[macro_use] +mod extra_assertions; + +mod codegen; +mod deps; +mod options; +mod time; + +pub mod callbacks; + +mod clang; +#[cfg(feature = "experimental")] +mod diagnostics; +mod features; +mod ir; +mod parse; +mod regex_set; + +pub use codegen::{ + AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle, +}; +#[cfg(feature = "__cli")] +pub use features::RUST_TARGET_STRINGS; +pub use features::{RustTarget, LATEST_STABLE_RUST}; +pub use ir::annotations::FieldVisibilityKind; +pub use ir::function::Abi; +pub use regex_set::RegexSet; + +use codegen::CodegenError; +use features::RustFeatures; +use ir::comment; +use ir::context::{BindgenContext, ItemId}; +use ir::item::Item; +use options::BindgenOptions; +use parse::ParseError; + +use std::borrow::Cow; +use std::collections::hash_map::Entry; +use std::env; +use std::ffi::OsStr; +use std::fs::{File, OpenOptions}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::rc::Rc; +use std::str::FromStr; + +// Some convenient typedefs for a fast hash map and hash set. +type HashMap = rustc_hash::FxHashMap; +type HashSet = rustc_hash::FxHashSet; + +/// Default prefix for the anon fields. +pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_"; + +const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern"; + +fn file_is_cpp(name_file: &str) -> bool { + name_file.ends_with(".hpp") || + name_file.ends_with(".hxx") || + name_file.ends_with(".hh") || + name_file.ends_with(".h++") +} + +fn args_are_cpp(clang_args: &[Box]) -> bool { + for w in clang_args.windows(2) { + if w[0].as_ref() == "-xc++" || w[1].as_ref() == "-xc++" { + return true; + } + if w[0].as_ref() == "-x" && w[1].as_ref() == "c++" { + return true; + } + if w[0].as_ref() == "-include" && file_is_cpp(w[1].as_ref()) { + return true; + } + } + false +} + +bitflags! { + /// A type used to indicate which kind of items we have to generate. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct CodegenConfig: u32 { + /// Whether to generate functions. + const FUNCTIONS = 1 << 0; + /// Whether to generate types. + const TYPES = 1 << 1; + /// Whether to generate constants. + const VARS = 1 << 2; + /// Whether to generate methods. + const METHODS = 1 << 3; + /// Whether to generate constructors + const CONSTRUCTORS = 1 << 4; + /// Whether to generate destructors. + const DESTRUCTORS = 1 << 5; + } +} + +impl CodegenConfig { + /// Returns true if functions should be generated. + pub fn functions(self) -> bool { + self.contains(CodegenConfig::FUNCTIONS) + } + + /// Returns true if types should be generated. + pub fn types(self) -> bool { + self.contains(CodegenConfig::TYPES) + } + + /// Returns true if constants should be generated. + pub fn vars(self) -> bool { + self.contains(CodegenConfig::VARS) + } + + /// Returns true if methds should be generated. + pub fn methods(self) -> bool { + self.contains(CodegenConfig::METHODS) + } + + /// Returns true if constructors should be generated. + pub fn constructors(self) -> bool { + self.contains(CodegenConfig::CONSTRUCTORS) + } + + /// Returns true if destructors should be generated. + pub fn destructors(self) -> bool { + self.contains(CodegenConfig::DESTRUCTORS) + } +} + +impl Default for CodegenConfig { + fn default() -> Self { + CodegenConfig::all() + } +} + +/// Formatting tools that can be used to format the bindings +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Formatter { + /// Do not format the bindings. + None, + /// Use `rustfmt` to format the bindings. + Rustfmt, + #[cfg(feature = "prettyplease")] + /// Use `prettyplease` to format the bindings. + Prettyplease, +} + +impl Default for Formatter { + fn default() -> Self { + Self::Rustfmt + } +} + +impl FromStr for Formatter { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "rustfmt" => Ok(Self::Rustfmt), + #[cfg(feature = "prettyplease")] + "prettyplease" => Ok(Self::Prettyplease), + _ => Err(format!("`{}` is not a valid formatter", s)), + } + } +} + +impl std::fmt::Display for Formatter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Self::None => "none", + Self::Rustfmt => "rustfmt", + #[cfg(feature = "prettyplease")] + Self::Prettyplease => "prettyplease", + }; + + s.fmt(f) + } +} + +/// Configure and generate Rust bindings for a C/C++ header. +/// +/// This is the main entry point to the library. +/// +/// ```ignore +/// use bindgen::builder; +/// +/// // Configure and generate bindings. +/// let bindings = builder().header("path/to/input/header") +/// .allowlist_type("SomeCoolClass") +/// .allowlist_function("do_some_cool_thing") +/// .generate()?; +/// +/// // Write the generated bindings to an output file. +/// bindings.write_to_file("path/to/output.rs")?; +/// ``` +/// +/// # Enums +/// +/// Bindgen can map C/C++ enums into Rust in different ways. The way bindgen maps enums depends on +/// the pattern passed to several methods: +/// +/// 1. [`constified_enum_module()`](#method.constified_enum_module) +/// 2. [`bitfield_enum()`](#method.bitfield_enum) +/// 3. [`newtype_enum()`](#method.newtype_enum) +/// 4. [`rustified_enum()`](#method.rustified_enum) +/// +/// For each C enum, bindgen tries to match the pattern in the following order: +/// +/// 1. Constified enum module +/// 2. Bitfield enum +/// 3. Newtype enum +/// 4. Rustified enum +/// +/// If none of the above patterns match, then bindgen will generate a set of Rust constants. +/// +/// # Clang arguments +/// +/// Extra arguments can be passed to with clang: +/// 1. [`clang_arg()`](#method.clang_arg): takes a single argument +/// 2. [`clang_args()`](#method.clang_args): takes an iterator of arguments +/// 3. `BINDGEN_EXTRA_CLANG_ARGS` environment variable: whitespace separate +/// environment variable of arguments +/// +/// Clang arguments specific to your crate should be added via the +/// `clang_arg()`/`clang_args()` methods. +/// +/// End-users of the crate may need to set the `BINDGEN_EXTRA_CLANG_ARGS` environment variable to +/// add additional arguments. For example, to build against a different sysroot a user could set +/// `BINDGEN_EXTRA_CLANG_ARGS` to `--sysroot=/path/to/sysroot`. +/// +/// # Regular expression arguments +/// +/// Some [`Builder`] methods, such as `allowlist_*` and `blocklist_*`, allow regular +/// expressions as arguments. These regular expressions will be enclosed in parentheses and +/// anchored with `^` and `$`. So, if the argument passed is ``, the regular expression to be +/// stored will be `^()$`. +/// +/// As a consequence, regular expressions passed to `bindgen` will try to match the whole name of +/// an item instead of a section of it, which means that to match any items with the prefix +/// `prefix`, the `prefix.*` regular expression must be used. +/// +/// Certain methods, like [`Builder::allowlist_function`], use regular expressions over function +/// names. To match C++ methods, prefix the name of the type where they belong, followed by an +/// underscore. So, if the type `Foo` has a method `bar`, it can be matched with the `Foo_bar` +/// regular expression. +/// +/// Additionally, Objective-C interfaces can be matched by prefixing the regular expression with +/// `I`. For example, the `IFoo` regular expression matches the `Foo` interface, and the `IFoo_foo` +/// regular expression matches the `foo` method of the `Foo` interface. +/// +/// Releases of `bindgen` with a version lesser or equal to `0.62.0` used to accept the wildcard +/// pattern `*` as a valid regular expression. This behavior has been deprecated, and the `.*` +/// regular expression must be used instead. +#[derive(Debug, Default, Clone)] +pub struct Builder { + options: BindgenOptions, +} + +/// Construct a new [`Builder`](./struct.Builder.html). +pub fn builder() -> Builder { + Default::default() +} + +fn get_extra_clang_args( + parse_callbacks: &[Rc], +) -> Vec { + // Add any extra arguments from the environment to the clang command line. + let extra_clang_args = match get_target_dependent_env_var( + parse_callbacks, + "BINDGEN_EXTRA_CLANG_ARGS", + ) { + None => return vec![], + Some(s) => s, + }; + + // Try to parse it with shell quoting. If we fail, make it one single big argument. + if let Some(strings) = shlex::split(&extra_clang_args) { + return strings; + } + vec![extra_clang_args] +} + +impl Builder { + /// Generate the Rust bindings using the options built up thus far. + pub fn generate(mut self) -> Result { + // Add any extra arguments from the environment to the clang command line. + self.options.clang_args.extend( + get_extra_clang_args(&self.options.parse_callbacks) + .into_iter() + .map(String::into_boxed_str), + ); + + for header in &self.options.input_headers { + self.options + .for_each_callback(|cb| cb.header_file(header.as_ref())); + } + + // Transform input headers to arguments on the clang command line. + self.options.clang_args.extend( + self.options.input_headers + [..self.options.input_headers.len().saturating_sub(1)] + .iter() + .flat_map(|header| ["-include".into(), header.clone()]), + ); + + let input_unsaved_files = + std::mem::take(&mut self.options.input_header_contents) + .into_iter() + .map(|(name, contents)| { + clang::UnsavedFile::new(name.as_ref(), contents.as_ref()) + }) + .collect::>(); + + Bindings::generate(self.options, input_unsaved_files) + } + + /// Preprocess and dump the input header files to disk. + /// + /// This is useful when debugging bindgen, using C-Reduce, or when filing + /// issues. The resulting file will be named something like `__bindgen.i` or + /// `__bindgen.ii` + pub fn dump_preprocessed_input(&self) -> io::Result<()> { + let clang = + clang_sys::support::Clang::find(None, &[]).ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "Cannot find clang executable", + ) + })?; + + // The contents of a wrapper file that includes all the input header + // files. + let mut wrapper_contents = String::new(); + + // Whether we are working with C or C++ inputs. + let mut is_cpp = args_are_cpp(&self.options.clang_args); + + // For each input header, add `#include "$header"`. + for header in &self.options.input_headers { + is_cpp |= file_is_cpp(header); + + wrapper_contents.push_str("#include \""); + wrapper_contents.push_str(header); + wrapper_contents.push_str("\"\n"); + } + + // For each input header content, add a prefix line of `#line 0 "$name"` + // followed by the contents. + for (name, contents) in &self.options.input_header_contents { + is_cpp |= file_is_cpp(name); + + wrapper_contents.push_str("#line 0 \""); + wrapper_contents.push_str(name); + wrapper_contents.push_str("\"\n"); + wrapper_contents.push_str(contents); + } + + let wrapper_path = PathBuf::from(if is_cpp { + "__bindgen.cpp" + } else { + "__bindgen.c" + }); + + { + let mut wrapper_file = File::create(&wrapper_path)?; + wrapper_file.write_all(wrapper_contents.as_bytes())?; + } + + let mut cmd = Command::new(clang.path); + cmd.arg("-save-temps") + .arg("-E") + .arg("-C") + .arg("-c") + .arg(&wrapper_path) + .stdout(Stdio::piped()); + + for a in &self.options.clang_args { + cmd.arg(a.as_ref()); + } + + for a in get_extra_clang_args(&self.options.parse_callbacks) { + cmd.arg(a); + } + + let mut child = cmd.spawn()?; + + let mut preprocessed = child.stdout.take().unwrap(); + let mut file = File::create(if is_cpp { + "__bindgen.ii" + } else { + "__bindgen.i" + })?; + io::copy(&mut preprocessed, &mut file)?; + + if child.wait()?.success() { + Ok(()) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "clang exited with non-zero status", + )) + } + } +} + +impl BindgenOptions { + fn build(&mut self) { + const REGEX_SETS_LEN: usize = 29; + + let regex_sets: [_; REGEX_SETS_LEN] = [ + &mut self.blocklisted_types, + &mut self.blocklisted_functions, + &mut self.blocklisted_items, + &mut self.blocklisted_files, + &mut self.blocklisted_vars, + &mut self.opaque_types, + &mut self.allowlisted_vars, + &mut self.allowlisted_types, + &mut self.allowlisted_functions, + &mut self.allowlisted_files, + &mut self.allowlisted_items, + &mut self.bitfield_enums, + &mut self.constified_enums, + &mut self.constified_enum_modules, + &mut self.newtype_enums, + &mut self.newtype_global_enums, + &mut self.rustified_enums, + &mut self.rustified_non_exhaustive_enums, + &mut self.type_alias, + &mut self.new_type_alias, + &mut self.new_type_alias_deref, + &mut self.bindgen_wrapper_union, + &mut self.manually_drop_union, + &mut self.no_partialeq_types, + &mut self.no_copy_types, + &mut self.no_debug_types, + &mut self.no_default_types, + &mut self.no_hash_types, + &mut self.must_use_types, + ]; + + let record_matches = self.record_matches; + #[cfg(feature = "experimental")] + { + let sets_len = REGEX_SETS_LEN + self.abi_overrides.len(); + let names = if self.emit_diagnostics { + <[&str; REGEX_SETS_LEN]>::into_iter([ + "--blocklist-type", + "--blocklist-function", + "--blocklist-item", + "--blocklist-file", + "--blocklist-var", + "--opaque-type", + "--allowlist-type", + "--allowlist-function", + "--allowlist-var", + "--allowlist-file", + "--allowlist-item", + "--bitfield-enum", + "--newtype-enum", + "--newtype-global-enum", + "--rustified-enum", + "--rustified-enum-non-exhaustive", + "--constified-enum-module", + "--constified-enum", + "--type-alias", + "--new-type-alias", + "--new-type-alias-deref", + "--bindgen-wrapper-union", + "--manually-drop-union", + "--no-partialeq", + "--no-copy", + "--no-debug", + "--no-default", + "--no-hash", + "--must-use", + ]) + .chain((0..self.abi_overrides.len()).map(|_| "--override-abi")) + .map(Some) + .collect() + } else { + vec![None; sets_len] + }; + + for (regex_set, name) in + self.abi_overrides.values_mut().chain(regex_sets).zip(names) + { + regex_set.build_with_diagnostics(record_matches, name); + } + } + #[cfg(not(feature = "experimental"))] + for regex_set in self.abi_overrides.values_mut().chain(regex_sets) { + regex_set.build(record_matches); + } + + let rust_target = self.rust_target; + #[allow(deprecated)] + if rust_target <= RustTarget::Stable_1_30 { + deprecated_target_diagnostic(rust_target, self); + } + + // Disable `untagged_union` if the target does not support it. + if !self.rust_features.untagged_union { + self.untagged_union = false; + } + } + + /// Update rust target version + pub fn set_rust_target(&mut self, rust_target: RustTarget) { + self.rust_target = rust_target; + + // Keep rust_features synced with rust_target + self.rust_features = rust_target.into(); + } + + /// Get features supported by target Rust version + pub fn rust_features(&self) -> RustFeatures { + self.rust_features + } + + fn last_callback( + &self, + f: impl Fn(&dyn callbacks::ParseCallbacks) -> Option, + ) -> Option { + self.parse_callbacks + .iter() + .filter_map(|cb| f(cb.as_ref())) + .last() + } + + fn all_callbacks( + &self, + f: impl Fn(&dyn callbacks::ParseCallbacks) -> Vec, + ) -> Vec { + self.parse_callbacks + .iter() + .flat_map(|cb| f(cb.as_ref())) + .collect() + } + + fn for_each_callback(&self, f: impl Fn(&dyn callbacks::ParseCallbacks)) { + self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); + } + + fn process_comment(&self, comment: &str) -> String { + let comment = comment::preprocess(comment); + self.parse_callbacks + .last() + .and_then(|cb| cb.process_comment(&comment)) + .unwrap_or(comment) + } +} + +fn deprecated_target_diagnostic(target: RustTarget, _options: &BindgenOptions) { + 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); + + #[cfg(feature = "experimental")] + if _options.emit_diagnostics { + use crate::diagnostics::{Diagnostic, Level}; + + let mut diagnostic = Diagnostic::default(); + diagnostic.with_title( + format!("The {} Rust target is deprecated.", target), + Level::Warn, + ); + diagnostic.add_annotation( + "This Rust target was passed to `--rust-target`", + Level::Info, + ); + 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); + diagnostic.display(); + } +} + +#[cfg(feature = "runtime")] +fn ensure_libclang_is_loaded() { + if clang_sys::is_loaded() { + return; + } + + // XXX (issue #350): Ensure that our dynamically loaded `libclang` + // doesn't get dropped prematurely, nor is loaded multiple times + // across different threads. + + lazy_static! { + static ref LIBCLANG: std::sync::Arc = { + clang_sys::load().expect("Unable to find libclang"); + clang_sys::get_library().expect( + "We just loaded libclang and it had better still be \ + here!", + ) + }; + } + + clang_sys::set_library(Some(LIBCLANG.clone())); +} + +#[cfg(not(feature = "runtime"))] +fn ensure_libclang_is_loaded() {} + +/// Error type for rust-bindgen. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum BindgenError { + /// The header was a folder. + FolderAsHeader(PathBuf), + /// Permissions to read the header is insufficient. + InsufficientPermissions(PathBuf), + /// The header does not exist. + NotExist(PathBuf), + /// Clang diagnosed an error. + ClangDiagnostic(String), + /// Code generation reported an error. + Codegen(CodegenError), +} + +impl std::fmt::Display for BindgenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BindgenError::FolderAsHeader(h) => { + write!(f, "'{}' is a folder", h.display()) + } + BindgenError::InsufficientPermissions(h) => { + write!(f, "insufficient permissions to read '{}'", h.display()) + } + BindgenError::NotExist(h) => { + write!(f, "header '{}' does not exist.", h.display()) + } + BindgenError::ClangDiagnostic(message) => { + write!(f, "clang diagnosed error: {}", message) + } + BindgenError::Codegen(err) => { + write!(f, "codegen error: {}", err) + } + } + } +} + +impl std::error::Error for BindgenError {} + +/// Generated Rust bindings. +#[derive(Debug)] +pub struct Bindings { + options: BindgenOptions, + module: proc_macro2::TokenStream, +} + +pub(crate) const HOST_TARGET: &str = + include_str!(concat!(env!("OUT_DIR"), "/host-target.txt")); + +// Some architecture triplets are different between rust and libclang, see #1211 +// and duplicates. +fn rust_to_clang_target(rust_target: &str) -> Box { + if rust_target.starts_with("aarch64-apple-") { + let mut clang_target = "arm64-apple-".to_owned(); + clang_target + .push_str(rust_target.strip_prefix("aarch64-apple-").unwrap()); + return clang_target.into(); + } else if rust_target.starts_with("riscv64gc-") { + let mut clang_target = "riscv64-".to_owned(); + clang_target.push_str(rust_target.strip_prefix("riscv64gc-").unwrap()); + return clang_target.into(); + } else if rust_target.ends_with("-espidf") { + let mut clang_target = + rust_target.strip_suffix("-espidf").unwrap().to_owned(); + clang_target.push_str("-elf"); + if clang_target.starts_with("riscv32imc-") { + clang_target = "riscv32-".to_owned() + + clang_target.strip_prefix("riscv32imc-").unwrap(); + } + return clang_target.into(); + } else if rust_target.starts_with("riscv32imc-") { + let mut clang_target = "riscv32-".to_owned(); + clang_target.push_str(rust_target.strip_prefix("riscv32imc-").unwrap()); + return clang_target.into(); + } else if rust_target.starts_with("riscv32imac-") { + let mut clang_target = "riscv32-".to_owned(); + clang_target + .push_str(rust_target.strip_prefix("riscv32imac-").unwrap()); + return clang_target.into(); + } + rust_target.into() +} + +/// Returns the effective target, and whether it was explicitly specified on the +/// clang flags. +fn find_effective_target(clang_args: &[Box]) -> (Box, bool) { + let mut args = clang_args.iter(); + while let Some(opt) = args.next() { + if opt.starts_with("--target=") { + let mut split = opt.split('='); + split.next(); + return (split.next().unwrap().into(), true); + } + + if opt.as_ref() == "-target" { + if let Some(target) = args.next() { + return (target.clone(), true); + } + } + } + + // If we're running from a build script, try to find the cargo target. + if let Ok(t) = env::var("TARGET") { + return (rust_to_clang_target(&t), false); + } + + (rust_to_clang_target(HOST_TARGET), false) +} + +impl Bindings { + /// Generate bindings for the given options. + pub(crate) fn generate( + mut options: BindgenOptions, + input_unsaved_files: Vec, + ) -> Result { + ensure_libclang_is_loaded(); + + #[cfg(feature = "runtime")] + debug!( + "Generating bindings, libclang at {}", + clang_sys::get_library().unwrap().path().display() + ); + #[cfg(not(feature = "runtime"))] + debug!("Generating bindings, libclang linked"); + + options.build(); + + let (effective_target, explicit_target) = + find_effective_target(&options.clang_args); + + let is_host_build = + rust_to_clang_target(HOST_TARGET) == effective_target; + + // NOTE: The is_host_build check wouldn't be sound normally in some + // cases if we were to call a binary (if you have a 32-bit clang and are + // building on a 64-bit system for example). But since we rely on + // opening libclang.so, it has to be the same architecture and thus the + // check is fine. + if !explicit_target && !is_host_build { + options.clang_args.insert( + 0, + format!("--target={}", effective_target).into_boxed_str(), + ); + }; + + fn detect_include_paths(options: &mut BindgenOptions) { + if !options.detect_include_paths { + return; + } + + // Filter out include paths and similar stuff, so we don't incorrectly + // promote them to `-isystem`. + let clang_args_for_clang_sys = { + let mut last_was_include_prefix = false; + options + .clang_args + .iter() + .filter(|arg| { + if last_was_include_prefix { + last_was_include_prefix = false; + return false; + } + + let arg = arg.as_ref(); + + // https://clang.llvm.org/docs/ClangCommandLineReference.html + // -isystem and -isystem-after are harmless. + if arg == "-I" || arg == "--include-directory" { + last_was_include_prefix = true; + return false; + } + + if arg.starts_with("-I") || + arg.starts_with("--include-directory=") + { + return false; + } + + true + }) + .map(|arg| arg.clone().into()) + .collect::>() + }; + + debug!( + "Trying to find clang with flags: {:?}", + clang_args_for_clang_sys + ); + + let clang = match clang_sys::support::Clang::find( + None, + &clang_args_for_clang_sys, + ) { + None => return, + Some(clang) => clang, + }; + + debug!("Found clang: {:?}", clang); + + // Whether we are working with C or C++ inputs. + let is_cpp = args_are_cpp(&options.clang_args) || + options.input_headers.iter().any(|h| file_is_cpp(h)); + + let search_paths = if is_cpp { + clang.cpp_search_paths + } else { + clang.c_search_paths + }; + + if let Some(search_paths) = search_paths { + for path in search_paths.into_iter() { + if let Ok(path) = path.into_os_string().into_string() { + options.clang_args.push("-isystem".into()); + options.clang_args.push(path.into_boxed_str()); + } + } + } + } + + detect_include_paths(&mut options); + + #[cfg(unix)] + fn can_read(perms: &std::fs::Permissions) -> bool { + use std::os::unix::fs::PermissionsExt; + perms.mode() & 0o444 > 0 + } + + #[cfg(not(unix))] + fn can_read(_: &std::fs::Permissions) -> bool { + true + } + + if let Some(h) = options.input_headers.last() { + let path = Path::new(h.as_ref()); + if let Ok(md) = std::fs::metadata(path) { + if md.is_dir() { + return Err(BindgenError::FolderAsHeader(path.into())); + } + if !can_read(&md.permissions()) { + return Err(BindgenError::InsufficientPermissions( + path.into(), + )); + } + options.clang_args.push(h.clone()); + } else { + return Err(BindgenError::NotExist(path.into())); + } + } + + for (idx, f) in input_unsaved_files.iter().enumerate() { + if idx != 0 || !options.input_headers.is_empty() { + options.clang_args.push("-include".into()); + } + options.clang_args.push(f.name.to_str().unwrap().into()) + } + + debug!("Fixed-up options: {:?}", options); + + let time_phases = options.time_phases; + let mut context = BindgenContext::new(options, &input_unsaved_files); + + if is_host_build { + debug_assert_eq!( + context.target_pointer_size(), + std::mem::size_of::<*mut ()>(), + "{:?} {:?}", + effective_target, + HOST_TARGET + ); + } + + { + let _t = time::Timer::new("parse").with_output(time_phases); + parse(&mut context)?; + } + + let (module, options) = + codegen::codegen(context).map_err(BindgenError::Codegen)?; + + Ok(Bindings { options, module }) + } + + /// Write these bindings as source text to a file. + pub fn write_to_file>(&self, path: P) -> io::Result<()> { + let file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(path.as_ref())?; + self.write(Box::new(file))?; + Ok(()) + } + + /// Write these bindings as source text to the given `Write`able. + pub fn write<'a>(&self, mut writer: Box) -> io::Result<()> { + const NL: &str = if cfg!(windows) { "\r\n" } else { "\n" }; + + if !self.options.disable_header_comment { + let version = + option_env!("CARGO_PKG_VERSION").unwrap_or("(unknown version)"); + writeln!( + writer, + "/* automatically generated by rust-bindgen {version} */{NL}", + )?; + } + + for line in self.options.raw_lines.iter() { + writer.write_all(line.as_bytes())?; + writer.write_all(NL.as_bytes())?; + } + + if !self.options.raw_lines.is_empty() { + writer.write_all(NL.as_bytes())?; + } + + match self.format_tokens(&self.module) { + Ok(formatted_bindings) => { + writer.write_all(formatted_bindings.as_bytes())?; + } + Err(err) => { + eprintln!( + "Failed to run rustfmt: {} (non-fatal, continuing)", + err + ); + writer.write_all(self.module.to_string().as_bytes())?; + } + } + Ok(()) + } + + /// Gets the rustfmt path to rustfmt the generated bindings. + fn rustfmt_path(&self) -> io::Result> { + debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); + if let Some(ref p) = self.options.rustfmt_path { + return Ok(Cow::Borrowed(p)); + } + if let Ok(rustfmt) = env::var("RUSTFMT") { + return Ok(Cow::Owned(rustfmt.into())); + } + #[cfg(feature = "which-rustfmt")] + match which::which("rustfmt") { + Ok(p) => Ok(Cow::Owned(p)), + Err(e) => { + Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))) + } + } + #[cfg(not(feature = "which-rustfmt"))] + // No rustfmt binary was specified, so assume that the binary is called + // "rustfmt" and that it is in the user's PATH. + Ok(Cow::Owned("rustfmt".into())) + } + + /// Formats a token stream with the formatter set up in `BindgenOptions`. + fn format_tokens( + &self, + tokens: &proc_macro2::TokenStream, + ) -> io::Result { + let _t = time::Timer::new("rustfmt_generated_string") + .with_output(self.options.time_phases); + + match self.options.formatter { + Formatter::None => return Ok(tokens.to_string()), + #[cfg(feature = "prettyplease")] + Formatter::Prettyplease => { + return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens))); + } + Formatter::Rustfmt => (), + } + + let rustfmt = self.rustfmt_path()?; + let mut cmd = Command::new(&*rustfmt); + + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); + + if let Some(path) = self + .options + .rustfmt_configuration_file + .as_ref() + .and_then(|f| f.to_str()) + { + cmd.args(["--config-path", path]); + } + + let mut child = cmd.spawn()?; + let mut child_stdin = child.stdin.take().unwrap(); + let mut child_stdout = child.stdout.take().unwrap(); + + let source = tokens.to_string(); + + // Write to stdin in a new thread, so that we can read from stdout on this + // thread. This keeps the child from blocking on writing to its stdout which + // might block us from writing to its stdin. + let stdin_handle = ::std::thread::spawn(move || { + let _ = child_stdin.write_all(source.as_bytes()); + source + }); + + let mut output = vec![]; + io::copy(&mut child_stdout, &mut output)?; + + let status = child.wait()?; + let source = stdin_handle.join().expect( + "The thread writing to rustfmt's stdin doesn't do \ + anything that could panic", + ); + + match String::from_utf8(output) { + Ok(bindings) => match status.code() { + Some(0) => Ok(bindings), + Some(2) => Err(io::Error::new( + io::ErrorKind::Other, + "Rustfmt parsing errors.".to_string(), + )), + Some(3) => { + rustfmt_non_fatal_error_diagnostic( + "Rustfmt could not format some lines", + &self.options, + ); + Ok(bindings) + } + _ => Err(io::Error::new( + io::ErrorKind::Other, + "Internal rustfmt error".to_string(), + )), + }, + _ => Ok(source), + } + } +} + +fn rustfmt_non_fatal_error_diagnostic(msg: &str, _options: &BindgenOptions) { + warn!("{}", msg); + + #[cfg(feature = "experimental")] + if _options.emit_diagnostics { + use crate::diagnostics::{Diagnostic, Level}; + + Diagnostic::default() + .with_title(msg, Level::Warn) + .add_annotation( + "The bindings will be generated but not formatted.", + Level::Note, + ) + .display(); + } +} + +impl std::fmt::Display for Bindings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut bytes = vec![]; + self.write(Box::new(&mut bytes) as Box) + .expect("writing to a vec cannot fail"); + f.write_str( + std::str::from_utf8(&bytes) + .expect("we should only write bindings that are valid utf-8"), + ) + } +} + +/// Determines whether the given cursor is in any of the files matched by the +/// options. +fn filter_builtins(ctx: &BindgenContext, cursor: &clang::Cursor) -> bool { + ctx.options().builtins || !cursor.is_builtin() +} + +/// Parse one `Item` from the Clang cursor. +fn parse_one( + ctx: &mut BindgenContext, + cursor: clang::Cursor, + parent: Option, +) { + if !filter_builtins(ctx, &cursor) { + return; + } + + match Item::parse(cursor, parent, ctx) { + Ok(..) => {} + Err(ParseError::Continue) => {} + Err(ParseError::Recurse) => { + cursor + .visit_sorted(ctx, |ctx, child| parse_one(ctx, child, parent)); + } + } +} + +/// Parse the Clang AST into our `Item` internal representation. +fn parse(context: &mut BindgenContext) -> Result<(), BindgenError> { + use clang_sys::*; + + let mut error = None; + for d in context.translation_unit().diags().iter() { + let msg = d.format(); + let is_err = d.severity() >= CXDiagnostic_Error; + if is_err { + let error = error.get_or_insert_with(String::new); + error.push_str(&msg); + error.push('\n'); + } else { + eprintln!("clang diag: {}", msg); + } + } + + if let Some(message) = error { + return Err(BindgenError::ClangDiagnostic(message)); + } + + let cursor = context.translation_unit().cursor(); + + if context.options().emit_ast { + fn dump_if_not_builtin(cur: &clang::Cursor) -> CXChildVisitResult { + if !cur.is_builtin() { + clang::ast_dump(cur, 0) + } else { + CXChildVisit_Continue + } + } + cursor.visit(|cur| dump_if_not_builtin(&cur)); + } + + let root = context.root_module(); + context.with_module(root, |ctx| { + cursor.visit_sorted(ctx, |ctx, child| parse_one(ctx, child, None)) + }); + + assert!( + context.current_module() == context.root_module(), + "How did this happen?" + ); + Ok(()) +} + +/// Extracted Clang version data +#[derive(Debug)] +pub struct ClangVersion { + /// Major and minor semver, if parsing was successful + pub parsed: Option<(u32, u32)>, + /// full version string + pub full: String, +} + +/// Get the major and the minor semver numbers of Clang's version +pub fn clang_version() -> ClangVersion { + ensure_libclang_is_loaded(); + + //Debian clang version 11.0.1-2 + let raw_v: String = clang::extract_clang_version(); + let split_v: Option> = raw_v + .split_whitespace() + .find(|t| t.chars().next().map_or(false, |v| v.is_ascii_digit())) + .map(|v| v.split('.').collect()); + if let Some(v) = split_v { + if v.len() >= 2 { + let maybe_major = v[0].parse::(); + let maybe_minor = v[1].parse::(); + if let (Ok(major), Ok(minor)) = (maybe_major, maybe_minor) { + return ClangVersion { + parsed: Some((major, minor)), + full: raw_v.clone(), + }; + } + } + }; + ClangVersion { + parsed: None, + full: raw_v.clone(), + } +} + +fn env_var + AsRef>( + parse_callbacks: &[Rc], + key: K, +) -> Result { + for callback in parse_callbacks { + callback.read_env_var(key.as_ref()); + } + std::env::var(key) +} + +/// Looks for the env var `var_${TARGET}`, and falls back to just `var` when it is not found. +fn get_target_dependent_env_var( + parse_callbacks: &[Rc], + var: &str, +) -> Option { + if let Ok(target) = env_var(parse_callbacks, "TARGET") { + if let Ok(v) = env_var(parse_callbacks, format!("{}_{}", var, target)) { + return Some(v); + } + if let Ok(v) = env_var( + parse_callbacks, + format!("{}_{}", var, target.replace('-', "_")), + ) { + return Some(v); + } + } + + env_var(parse_callbacks, var).ok() +} + +/// A ParseCallbacks implementation that will act on file includes by echoing a rerun-if-changed +/// line and on env variable usage by echoing a rerun-if-env-changed line +/// +/// When running inside a `build.rs` script, this can be used to make cargo invalidate the +/// generated bindings whenever any of the files included from the header change: +/// ``` +/// use bindgen::builder; +/// let bindings = builder() +/// .header("path/to/input/header") +/// .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) +/// .generate(); +/// ``` +#[derive(Debug)] +pub struct CargoCallbacks { + rerun_on_header_files: bool, +} + +/// Create a new `CargoCallbacks` value with [`CargoCallbacks::rerun_on_header_files`] disabled. +/// +/// This constructor has been deprecated in favor of [`CargoCallbacks::new`] where +/// [`CargoCallbacks::rerun_on_header_files`] is enabled by default. +#[deprecated = "Use `CargoCallbacks::new()` instead. Please, check the documentation for further information."] +pub const CargoCallbacks: CargoCallbacks = CargoCallbacks { + rerun_on_header_files: false, +}; + +impl CargoCallbacks { + /// Create a new `CargoCallbacks` value. + pub fn new() -> Self { + Self { + rerun_on_header_files: true, + } + } + + /// Whether Cargo should re-run the build script if any of the input header files has changed. + /// + /// This option is enabled by default unless the deprecated [`const@CargoCallbacks`] + /// constructor is used. + pub fn rerun_on_header_files(mut self, doit: bool) -> Self { + self.rerun_on_header_files = doit; + self + } +} + +impl Default for CargoCallbacks { + fn default() -> Self { + Self::new() + } +} + +impl callbacks::ParseCallbacks for CargoCallbacks { + fn header_file(&self, filename: &str) { + if self.rerun_on_header_files { + println!("cargo:rerun-if-changed={}", filename); + } + } + + fn include_file(&self, filename: &str) { + println!("cargo:rerun-if-changed={}", filename); + } + + fn read_env_var(&self, key: &str) { + println!("cargo:rerun-if-env-changed={}", key); + } +} + +/// Test command_line_flag function. +#[test] +fn commandline_flag_unit_test_function() { + //Test 1 + let bindings = crate::builder(); + let command_line_flags = bindings.command_line_flags(); + + let test_cases = [ + "--rust-target", + "--no-derive-default", + "--generate", + "functions,types,vars,methods,constructors,destructors", + ] + .iter() + .map(|&x| x.into()) + .collect::>(); + + assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); + + //Test 2 + let bindings = crate::builder() + .header("input_header") + .allowlist_type("Distinct_Type") + .allowlist_function("safe_function"); + + let command_line_flags = bindings.command_line_flags(); + let test_cases = [ + "--rust-target", + "input_header", + "--no-derive-default", + "--generate", + "functions,types,vars,methods,constructors,destructors", + "--allowlist-type", + "Distinct_Type", + "--allowlist-function", + "safe_function", + ] + .iter() + .map(|&x| x.into()) + .collect::>(); + println!("{:?}", command_line_flags); + + assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); +} + +#[test] +fn test_rust_to_clang_target() { + assert_eq!( + rust_to_clang_target("aarch64-apple-ios").as_ref(), + "arm64-apple-ios" + ); +} + +#[test] +fn test_rust_to_clang_target_riscv() { + assert_eq!( + rust_to_clang_target("riscv64gc-unknown-linux-gnu").as_ref(), + "riscv64-unknown-linux-gnu" + ); + assert_eq!( + rust_to_clang_target("riscv32imc-unknown-none-elf").as_ref(), + "riscv32-unknown-none-elf" + ); + assert_eq!( + rust_to_clang_target("riscv32imac-unknown-none-elf").as_ref(), + "riscv32-unknown-none-elf" + ); +} + +#[test] +fn test_rust_to_clang_target_espidf() { + assert_eq!( + rust_to_clang_target("riscv32imc-esp-espidf").as_ref(), + "riscv32-esp-elf" + ); + assert_eq!( + rust_to_clang_target("xtensa-esp32-espidf").as_ref(), + "xtensa-esp32-elf" + ); +} -- cgit v1.2.3