Skip to content
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

Add support for custom attributes #2866

Merged
merged 1 commit into from
Sep 3, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
- Add option to use DST structs for flexible arrays (--flexarray-dst, #2772).
- Add option to dynamically load variables (#2812).
- Add option in CLI to use rustified non-exhaustive enums (--rustified-non-exhaustive-enum, #2847).
- Add support for custom attributes (--with-attribute-custom, #2866)
## Changed
- Remove which and lazy-static dependencies (#2809, #2817).
- Generate compile-time layout tests (#2787).
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bindgen-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ clap = { version = "4", features = ["derive"] }
clap_complete = "4"
env_logger = { version = "0.10.0", optional = true }
log = { version = "0.4", optional = true }
proc-macro2 = { version = "1", default-features = false }
shlex = "1"

[features]
Expand Down
131 changes: 131 additions & 0 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ use bindgen::{
};
use clap::error::{Error, ErrorKind};
use clap::{CommandFactory, Parser};
use proc_macro2::TokenStream;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::str::FromStr;

fn rust_target_help() -> String {
format!(
Expand Down Expand Up @@ -87,6 +89,43 @@ fn parse_custom_derive(
Ok((derives, regex.to_owned()))
}

fn parse_custom_attribute(
custom_attribute: &str,
) -> Result<(Vec<String>, String), Error> {
let mut brace_level = 0;
let (regex, attributes) = custom_attribute
.rsplit_once(|c| {
match c {
']' => brace_level += 1,
'[' => brace_level -= 1,
_ => {}
}
c == '=' && brace_level == 0
})
.ok_or_else(|| Error::raw(ErrorKind::InvalidValue, "Missing `=`"))?;

let mut brace_level = 0;
let attributes = attributes
.split(|c| {
match c {
']' => brace_level += 1,
'[' => brace_level -= 1,
_ => {}
}
c == ',' && brace_level == 0
})
.map(|s| s.to_owned())
.collect::<Vec<_>>();

for attribute in &attributes {
if let Err(err) = TokenStream::from_str(attribute) {
return Err(Error::raw(ErrorKind::InvalidValue, err));
}
}

Ok((attributes, regex.to_owned()))
}

#[derive(Parser, Debug)]
#[clap(
about = "Generates Rust bindings from C/C++ headers.",
Expand Down Expand Up @@ -424,6 +463,18 @@ struct BindgenCommand {
/// 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", value_parser = parse_custom_derive)]
with_derive_custom_union: Vec<(Vec<String>, String)>,
/// Add custom attributes on any kind of type. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes.
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
with_attribute_custom: Vec<(Vec<String>, String)>,
/// Add custom attributes on a `struct`. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes.
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
with_attribute_custom_struct: Vec<(Vec<String>, String)>,
/// Add custom attributes on an `enum. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes.
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
with_attribute_custom_enum: Vec<(Vec<String>, String)>,
/// Add custom attributes on a `union`. The CUSTOM value must be of the shape REGEX=ATTRIBUTE where ATTRIBUTE is a coma-separated list of attributes.
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
with_attribute_custom_union: Vec<(Vec<String>, String)>,
/// Generate wrappers for `static` and `static inline` functions.
#[arg(long, requires = "experimental")]
wrap_static_fns: bool,
Expand Down Expand Up @@ -574,6 +625,10 @@ where
with_derive_custom_struct,
with_derive_custom_enum,
with_derive_custom_union,
with_attribute_custom,
with_attribute_custom_struct,
with_attribute_custom_enum,
with_attribute_custom_union,
wrap_static_fns,
wrap_static_fns_path,
wrap_static_fns_suffix,
Expand Down Expand Up @@ -1130,6 +1185,82 @@ where
}
}

#[derive(Debug)]
struct CustomAttributeCallback {
attributes: Vec<String>,
kind: Option<TypeKind>,
regex_set: bindgen::RegexSet,
}

impl bindgen::callbacks::ParseCallbacks for CustomAttributeCallback {
fn cli_args(&self) -> Vec<String> {
let mut args = vec![];

let flag = match &self.kind {
None => "--with-attribute-custom",
Some(TypeKind::Struct) => "--with-attribute-custom-struct",
Some(TypeKind::Enum) => "--with-attribute-custom-enum",
Some(TypeKind::Union) => "--with-attribute-custom-union",
};

let attributes = self.attributes.join(",");

for item in self.regex_set.get_items() {
args.extend_from_slice(&[
flag.to_owned(),
format!("{}={}", item, attributes),
]);
}

args
}

fn add_attributes(
&self,
info: &bindgen::callbacks::AttributeInfo<'_>,
) -> Vec<String> {
if self.kind.map(|kind| kind == info.kind).unwrap_or(true) &&
self.regex_set.matches(info.name)
{
return self.attributes.clone();
}
vec![]
}
}

for (custom_attributes, kind, name) in [
(with_attribute_custom, None, "--with-attribute-custom"),
(
with_attribute_custom_struct,
Some(TypeKind::Struct),
"--with-attribute-custom-struct",
),
(
with_attribute_custom_enum,
Some(TypeKind::Enum),
"--with-attribute-custom-enum",
),
(
with_attribute_custom_union,
Some(TypeKind::Union),
"--with-attribute-custom-union",
),
] {
let name = emit_diagnostics.then_some(name);
for (attributes, regex) in custom_attributes {
let mut regex_set = RegexSet::new();
regex_set.insert(regex);
regex_set.build_with_diagnostics(false, name);

builder =
builder.parse_callbacks(Box::new(CustomAttributeCallback {
attributes,
kind,
regex_set,
}));
}
}

if wrap_static_fns {
builder = builder.wrap_static_fns(true);
}
Expand Down
12 changes: 12 additions & 0 deletions bindgen-integration/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ impl ParseCallbacks for MacroCallback {
vec![]
}
}

// Test the "custom attributes" capability.
fn add_attributes(
&self,
info: &bindgen::callbacks::AttributeInfo<'_>,
) -> Vec<String> {
if info.name == "Test" {
vec!["#[cfg_attr(test, derive(PartialOrd))]".into()]
} else {
vec![]
}
}
}

impl Drop for MacroCallback {
Expand Down
9 changes: 9 additions & 0 deletions bindgen-integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ fn test_custom_derive() {
assert!(!(test1 > test2));
}

#[test]
fn test_custom_attributes() {
// The `add_attributes` callback should have added `#[cfg_attr(test, derive(PartialOrd))])`
// to the `Test` struct. If it didn't, this will fail to compile.
let test1 = unsafe { bindings::Test::new(5) };
let test2 = unsafe { bindings::Test::new(6) };
assert!(test1 < test2);
}

#[test]
fn test_wrap_static_fns() {
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
Expand Down
1 change: 1 addition & 0 deletions bindgen-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ clap = { version = "4", features = ["derive"] }
clap_complete = "4"
shlex = "1"
prettyplease = { version = "0.2.7", features = ["verbatim"] }
proc-macro2 = { version = "1", default-features = false }
syn = { version = "2.0" }
tempfile = "3"
similar = { version = "2.2.1", features = ["inline"] }
Expand Down
48 changes: 48 additions & 0 deletions bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs

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

22 changes: 22 additions & 0 deletions bindgen-tests/tests/expectations/tests/attribute-custom.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/attribute-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-attribute-custom="foo_[^e].*=#[doc(hidden)]" --with-attribute-custom-struct="foo.*=#[derive(Default)]" --with-attribute-custom-enum="foo.*=#[cfg_attr(test, derive(PartialOrd, Copy))]" --with-attribute-custom-union="foo.*=#[derive(Clone)],#[derive(Copy)]"
struct foo_struct {
int inner;
};
enum foo_enum {
inner = 0
};
union foo_union {
int fst;
float snd;
};
struct non_matching {
int inner;
};
28 changes: 28 additions & 0 deletions bindgen-tests/tests/headers/attribute-custom.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// bindgen-flags: --no-derive-debug --no-derive-copy --no-derive-default --default-enum-style rust --no-layout-tests

/** <div rustbindgen attribute="#[derive(Debug)]"></div> */
struct my_type;

/** <div rustbindgen attribute="#[derive(Clone)]"></div> */
struct my_type;

struct my_type {
int a;
};

/**
* <div rustbindgen attribute="#[derive(Debug)]"></div>
* <div rustbindgen attribute="#[derive(Clone)]"></div>
*/
struct my_type2;

struct my_type2 {
unsigned a;
};

/**
* <div rustbindgen attribute="#[derive(Debug)]" attribute="#[derive(Clone)]"></div>
*/
struct my_type3 {
unsigned long a;
};
19 changes: 19 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ pub trait ParseCallbacks: fmt::Debug {
vec![]
}

/// Provide a list of custom attributes.
///
/// If no additional attributes are wanted, this function should return an
/// empty `Vec`.
fn add_attributes(&self, _info: &AttributeInfo<'_>) -> Vec<String> {
vec![]
}

/// Process a source code comment.
fn process_comment(&self, _comment: &str) -> Option<String> {
None
Expand Down Expand Up @@ -167,6 +175,17 @@ pub struct DeriveInfo<'a> {
pub kind: TypeKind,
}

/// Relevant information about a type to which new attributes will be added using
/// [`ParseCallbacks::add_attributes`].
#[derive(Debug)]
#[non_exhaustive]
pub struct AttributeInfo<'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 {
Expand Down
Loading