Skip to content

Commit

Permalink
Add support for custom attributes
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Kröning <[email protected]>
  • Loading branch information
mkroening committed Aug 31, 2024
1 parent 7600bf8 commit dc6c119
Show file tree
Hide file tree
Showing 15 changed files with 353 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,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
4 changes: 4 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
2 changes: 2 additions & 0 deletions bindgen-integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ build = "build.rs"
[build-dependencies]
bindgen = { path = "../bindgen", features = ["experimental"] }
cc = "1.0"
proc-macro2 = { version = "1", default-features = false }
quote = { version = "1", default-features = false }

[features]
static = ["bindgen/static"]
Expand Down
16 changes: 16 additions & 0 deletions bindgen-integration/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
extern crate bindgen;
extern crate proc_macro2;
extern crate quote;

use bindgen::callbacks::{
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
};
use bindgen::{Builder, EnumVariation, Formatter};
use proc_macro2::TokenStream;
use quote::quote;
use std::collections::HashSet;
use std::env;
use std::path::PathBuf;
Expand Down Expand Up @@ -133,6 +137,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
45 changes: 45 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;
};
Loading

0 comments on commit dc6c119

Please sign in to comment.