Skip to content

Commit fd1409c

Browse files
committed
Add support for custom attributes
Signed-off-by: Martin Kröning <[email protected]>
1 parent 7600bf8 commit fd1409c

File tree

15 files changed

+351
-1
lines changed

15 files changed

+351
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
- Add option to use DST structs for flexible arrays (--flexarray-dst, #2772).
209209
- Add option to dynamically load variables (#2812).
210210
- Add option in CLI to use rustified non-exhaustive enums (--rustified-non-exhaustive-enum, #2847).
211+
- Add support for custom attributes (--with-attribute-custom, #2866)
211212
## Changed
212213
- Remove which and lazy-static dependencies (#2809, #2817).
213214
- Generate compile-time layout tests (#2787).

Cargo.lock

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-cli/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ clap = { version = "4", features = ["derive"] }
2525
clap_complete = "4"
2626
env_logger = { version = "0.10.0", optional = true }
2727
log = { version = "0.4", optional = true }
28+
proc-macro2 = { version = "1", default-features = false }
2829
shlex = "1"
2930

3031
[features]

bindgen-cli/options.rs

+131
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ use bindgen::{
66
};
77
use clap::error::{Error, ErrorKind};
88
use clap::{CommandFactory, Parser};
9+
use proc_macro2::TokenStream;
910
use std::fs::File;
1011
use std::io;
1112
use std::path::{Path, PathBuf};
1213
use std::process::exit;
14+
use std::str::FromStr;
1315

1416
fn rust_target_help() -> String {
1517
format!(
@@ -87,6 +89,43 @@ fn parse_custom_derive(
8789
Ok((derives, regex.to_owned()))
8890
}
8991

92+
fn parse_custom_attribute(
93+
custom_attribute: &str,
94+
) -> Result<(Vec<String>, String), Error> {
95+
let mut brace_level = 0;
96+
let (regex, attributes) = custom_attribute
97+
.rsplit_once(|c| {
98+
match c {
99+
']' => brace_level += 1,
100+
'[' => brace_level -= 1,
101+
_ => {}
102+
}
103+
c == '=' && brace_level == 0
104+
})
105+
.ok_or_else(|| Error::raw(ErrorKind::InvalidValue, "Missing `=`"))?;
106+
107+
let mut brace_level = 0;
108+
let attributes = attributes
109+
.split(|c| {
110+
match c {
111+
']' => brace_level += 1,
112+
'[' => brace_level -= 1,
113+
_ => {}
114+
}
115+
c == ',' && brace_level == 0
116+
})
117+
.map(|s| s.to_owned())
118+
.collect::<Vec<_>>();
119+
120+
for attribute in &attributes {
121+
if let Err(err) = TokenStream::from_str(attribute) {
122+
return Err(Error::raw(ErrorKind::InvalidValue, err));
123+
}
124+
}
125+
126+
Ok((attributes, regex.to_owned()))
127+
}
128+
90129
#[derive(Parser, Debug)]
91130
#[clap(
92131
about = "Generates Rust bindings from C/C++ headers.",
@@ -424,6 +463,18 @@ struct BindgenCommand {
424463
/// 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.
425464
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_derive)]
426465
with_derive_custom_union: Vec<(Vec<String>, String)>,
466+
/// 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.
467+
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
468+
with_attribute_custom: Vec<(Vec<String>, String)>,
469+
/// 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.
470+
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
471+
with_attribute_custom_struct: Vec<(Vec<String>, String)>,
472+
/// 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.
473+
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
474+
with_attribute_custom_enum: Vec<(Vec<String>, String)>,
475+
/// 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.
476+
#[arg(long, value_name = "CUSTOM", value_parser = parse_custom_attribute)]
477+
with_attribute_custom_union: Vec<(Vec<String>, String)>,
427478
/// Generate wrappers for `static` and `static inline` functions.
428479
#[arg(long, requires = "experimental")]
429480
wrap_static_fns: bool,
@@ -574,6 +625,10 @@ where
574625
with_derive_custom_struct,
575626
with_derive_custom_enum,
576627
with_derive_custom_union,
628+
with_attribute_custom,
629+
with_attribute_custom_struct,
630+
with_attribute_custom_enum,
631+
with_attribute_custom_union,
577632
wrap_static_fns,
578633
wrap_static_fns_path,
579634
wrap_static_fns_suffix,
@@ -1130,6 +1185,82 @@ where
11301185
}
11311186
}
11321187

1188+
#[derive(Debug)]
1189+
struct CustomAttributeCallback {
1190+
attributes: Vec<String>,
1191+
kind: Option<TypeKind>,
1192+
regex_set: bindgen::RegexSet,
1193+
}
1194+
1195+
impl bindgen::callbacks::ParseCallbacks for CustomAttributeCallback {
1196+
fn cli_args(&self) -> Vec<String> {
1197+
let mut args = vec![];
1198+
1199+
let flag = match &self.kind {
1200+
None => "--with-attribute-custom",
1201+
Some(TypeKind::Struct) => "--with-attribute-custom-struct",
1202+
Some(TypeKind::Enum) => "--with-attribute-custom-enum",
1203+
Some(TypeKind::Union) => "--with-attribute-custom-union",
1204+
};
1205+
1206+
let attributes = self.attributes.join(",");
1207+
1208+
for item in self.regex_set.get_items() {
1209+
args.extend_from_slice(&[
1210+
flag.to_owned(),
1211+
format!("{}={}", item, attributes),
1212+
]);
1213+
}
1214+
1215+
args
1216+
}
1217+
1218+
fn add_attributes(
1219+
&self,
1220+
info: &bindgen::callbacks::AttributeInfo<'_>,
1221+
) -> Vec<String> {
1222+
if self.kind.map(|kind| kind == info.kind).unwrap_or(true) &&
1223+
self.regex_set.matches(info.name)
1224+
{
1225+
return self.attributes.clone();
1226+
}
1227+
vec![]
1228+
}
1229+
}
1230+
1231+
for (custom_attributes, kind, name) in [
1232+
(with_attribute_custom, None, "--with-attribute-custom"),
1233+
(
1234+
with_attribute_custom_struct,
1235+
Some(TypeKind::Struct),
1236+
"--with-attribute-custom-struct",
1237+
),
1238+
(
1239+
with_attribute_custom_enum,
1240+
Some(TypeKind::Enum),
1241+
"--with-attribute-custom-enum",
1242+
),
1243+
(
1244+
with_attribute_custom_union,
1245+
Some(TypeKind::Union),
1246+
"--with-attribute-custom-union",
1247+
),
1248+
] {
1249+
let name = emit_diagnostics.then_some(name);
1250+
for (attributes, regex) in custom_attributes {
1251+
let mut regex_set = RegexSet::new();
1252+
regex_set.insert(regex);
1253+
regex_set.build_with_diagnostics(false, name);
1254+
1255+
builder =
1256+
builder.parse_callbacks(Box::new(CustomAttributeCallback {
1257+
attributes,
1258+
kind,
1259+
regex_set,
1260+
}));
1261+
}
1262+
}
1263+
11331264
if wrap_static_fns {
11341265
builder = builder.wrap_static_fns(true);
11351266
}

bindgen-integration/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ build = "build.rs"
99
[build-dependencies]
1010
bindgen = { path = "../bindgen", features = ["experimental"] }
1111
cc = "1.0"
12+
proc-macro2 = { version = "1", default-features = false }
13+
quote = { version = "1", default-features = false }
1214

1315
[features]
1416
static = ["bindgen/static"]

bindgen-integration/build.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
extern crate bindgen;
2+
extern crate proc_macro2;
3+
extern crate quote;
24

35
use bindgen::callbacks::{
46
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
@@ -133,6 +135,18 @@ impl ParseCallbacks for MacroCallback {
133135
vec![]
134136
}
135137
}
138+
139+
// Test the "custom attributes" capability.
140+
fn add_attributes(
141+
&self,
142+
info: &bindgen::callbacks::AttributeInfo<'_>,
143+
) -> Vec<String> {
144+
if info.name == "Test" {
145+
vec!["#[cfg_attr(test, derive(PartialOrd))]".into()]
146+
} else {
147+
vec![]
148+
}
149+
}
136150
}
137151

138152
impl Drop for MacroCallback {

bindgen-integration/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,15 @@ fn test_custom_derive() {
297297
assert!(!(test1 > test2));
298298
}
299299

300+
#[test]
301+
fn test_custom_attributes() {
302+
// The `add_attributes` callback should have added `#[cfg_attr(test, derive(PartialOrd))])`
303+
// to the `Test` struct. If it didn't, this will fail to compile.
304+
let test1 = unsafe { bindings::Test::new(5) };
305+
let test2 = unsafe { bindings::Test::new(6) };
306+
assert!(test1 < test2);
307+
}
308+
300309
#[test]
301310
fn test_wrap_static_fns() {
302311
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090

bindgen-tests/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ clap = { version = "4", features = ["derive"] }
1010
clap_complete = "4"
1111
shlex = "1"
1212
prettyplease = { version = "0.2.7", features = ["verbatim"] }
13+
proc-macro2 = { version = "1", default-features = false }
1314
syn = { version = "2.0" }
1415
tempfile = "3"
1516
similar = { version = "2.2.1", features = ["inline"] }

bindgen-tests/tests/expectations/tests/attribute-custom-cli.rs

+45
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-tests/tests/expectations/tests/attribute-custom.rs

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// 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)]"
2+
struct foo_struct {
3+
int inner;
4+
};
5+
enum foo_enum {
6+
inner = 0
7+
};
8+
union foo_union {
9+
int fst;
10+
float snd;
11+
};
12+
struct non_matching {
13+
int inner;
14+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// bindgen-flags: --no-derive-debug --no-derive-copy --no-derive-default --default-enum-style rust --no-layout-tests
2+
3+
/** <div rustbindgen attribute="#[derive(Debug)]"></div> */
4+
struct my_type;
5+
6+
/** <div rustbindgen attribute="#[derive(Clone)]"></div> */
7+
struct my_type;
8+
9+
struct my_type {
10+
int a;
11+
};
12+
13+
/**
14+
* <div rustbindgen attribute="#[derive(Debug)]"></div>
15+
* <div rustbindgen attribute="#[derive(Clone)]"></div>
16+
*/
17+
struct my_type2;
18+
19+
struct my_type2 {
20+
unsigned a;
21+
};
22+
23+
/**
24+
* <div rustbindgen attribute="#[derive(Debug)]" attribute="#[derive(Clone)]"></div>
25+
*/
26+
struct my_type3 {
27+
unsigned long a;
28+
};

0 commit comments

Comments
 (0)