Skip to content

Implement cli option for custom derive #2328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion bindgen-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
86 changes: 85 additions & 1 deletion bindgen-cli/options.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
}
2 changes: 1 addition & 1 deletion bindgen-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
115 changes: 115 additions & 0 deletions bindgen-tests/tests/expectations/tests/derive-custom-cli.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions bindgen-tests/tests/headers/derive-custom-cli.h
Original file line number Diff line number Diff line change
@@ -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;
};
1 change: 1 addition & 0 deletions bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
20 changes: 20 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand Down
17 changes: 14 additions & 3 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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()));
Expand Down
Loading