Skip to content

Commit 9ffb309

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

File tree

8 files changed

+215
-1
lines changed

8 files changed

+215
-1
lines changed

Cargo.lock

+3
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

+134
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,85 @@ 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<TokenStream> {
1222+
if self.kind.map(|kind| kind == info.kind).unwrap_or(true) &&
1223+
self.regex_set.matches(info.name)
1224+
{
1225+
return self
1226+
.attributes
1227+
.iter()
1228+
.map(|s| s.parse().unwrap())
1229+
.collect();
1230+
}
1231+
vec![]
1232+
}
1233+
}
1234+
1235+
for (custom_attributes, kind, name) in [
1236+
(with_attribute_custom, None, "--with-attribute-custom"),
1237+
(
1238+
with_attribute_custom_struct,
1239+
Some(TypeKind::Struct),
1240+
"--with-attribute-custom-struct",
1241+
),
1242+
(
1243+
with_attribute_custom_enum,
1244+
Some(TypeKind::Enum),
1245+
"--with-attribute-custom-enum",
1246+
),
1247+
(
1248+
with_attribute_custom_union,
1249+
Some(TypeKind::Union),
1250+
"--with-attribute-custom-union",
1251+
),
1252+
] {
1253+
let name = emit_diagnostics.then_some(name);
1254+
for (attributes, regex) in custom_attributes {
1255+
let mut regex_set = RegexSet::new();
1256+
regex_set.insert(regex);
1257+
regex_set.build_with_diagnostics(false, name);
1258+
1259+
builder = builder.parse_callbacks(Box::new(CustomAttributeCallback {
1260+
attributes,
1261+
kind,
1262+
regex_set,
1263+
}));
1264+
}
1265+
}
1266+
11331267
if wrap_static_fns {
11341268
builder = builder.wrap_static_fns(true);
11351269
}

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

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

35
use bindgen::callbacks::{
46
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
57
};
68
use bindgen::{Builder, EnumVariation, Formatter};
9+
use proc_macro2::TokenStream;
10+
use quote::quote;
711
use std::collections::HashSet;
812
use std::env;
913
use std::path::PathBuf;
@@ -133,6 +137,15 @@ impl ParseCallbacks for MacroCallback {
133137
vec![]
134138
}
135139
}
140+
141+
// Test the "custom derives" capability.
142+
fn add_attributes(&self, info: &bindgen::callbacks::AttributeInfo<'_>) -> Vec<TokenStream> {
143+
if info.name == "Test" {
144+
vec![quote!(#[cfg_attr(test, derive(PartialOrd))])]
145+
} else {
146+
vec![]
147+
}
148+
}
136149
}
137150

138151
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/callbacks.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! A public API for more fine-grained customization of bindgen behavior.
22
3+
use proc_macro2::TokenStream;
4+
35
pub use crate::ir::analysis::DeriveTrait;
46
pub use crate::ir::derive::CanDerive as ImplementsTrait;
57
pub use crate::ir::enum_ty::{EnumVariantCustomBehavior, EnumVariantValue};
@@ -129,6 +131,14 @@ pub trait ParseCallbacks: fmt::Debug {
129131
vec![]
130132
}
131133

134+
/// Provide a list of custom attributes.
135+
///
136+
/// If no additional attributes are wanted, this function should return an
137+
/// empty `Vec`.
138+
fn add_attributes(&self, _info: &AttributeInfo<'_>) -> Vec<TokenStream> {
139+
vec![]
140+
}
141+
132142
/// Process a source code comment.
133143
fn process_comment(&self, _comment: &str) -> Option<String> {
134144
None
@@ -167,6 +177,17 @@ pub struct DeriveInfo<'a> {
167177
pub kind: TypeKind,
168178
}
169179

180+
/// Relevant information about a type to which new attributes will be added using
181+
/// [`ParseCallbacks::add_attributes`].
182+
#[derive(Debug)]
183+
#[non_exhaustive]
184+
pub struct AttributeInfo<'a> {
185+
/// The name of the type.
186+
pub name: &'a str,
187+
/// The kind of the type.
188+
pub kind: TypeKind,
189+
}
190+
170191
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171192
/// The kind of the current type.
172193
pub enum TypeKind {

bindgen/codegen/mod.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use self::struct_layout::StructLayoutTracker;
2020

2121
use super::BindgenOptions;
2222

23-
use crate::callbacks::{DeriveInfo, FieldInfo, TypeKind as DeriveTypeKind};
23+
use crate::callbacks::{AttributeInfo, DeriveInfo, FieldInfo, TypeKind as DeriveTypeKind};
2424
use crate::codegen::error::Error;
2525
use crate::ir::analysis::{HasVtable, Sizedness};
2626
use crate::ir::annotations::{
@@ -1047,6 +1047,15 @@ impl CodeGenerator for Type {
10471047
.extend(custom_derives.iter().map(|s| s.as_str()));
10481048
attributes.push(attributes::derives(&derives));
10491049

1050+
let custom_attributes =
1051+
ctx.options().all_callbacks(|cb| {
1052+
cb.add_attributes(&AttributeInfo {
1053+
name: &name,
1054+
kind: DeriveTypeKind::Struct,
1055+
})
1056+
});
1057+
attributes.extend(custom_attributes);
1058+
10501059
quote! {
10511060
#( #attributes )*
10521061
pub struct #rust_name
@@ -2377,6 +2386,18 @@ impl CodeGenerator for CompInfo {
23772386
attributes.push(attributes::derives(&derives))
23782387
}
23792388

2389+
let custom_attributes = ctx.options().all_callbacks(|cb| {
2390+
cb.add_attributes(&AttributeInfo {
2391+
name: &canonical_name,
2392+
kind: if is_rust_union {
2393+
DeriveTypeKind::Union
2394+
} else {
2395+
DeriveTypeKind::Struct
2396+
},
2397+
})
2398+
});
2399+
attributes.extend(custom_attributes);
2400+
23802401
if item.must_use(ctx) {
23812402
attributes.push(attributes::must_use());
23822403
}
@@ -3568,6 +3589,16 @@ impl CodeGenerator for Enum {
35683589
// In most cases this will be a no-op, since custom_derives will be empty.
35693590
derives.extend(custom_derives.iter().map(|s| s.as_str()));
35703591

3592+
// The custom attribute callback may return a list of attributes;
3593+
// add them to the end of the list.
3594+
let custom_attributes = ctx.options().all_callbacks(|cb| {
3595+
cb.add_attributes(&AttributeInfo {
3596+
name: &name,
3597+
kind: DeriveTypeKind::Enum,
3598+
})
3599+
});
3600+
attrs.extend(custom_attributes);
3601+
35713602
attrs.push(attributes::derives(&derives));
35723603
}
35733604

0 commit comments

Comments
 (0)