summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Dumont <dan.dumont@hcl.com>2023-01-20 15:12:42 -0500
committerGitHub <noreply@github.com>2023-01-20 15:12:42 -0500
commitbca47cd9c2e718012f7f953be25bb3a6a9ca400b (patch)
tree432dc9180f96a0365c9c49f73158a26f51e727d1
parent190a017a100b00aeebe2c5e5acfd5ee699a655c9 (diff)
Implement cli option for custom derive (#2328)
* custom derives after DeriveInfo * Introduce `TypeKind` instead of `CompKind` * Add tests * Emit CLI flags for callbacks * update changelog * run rustfmt * fix tests * fix features Co-authored-by: Christian Poveda <christian.poveda@ferrous-systems.com>
-rw-r--r--CHANGELOG.md7
-rw-r--r--bindgen-cli/Cargo.toml2
-rw-r--r--bindgen-cli/options.rs86
-rw-r--r--bindgen-tests/Cargo.toml2
-rw-r--r--bindgen-tests/tests/expectations/tests/derive-custom-cli.rs115
-rw-r--r--bindgen-tests/tests/headers/derive-custom-cli.h14
-rw-r--r--bindgen/Cargo.toml1
-rw-r--r--bindgen/callbacks.rs20
-rw-r--r--bindgen/codegen/mod.rs17
-rw-r--r--bindgen/lib.rs9
-rw-r--r--bindgen/regex_set.rs7
11 files changed, 271 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e70b1187..9850539f 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 76099eb0..b900d940 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 c536ed62..2b09d85f 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 <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
+ #[arg(long, value_name = "CUSTOM")]
+ with_derive_custom: Vec<String>,
+ /// Derive custom traits on a `struct`. The <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
+ #[arg(long, value_name = "CUSTOM")]
+ with_derive_custom_struct: Vec<String>,
+ /// Derive custom traits on an `enum. The <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
+ #[arg(long, value_name = "CUSTOM")]
+ with_derive_custom_enum: Vec<String>,
+ /// Derive custom traits on a `union`. The <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
+ #[arg(long, value_name = "CUSTOM")]
+ with_derive_custom_union: Vec<String>,
/// 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<String>,
+ kind: Option<TypeKind>,
+ regex_set: bindgen::RegexSet,
+ }
+
+ impl bindgen::callbacks::ParseCallbacks for CustomDeriveCallback {
+ fn cli_args(&self) -> Vec<String> {
+ 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<String> {
+ 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 32694dad..0678274f 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 00000000..36f84c7d
--- /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<foo_struct> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<foo_struct>(),
+ 4usize,
+ concat!("Size of: ", stringify!(foo_struct))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<foo_struct>(),
+ 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<f32>,
+}
+#[test]
+fn bindgen_test_layout_foo_union() {
+ const UNINIT: ::std::mem::MaybeUninit<foo_union> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<foo_union>(),
+ 4usize,
+ concat!("Size of: ", stringify!(foo_union))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<foo_union>(),
+ 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<non_matching> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<non_matching>(),
+ 4usize,
+ concat!("Size of: ", stringify!(non_matching))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<non_matching>(),
+ 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 00000000..9b65536f
--- /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 5a03d248..2292a5ef 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 cba406cb..dc20e258 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<String> {
+ 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 041d3669..6b24ae1b 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 46444a93..cf1486c2 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 9f1e2251..6246dd25 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()