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>
This commit is contained in:
Dan Dumont 2023-01-20 15:12:42 -05:00 committed by GitHub
parent 190a017a10
commit bca47cd9c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 271 additions and 9 deletions

View File

@ -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

View File

@ -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 }

View File

@ -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))
}

View File

@ -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"] }

View File

@ -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)
)
);
}

View File

@ -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;
};

View File

@ -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!

View File

@ -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`.

View File

@ -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()));

View File

@ -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());

View File

@ -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()