diff --git a/CHANGELOG.md b/CHANGELOG.md index e70b118764..9850539fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,13 +156,18 @@ ## Changed * Fixed name collisions when having a C `enum` and a `typedef` with the same name. - * The `ParseCallbacks::generated_name_override` now receives `ItemInfo<'_>` as + * The `ParseCallbacks::generated_name_override` method now receives `ItemInfo<'_>` as argument instead of a `&str`. * Updated the `clang-sys` crate version to 1.4.0 to support clang 15. * The return type is now ommited in signatures of functions returning `void`. * Updated the `clap` dependency for `bindgen-cli` to 4. * Rewrote the `bindgen-cli` argument parser which could introduce unexpected behavior changes. + * The `ParseCallbacks::add_derives` method now receives `DeriveInfo<'_>` as + argument instead of a `&str`. This type also includes the kind of target type. + * Added a new set of flags `--with-derive-custom`, + `--with-derive-custom-struct`, `--with-derive-custom-enum` and + `--with-derive-custom-enum` to add custom derives from the CLI. ## Removed diff --git a/bindgen-cli/Cargo.toml b/bindgen-cli/Cargo.toml index 76099eb060..b900d9406d 100644 --- a/bindgen-cli/Cargo.toml +++ b/bindgen-cli/Cargo.toml @@ -21,7 +21,7 @@ path = "main.rs" name = "bindgen" [dependencies] -bindgen = { path = "../bindgen", version = "=0.63.0" } +bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli"] } shlex = "1" clap = { version = "4", features = ["derive"] } env_logger = { version = "0.9.0", optional = true } diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs index c536ed6275..2b09d85fd9 100644 --- a/bindgen-cli/options.rs +++ b/bindgen-cli/options.rs @@ -1,6 +1,7 @@ +use bindgen::callbacks::TypeKind; use bindgen::{ builder, AliasVariation, Builder, CodegenConfig, EnumVariation, - MacroTypeVariation, NonCopyUnionStyle, RustTarget, + MacroTypeVariation, NonCopyUnionStyle, RegexSet, RustTarget, DEFAULT_ANON_FIELDS_PREFIX, RUST_TARGET_STRINGS, }; use clap::Parser; @@ -340,6 +341,18 @@ struct BindgenCommand { /// Wrap unsafe operations in unsafe blocks. #[arg(long)] wrap_unsafe_ops: bool, + /// Derive custom traits on any kind of type. The value must be of the shape = where is a coma-separated list of derive macros. + #[arg(long, value_name = "CUSTOM")] + with_derive_custom: Vec, + /// Derive custom traits on a `struct`. The value must be of the shape = where is a coma-separated list of derive macros. + #[arg(long, value_name = "CUSTOM")] + with_derive_custom_struct: Vec, + /// Derive custom traits on an `enum. The value must be of the shape = where is a coma-separated list of derive macros. + #[arg(long, value_name = "CUSTOM")] + with_derive_custom_enum: Vec, + /// Derive custom traits on a `union`. The value must be of the shape = where is a coma-separated list of derive macros. + #[arg(long, value_name = "CUSTOM")] + with_derive_custom_union: Vec, /// Prints the version, and exits #[arg(short = 'V', long)] version: bool, @@ -456,6 +469,10 @@ where merge_extern_blocks, override_abi, wrap_unsafe_ops, + with_derive_custom, + with_derive_custom_struct, + with_derive_custom_enum, + with_derive_custom_union, version, clang_args, } = command; @@ -894,5 +911,72 @@ where builder = builder.wrap_unsafe_ops(true); } + #[derive(Debug)] + struct CustomDeriveCallback { + derives: Vec, + kind: Option, + regex_set: bindgen::RegexSet, + } + + impl bindgen::callbacks::ParseCallbacks for CustomDeriveCallback { + fn cli_args(&self) -> Vec { + let mut args = vec![]; + + let flag = match &self.kind { + None => "--with-derive-custom", + Some(TypeKind::Struct) => "--with-derive-custom-struct", + Some(TypeKind::Enum) => "--with-derive-custom-enum", + Some(TypeKind::Union) => "--with-derive-custom-union", + }; + + let derives = self.derives.join(","); + + for item in self.regex_set.get_items() { + args.extend_from_slice(&[ + flag.to_owned(), + format!("{}={}", item, derives), + ]); + } + + args + } + + fn add_derives( + &self, + info: &bindgen::callbacks::DeriveInfo<'_>, + ) -> Vec { + if self.kind.map(|kind| kind == info.kind).unwrap_or(true) && + self.regex_set.matches(info.name) + { + return self.derives.clone(); + } + vec![] + } + } + + for (custom_derives, kind) in [ + (with_derive_custom, None), + (with_derive_custom_struct, Some(TypeKind::Struct)), + (with_derive_custom_enum, Some(TypeKind::Enum)), + (with_derive_custom_union, Some(TypeKind::Union)), + ] { + for custom_derive in custom_derives { + let (regex, derives) = custom_derive + .rsplit_once('=') + .expect("Invalid custom derive argument: Missing `=`"); + let derives = derives.split(',').map(|s| s.to_owned()).collect(); + + let mut regex_set = RegexSet::new(); + regex_set.insert(regex); + regex_set.build(false); + + builder = builder.parse_callbacks(Box::new(CustomDeriveCallback { + derives, + kind, + regex_set, + })); + } + } + Ok((builder, output, verbose)) } diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml index 32694dad31..0678274fd6 100644 --- a/bindgen-tests/Cargo.toml +++ b/bindgen-tests/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" publish = false [dev-dependencies] -bindgen = { path = "../bindgen" } +bindgen = { path = "../bindgen", features = ["cli"] } diff = "0.1" shlex = "1" clap = { version = "4", features = ["derive"] } diff --git a/bindgen-tests/tests/expectations/tests/derive-custom-cli.rs b/bindgen-tests/tests/expectations/tests/derive-custom-cli.rs new file mode 100644 index 0000000000..36f84c7d93 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/derive-custom-cli.rs @@ -0,0 +1,115 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] + +#[repr(C)] +#[derive(Clone, Default)] +pub struct foo_struct { + pub inner: ::std::os::raw::c_int, +} +#[test] +fn bindgen_test_layout_foo_struct() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(foo_struct)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(foo_struct)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).inner) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(foo_struct), + "::", + stringify!(inner) + ) + ); +} +#[repr(u32)] +#[derive(Clone, Hash, PartialEq, Eq, Copy)] +pub enum foo_enum { + inner = 0, +} +#[repr(C)] +#[derive(Clone, Copy)] +pub union foo_union { + pub fst: ::std::mem::ManuallyDrop<::std::os::raw::c_int>, + pub snd: ::std::mem::ManuallyDrop, +} +#[test] +fn bindgen_test_layout_foo_union() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(foo_union)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(foo_union)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).fst) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(foo_union), + "::", + stringify!(fst) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).snd) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(foo_union), + "::", + stringify!(snd) + ) + ); +} +#[repr(C)] +pub struct non_matching { + pub inner: ::std::os::raw::c_int, +} +#[test] +fn bindgen_test_layout_non_matching() { + const UNINIT: ::std::mem::MaybeUninit = + ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(non_matching)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(non_matching)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).inner) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(non_matching), + "::", + stringify!(inner) + ) + ); +} diff --git a/bindgen-tests/tests/headers/derive-custom-cli.h b/bindgen-tests/tests/headers/derive-custom-cli.h new file mode 100644 index 0000000000..9b65536f94 --- /dev/null +++ b/bindgen-tests/tests/headers/derive-custom-cli.h @@ -0,0 +1,14 @@ +// bindgen-flags: --default-enum-style rust --default-non-copy-union-style manually_drop --no-default=".*" --no-hash=".*" --no-partialeq=".*" --no-debug=".*" --no-copy=".*" --with-derive-custom="foo_[^e].*=Clone" --with-derive-custom-struct="foo.*=Default" --with-derive-custom-enum="foo.*=Copy" --with-derive-custom-union="foo.*=Copy" +struct foo_struct { + int inner; +}; +enum foo_enum { + inner = 0 +}; +union foo_union { + int fst; + float snd; +}; +struct non_matching { + int inner; +}; diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index 5a03d2481e..2292a5efe4 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -47,6 +47,7 @@ static = ["clang-sys/static"] runtime = ["clang-sys/runtime"] # Dynamically discover a `rustfmt` binary using the `which` crate which-rustfmt = ["which"] +cli = [] # These features only exist for CI testing -- don't use them if you're not hacking # on bindgen! diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index cba406cbd1..dc20e2581e 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -25,6 +25,12 @@ impl Default for MacroParsingBehavior { /// A trait to allow configuring different kinds of types in different /// situations. pub trait ParseCallbacks: fmt::Debug { + #[cfg(feature = "cli")] + #[doc(hidden)] + fn cli_args(&self) -> Vec { + vec![] + } + /// This function will be run on every macro that is identified. fn will_parse_macro(&self, _name: &str) -> MacroParsingBehavior { MacroParsingBehavior::Default @@ -120,10 +126,24 @@ pub trait ParseCallbacks: fmt::Debug { /// Relevant information about a type to which new derive attributes will be added using /// [`ParseCallbacks::add_derives`]. +#[derive(Debug)] #[non_exhaustive] pub struct DeriveInfo<'a> { /// The name of the type. pub name: &'a str, + /// The kind of the type. + pub kind: TypeKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The kind of the current type. +pub enum TypeKind { + /// The type is a Rust `struct`. + Struct, + /// The type is a Rust `enum`. + Enum, + /// The type is a Rust `union`. + Union, } /// An struct providing information about the item being passed to `ParseCallbacks::generated_name_override`. diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 041d36691c..6b24ae1bd0 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -18,6 +18,7 @@ use self::struct_layout::StructLayoutTracker; use super::BindgenOptions; +use crate::callbacks::{DeriveInfo, TypeKind as DeriveTypeKind}; use crate::ir::analysis::{HasVtable, Sizedness}; use crate::ir::annotations::FieldAccessorKind; use crate::ir::comp::{ @@ -2100,11 +2101,18 @@ impl CodeGenerator for CompInfo { let mut derives: Vec<_> = derivable_traits.into(); derives.extend(item.annotations().derives().iter().map(String::as_str)); + let is_rust_union = is_union && struct_layout.is_rust_union(); + // The custom derives callback may return a list of derive attributes; // add them to the end of the list. let custom_derives = ctx.options().all_callbacks(|cb| { - cb.add_derives(&crate::callbacks::DeriveInfo { + cb.add_derives(&DeriveInfo { name: &canonical_name, + kind: if is_rust_union { + DeriveTypeKind::Union + } else { + DeriveTypeKind::Struct + }, }) }); // In most cases this will be a no-op, since custom_derives will be empty. @@ -2118,7 +2126,7 @@ impl CodeGenerator for CompInfo { attributes.push(attributes::must_use()); } - let mut tokens = if is_union && struct_layout.is_rust_union() { + let mut tokens = if is_rust_union { quote! { #( #attributes )* pub union #canonical_ident @@ -3112,7 +3120,10 @@ impl CodeGenerator for Enum { // The custom derives callback may return a list of derive attributes; // add them to the end of the list. let custom_derives = ctx.options().all_callbacks(|cb| { - cb.add_derives(&crate::callbacks::DeriveInfo { name: &name }) + cb.add_derives(&DeriveInfo { + name: &name, + kind: DeriveTypeKind::Enum, + }) }); // In most cases this will be a no-op, since custom_derives will be empty. derives.extend(custom_derives.iter().map(|s| s.as_str())); diff --git a/bindgen/lib.rs b/bindgen/lib.rs index 46444a93bb..cf1486c2d2 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -65,7 +65,7 @@ mod clang; mod codegen; mod deps; mod features; -mod ir; +pub mod ir; mod parse; mod regex_set; mod time; @@ -91,7 +91,7 @@ use crate::ir::context::{BindgenContext, ItemId}; pub use crate::ir::function::Abi; use crate::ir::item::Item; use crate::parse::ParseError; -use crate::regex_set::RegexSet; +pub use crate::regex_set::RegexSet; use std::borrow::Cow; use std::env; @@ -653,6 +653,11 @@ impl Builder { output_vector.push("--wrap-unsafe-ops".into()); } + #[cfg(feature = "cli")] + for callbacks in &self.options.parse_callbacks { + output_vector.extend(callbacks.cli_args()); + } + // Add clang arguments output_vector.push("--".into()); diff --git a/bindgen/regex_set.rs b/bindgen/regex_set.rs index 9f1e2251cd..6246dd255b 100644 --- a/bindgen/regex_set.rs +++ b/bindgen/regex_set.rs @@ -16,6 +16,13 @@ pub struct RegexSet { } impl RegexSet { + /// Create a new RegexSet + pub fn new() -> RegexSet { + RegexSet { + ..Default::default() + } + } + /// Is this set empty? pub fn is_empty(&self) -> bool { self.items.is_empty()