| //! 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 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, | |
| }; | |
| pub use features::{RustEdition, RustTarget, LATEST_STABLE_RUST}; | |
| pub use ir::annotations::FieldVisibilityKind; | |
| pub use ir::function::Abi; | |
| #[cfg(feature = "__cli")] | |
| pub use options::cli::builder_from_flags; | |
| 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::mem::size_of; | |
| 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<K, V> = rustc_hash::FxHashMap<K, V>; | |
| type HashSet<K> = rustc_hash::FxHashSet<K>; | |
| /// 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 { | |
| Path::new(name_file).extension().is_some_and(|ext| { | |
| ext.eq_ignore_ascii_case("hpp") || | |
| ext.eq_ignore_ascii_case("hxx") || | |
| ext.eq_ignore_ascii_case("hh") || | |
| ext.eq_ignore_ascii_case("h++") | |
| }) | |
| } | |
| fn args_are_cpp(clang_args: &[Box<str>]) -> 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 methods 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, Default)] | |
| #[non_exhaustive] | |
| pub enum Formatter { | |
| /// Do not format the bindings. | |
| None, | |
| /// Use `rustfmt` to format the bindings. | |
| #[default] | |
| Rustfmt, | |
| #[cfg(feature = "prettyplease")] | |
| /// Use `prettyplease` to format the bindings. | |
| Prettyplease, | |
| } | |
| impl FromStr for Formatter { | |
| type Err = String; | |
| fn from_str(s: &str) -> Result<Self, Self::Err> { | |
| match s { | |
| "none" => Ok(Self::None), | |
| "rustfmt" => Ok(Self::Rustfmt), | |
| #[cfg(feature = "prettyplease")] | |
| "prettyplease" => Ok(Self::Prettyplease), | |
| _ => Err(format!("`{s}` is not a valid formatter")), | |
| } | |
| } | |
| } | |
| 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", | |
| }; | |
| std::fmt::Display::fmt(&s, 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) | |
| /// 5. [`rustified_non_exhaustive_enum()`](#method.rustified_non_exhaustive_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 `<regex>`, the regular expression to be | |
| /// stored will be `^(<regex>)$`. | |
| /// | |
| /// 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<dyn callbacks::ParseCallbacks>], | |
| ) -> Vec<String> { | |
| // Add any extra arguments from the environment to the clang command line. | |
| let Some(extra_clang_args) = get_target_dependent_env_var( | |
| parse_callbacks, | |
| "BINDGEN_EXTRA_CLANG_ARGS", | |
| ) else { | |
| return vec![]; | |
| }; | |
| // 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<Bindings, BindgenError> { | |
| // Ensure at least one input source (file or content) was provided. | |
| // This does not enforce non-empty content, as header_contents always adds an entry. | |
| if self.options.input_headers.is_empty() && | |
| self.options.input_header_contents.is_empty() | |
| { | |
| return Err(BindgenError::NoHeadersProvided); | |
| } | |
| // Keep rust_features synced with rust_target | |
| self.options.rust_features = match self.options.rust_edition { | |
| Some(edition) => { | |
| if !edition.is_available(self.options.rust_target) { | |
| return Err(BindgenError::UnsupportedEdition( | |
| edition, | |
| self.options.rust_target, | |
| )); | |
| } | |
| RustFeatures::new(self.options.rust_target, edition) | |
| } | |
| None => { | |
| RustFeatures::new_with_latest_edition(self.options.rust_target) | |
| } | |
| }; | |
| // 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.fallback_clang_args = self | |
| .options | |
| .clang_args | |
| .iter() | |
| .filter(|arg| { | |
| !arg.starts_with("-MMD") && | |
| !arg.starts_with("-MD") && | |
| !arg.starts_with("--write-user-dependencies") && | |
| !arg.starts_with("--user-dependencies") | |
| }) | |
| .cloned() | |
| .collect::<Vec<_>>(); | |
| 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::<Vec<_>>(); | |
| 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 = 30; | |
| 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, | |
| &mut self.safe_functions, | |
| ]; | |
| 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", | |
| "--safe-functions", | |
| ]) | |
| .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); | |
| } | |
| } | |
| /// Update rust target version | |
| pub fn set_rust_target(&mut self, rust_target: RustTarget) { | |
| self.rust_target = rust_target; | |
| } | |
| /// Get features supported by target Rust version | |
| pub fn rust_features(&self) -> RustFeatures { | |
| self.rust_features | |
| } | |
| fn last_callback<T>( | |
| &self, | |
| f: impl Fn(&dyn callbacks::ParseCallbacks) -> Option<T>, | |
| ) -> Option<T> { | |
| self.parse_callbacks | |
| .iter() | |
| .filter_map(|cb| f(cb.as_ref())) | |
| .next_back() | |
| } | |
| fn all_callbacks<T>( | |
| &self, | |
| f: impl Fn(&dyn callbacks::ParseCallbacks) -> Vec<T>, | |
| ) -> Vec<T> { | |
| 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.last_callback(|cb| cb.process_comment(&comment)) | |
| .unwrap_or(comment) | |
| } | |
| } | |
| #[cfg(feature = "runtime")] | |
| fn ensure_libclang_is_loaded() { | |
| use std::sync::{Arc, OnceLock}; | |
| 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. | |
| static LIBCLANG: OnceLock<Arc<clang_sys::SharedLibrary>> = OnceLock::new(); | |
| let libclang = LIBCLANG.get_or_init(|| { | |
| 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), | |
| /// No input headers were provided. | |
| NoHeadersProvided, | |
| /// The header does not exist. | |
| NotExist(PathBuf), | |
| /// Clang diagnosed an error. | |
| ClangDiagnostic(String), | |
| /// Code generation reported an error. | |
| Codegen(CodegenError), | |
| /// The passed edition is not available on that Rust target. | |
| UnsupportedEdition(RustEdition, RustTarget), | |
| } | |
| 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::NoHeadersProvided => { | |
| write!(f, "no input headers were provided") | |
| } | |
| 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}") | |
| } | |
| BindgenError::UnsupportedEdition(edition, target) => { | |
| write!(f, "edition {edition} is not available on Rust {target}") | |
| } | |
| } | |
| } | |
| } | |
| 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<str> { | |
| const TRIPLE_HYPHENS_MESSAGE: &str = "Target triple should contain hyphens"; | |
| let mut triple: Vec<&str> = rust_target.split_terminator('-').collect(); | |
| assert!(!triple.is_empty(), "{TRIPLE_HYPHENS_MESSAGE}"); | |
| triple.resize(4, ""); | |
| // RISC-V | |
| if triple[0].starts_with("riscv32") { | |
| triple[0] = "riscv32"; | |
| } else if triple[0].starts_with("riscv64") { | |
| triple[0] = "riscv64"; | |
| } | |
| // Apple | |
| if triple[1] == "apple" { | |
| if triple[0] == "aarch64" { | |
| triple[0] = "arm64"; | |
| } | |
| if triple[3] == "sim" { | |
| triple[3] = "simulator"; | |
| } | |
| } | |
| // ESP-IDF | |
| if triple[2] == "espidf" { | |
| triple[2] = "elf"; | |
| } | |
| // Windows gnullvm | |
| if triple[3] == "gnullvm" { | |
| triple[3] = "gnu"; | |
| } | |
| triple | |
| .iter() | |
| .skip(1) | |
| .fold(triple[0].to_string(), |triple, part| { | |
| if part.is_empty() { | |
| triple | |
| } else { | |
| triple + "-" + part | |
| } | |
| }) | |
| .into() | |
| } | |
| /// Returns the effective target, and whether it was explicitly specified on the | |
| /// clang flags. | |
| fn find_effective_target(clang_args: &[Box<str>]) -> (Box<str>, 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: &[clang::UnsavedFile], | |
| ) -> Result<Bindings, BindgenError> { | |
| ensure_libclang_is_loaded(); | |
| #[cfg(feature = "runtime")] | |
| match clang_sys::get_library().unwrap().version() { | |
| None => { | |
| warn!("Could not detect a Clang version, make sure you are using libclang 9 or newer"); | |
| } | |
| Some(version) => { | |
| if version < clang_sys::Version::V9_0 { | |
| warn!("Detected Clang version {version:?} which is unsupported and can cause invalid code generation, use libclang 9 or newer"); | |
| } | |
| } | |
| } | |
| #[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 { | |
| let target_arg = | |
| format!("--target={effective_target}").into_boxed_str(); | |
| options.clang_args.insert(0, target_arg.clone()); | |
| options.fallback_clang_args.insert(0, target_arg); | |
| } | |
| 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::<Vec<_>>() | |
| }; | |
| debug!( | |
| "Trying to find clang with flags: {clang_args_for_clang_sys:?}" | |
| ); | |
| let Some(clang) = clang_sys::support::Clang::find( | |
| None, | |
| &clang_args_for_clang_sys, | |
| ) else { | |
| return; | |
| }; | |
| 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 { | |
| if let Ok(path) = path.into_os_string().into_string() { | |
| options.clang_args.push("-isystem".into()); | |
| options.clang_args.push(path.clone().into_boxed_str()); | |
| options.fallback_clang_args.push("-isystem".into()); | |
| options.fallback_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(), | |
| 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<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { | |
| let file = OpenOptions::new() | |
| .write(true) | |
| .truncate(true) | |
| .create(true) | |
| .open(path.as_ref())?; | |
| self.write(file)?; | |
| Ok(()) | |
| } | |
| /// Write these bindings as source text to the given `Write`able. | |
| pub fn write(&self, mut writer: impl Write) -> 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)"); | |
| write!( | |
| writer, | |
| "/* automatically generated by rust-bindgen {version} */{NL}{NL}", | |
| )?; | |
| } | |
| for line in &self.options.raw_lines { | |
| 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: {err} (non-fatal, continuing)" | |
| ); | |
| writer.write_all(self.module.to_string().as_bytes())?; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Gets the rustfmt path to rustfmt the generated bindings. | |
| fn rustfmt_path(&self) -> Cow<'_, Path> { | |
| debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); | |
| if let Some(ref p) = self.options.rustfmt_path { | |
| Cow::Borrowed(p) | |
| } else if let Ok(rustfmt) = env::var("RUSTFMT") { | |
| Cow::Owned(rustfmt.into()) | |
| } else { | |
| // No rustfmt binary was specified, so assume that the binary is called | |
| // "rustfmt" and that it is in the user's PATH. | |
| Cow::Borrowed(Path::new("rustfmt")) | |
| } | |
| } | |
| /// Formats a token stream with the formatter set up in `BindgenOptions`. | |
| fn format_tokens( | |
| &self, | |
| tokens: &proc_macro2::TokenStream, | |
| ) -> io::Result<String> { | |
| 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 edition = self | |
| .options | |
| .rust_edition | |
| .unwrap_or_else(|| self.options.rust_target.latest_edition()); | |
| cmd.args(["--edition", &format!("{edition}")]); | |
| 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::Warning) | |
| .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(&mut bytes) | |
| .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<ItemId>, | |
| ) { | |
| 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() { | |
| 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() { | |
| CXChildVisit_Continue | |
| } else { | |
| clang::ast_dump(cur, 0) | |
| } | |
| } | |
| 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_eq!( | |
| 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<Vec<&str>> = raw_v | |
| .split_whitespace() | |
| .find(|t| t.chars().next().is_some_and(|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::<u32>(); | |
| let maybe_minor = v[1].parse::<u32>(); | |
| 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<K: AsRef<str> + AsRef<OsStr>>( | |
| parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>], | |
| key: K, | |
| ) -> Result<String, env::VarError> { | |
| for callback in parse_callbacks { | |
| callback.read_env_var(key.as_ref()); | |
| } | |
| 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<dyn callbacks::ParseCallbacks>], | |
| var: &str, | |
| ) -> Option<String> { | |
| 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 = 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::<Vec<String>>(); | |
| assert!(test_cases.iter().all(|x| command_line_flags.contains(x))); | |
| //Test 2 | |
| let bindings = 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::<Vec<String>>(); | |
| 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("riscv64imac-unknown-none-elf").as_ref(), | |
| "riscv64-unknown-none-elf" | |
| ); | |
| 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" | |
| ); | |
| assert_eq!( | |
| rust_to_clang_target("riscv32imafc-unknown-none-elf").as_ref(), | |
| "riscv32-unknown-none-elf" | |
| ); | |
| assert_eq!( | |
| rust_to_clang_target("riscv32i-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" | |
| ); | |
| } | |
| #[test] | |
| fn test_rust_to_clang_target_gnullvm() { | |
| assert_eq!( | |
| rust_to_clang_target("x86_64-pc-windows-gnullvm").as_ref(), | |
| "x86_64-pc-windows-gnu" | |
| ); | |
| assert_eq!( | |
| rust_to_clang_target("aarch64-pc-windows-gnullvm").as_ref(), | |
| "aarch64-pc-windows-gnu" | |
| ); | |
| } | |
| #[test] | |
| fn test_rust_to_clang_target_simulator() { | |
| assert_eq!( | |
| rust_to_clang_target("aarch64-apple-ios-sim").as_ref(), | |
| "arm64-apple-ios-simulator" | |
| ); | |
| assert_eq!( | |
| rust_to_clang_target("aarch64-apple-tvos-sim").as_ref(), | |
| "arm64-apple-tvos-simulator" | |
| ); | |
| assert_eq!( | |
| rust_to_clang_target("aarch64-apple-watchos-sim").as_ref(), | |
| "arm64-apple-watchos-simulator" | |
| ); | |
| } |