1380 lines
44 KiB
Rust
1380 lines
44 KiB
Rust
//! 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<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 {
|
|
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<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 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<Self, Self::Err> {
|
|
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 `<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 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<Bindings, BindgenError> {
|
|
// 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::<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 = 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<T>(
|
|
&self,
|
|
f: impl Fn(&dyn callbacks::ParseCallbacks) -> Option<T>,
|
|
) -> Option<T> {
|
|
self.parse_callbacks
|
|
.iter()
|
|
.filter_map(|cb| f(cb.as_ref()))
|
|
.last()
|
|
}
|
|
|
|
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.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::SharedLibrary> = {
|
|
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<str> {
|
|
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<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: Vec<clang::UnsavedFile>,
|
|
) -> Result<Bindings, BindgenError> {
|
|
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::<Vec<_>>()
|
|
};
|
|
|
|
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<P: AsRef<Path>>(&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<dyn Write + 'a>) -> 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<Cow<PathBuf>> {
|
|
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<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 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<dyn Write>)
|
|
.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().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<Vec<&str>> = 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::<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, std::env::VarError> {
|
|
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<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 = 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::<Vec<String>>();
|
|
|
|
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::<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("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"
|
|
);
|
|
}
|