//! 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"]
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. pubconst DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_";
/// Formatting tools that can be used to format the bindings #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pubenum Formatter { /// Do not format the bindings.
None, /// Use `rustfmt` to format the bindings.
Rustfmt, #[cfg(feature = "prettyplease")] /// Use `prettyplease` to format the bindings.
Prettyplease,
}
/// 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)] pubstruct Builder {
options: BindgenOptions,
}
/// Construct a new [`Builder`](./struct.Builder.html). pubfn 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. iflet 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. pubfn generate(mutself) -> Result<Bindings, BindgenError> { // Keep rust_features synced with rust_target self.options.rust_features = matchself.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()));
}
/// 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` pubfn 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. letmut wrapper_contents = String::new();
// Whether we are working with C or C++ inputs. letmut 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);
// 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);
#[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!")
});
/// Error type for rust-bindgen. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] pubenum 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), /// 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 { matchself {
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}")
}
BindgenError::UnsupportedEdition(edition, target) => {
write!(f, "edition {edition} is not available on Rust {target}")
}
}
}
}
// 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";
// If we're running from a build script, try to find the cargo target. iflet 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");
}
}
}
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 = { letmut last_was_include_prefix = false;
options
.clang_args
.iter()
.filter(|arg| { if last_was_include_prefix {
last_was_include_prefix = false; returnfalse;
}
// 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
};
{ 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. pubfn 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. pubfn 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)");
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())?;
}
/// Gets the rustfmt path to rustfmt the generated bindings. fn rustfmt_path(&self) -> Cow<'_, Path> {
debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); iflet Some(ref p) = self.options.rustfmt_path {
Cow::Borrowed(p)
} elseiflet 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);
// 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 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),
}
}
}
#[cfg(feature = "experimental")] if _options.emit_diagnostics { usecrate::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 { letmut bytes = vec![]; self.write(Box::new(&mut bytes) asBox<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;
}
assert_eq!(
context.current_module(),
context.root_module(), "How did this happen?"
);
Ok(())
}
/// Extracted Clang version data #[derive(Debug)] pubstruct 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 pubfn clang_version() -> ClangVersion {
ensure_libclang_is_loaded();
/// 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> { iflet Ok(target) = env_var(parse_callbacks, "TARGET") { iflet Ok(v) = env_var(parse_callbacks, format!("{var}_{target}")) { return Some(v);
} iflet 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)] pubstruct 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."] pubconst CargoCallbacks: CargoCallbacks = CargoCallbacks {
rerun_on_header_files: false,
};
impl CargoCallbacks { /// Create a new `CargoCallbacks` value. pubfn 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. pubfn rerun_on_header_files(mutself, doit: bool) -> Self { self.rerun_on_header_files = doit; self
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.