From 9ffb309d9b35e0b3ff4f13a33b28ccb184110e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Mon, 8 Jul 2024 16:47:07 +0200 Subject: [PATCH] Add support for custom attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Kröning --- Cargo.lock | 3 + bindgen-cli/Cargo.toml | 1 + bindgen-cli/options.rs | 134 +++++++++++++++++++++++++++++++++ bindgen-integration/Cargo.toml | 2 + bindgen-integration/build.rs | 13 ++++ bindgen-integration/src/lib.rs | 9 +++ bindgen/callbacks.rs | 21 ++++++ bindgen/codegen/mod.rs | 33 +++++++- 8 files changed, 215 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ace73d6b07..4c810dfa65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,7 @@ dependencies = [ "clap_complete", "env_logger 0.10.0", "log", + "proc-macro2", "shlex", ] @@ -58,6 +59,8 @@ version = "0.1.0" dependencies = [ "bindgen", "cc", + "proc-macro2", + "quote", ] [[package]] diff --git a/bindgen-cli/Cargo.toml b/bindgen-cli/Cargo.toml index 4f8e182fd7..a879359eaa 100644 --- a/bindgen-cli/Cargo.toml +++ b/bindgen-cli/Cargo.toml @@ -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] diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs index cd5e9bb127..d2cd04c95f 100644 --- a/bindgen-cli/options.rs +++ b/bindgen-cli/options.rs @@ -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!( @@ -87,6 +89,43 @@ fn parse_custom_derive( Ok((derives, regex.to_owned())) } +fn parse_custom_attribute( + custom_attribute: &str, +) -> Result<(Vec, 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::>(); + + 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.", @@ -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)>, + /// 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)>, + /// 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)>, + /// 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)>, + /// 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)>, /// Generate wrappers for `static` and `static inline` functions. #[arg(long, requires = "experimental")] wrap_static_fns: bool, @@ -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, @@ -1130,6 +1185,85 @@ where } } + #[derive(Debug)] + struct CustomAttributeCallback { + attributes: Vec, + kind: Option, + regex_set: bindgen::RegexSet, + } + + impl bindgen::callbacks::ParseCallbacks for CustomAttributeCallback { + fn cli_args(&self) -> Vec { + 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 { + if self.kind.map(|kind| kind == info.kind).unwrap_or(true) && + self.regex_set.matches(info.name) + { + return self + .attributes + .iter() + .map(|s| s.parse().unwrap()) + .collect(); + } + 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); } diff --git a/bindgen-integration/Cargo.toml b/bindgen-integration/Cargo.toml index ccdd1467df..4c7271381d 100644 --- a/bindgen-integration/Cargo.toml +++ b/bindgen-integration/Cargo.toml @@ -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"] diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index 6b06c91bc3..ca1087f073 100644 --- a/bindgen-integration/build.rs +++ b/bindgen-integration/build.rs @@ -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; @@ -133,6 +137,15 @@ impl ParseCallbacks for MacroCallback { vec![] } } + + // Test the "custom derives" capability. + fn add_attributes(&self, info: &bindgen::callbacks::AttributeInfo<'_>) -> Vec { + if info.name == "Test" { + vec![quote!(#[cfg_attr(test, derive(PartialOrd))])] + } else { + vec![] + } + } } impl Drop for MacroCallback { diff --git a/bindgen-integration/src/lib.rs b/bindgen-integration/src/lib.rs index c37055ee7d..48cfe092d2 100755 --- a/bindgen-integration/src/lib.rs +++ b/bindgen-integration/src/lib.rs @@ -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 diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 0f16c4c0bf..09bbbfc79f 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -1,5 +1,7 @@ //! A public API for more fine-grained customization of bindgen behavior. +use proc_macro2::TokenStream; + pub use crate::ir::analysis::DeriveTrait; pub use crate::ir::derive::CanDerive as ImplementsTrait; pub use crate::ir::enum_ty::{EnumVariantCustomBehavior, EnumVariantValue}; @@ -129,6 +131,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 { + vec![] + } + /// Process a source code comment. fn process_comment(&self, _comment: &str) -> Option { None @@ -167,6 +177,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 { diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index e2aaee9820..a0efa10235 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -20,7 +20,7 @@ use self::struct_layout::StructLayoutTracker; use super::BindgenOptions; -use crate::callbacks::{DeriveInfo, FieldInfo, TypeKind as DeriveTypeKind}; +use crate::callbacks::{AttributeInfo, DeriveInfo, FieldInfo, TypeKind as DeriveTypeKind}; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; use crate::ir::annotations::{ @@ -1047,6 +1047,15 @@ impl CodeGenerator for Type { .extend(custom_derives.iter().map(|s| s.as_str())); attributes.push(attributes::derives(&derives)); + let custom_attributes = + ctx.options().all_callbacks(|cb| { + cb.add_attributes(&AttributeInfo { + name: &name, + kind: DeriveTypeKind::Struct, + }) + }); + attributes.extend(custom_attributes); + quote! { #( #attributes )* pub struct #rust_name @@ -2377,6 +2386,18 @@ impl CodeGenerator for CompInfo { attributes.push(attributes::derives(&derives)) } + let custom_attributes = ctx.options().all_callbacks(|cb| { + cb.add_attributes(&AttributeInfo { + name: &canonical_name, + kind: if is_rust_union { + DeriveTypeKind::Union + } else { + DeriveTypeKind::Struct + }, + }) + }); + attributes.extend(custom_attributes); + if item.must_use(ctx) { attributes.push(attributes::must_use()); } @@ -3568,6 +3589,16 @@ impl CodeGenerator for 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())); + // The custom attribute callback may return a list of attributes; + // add them to the end of the list. + let custom_attributes = ctx.options().all_callbacks(|cb| { + cb.add_attributes(&AttributeInfo { + name: &name, + kind: DeriveTypeKind::Enum, + }) + }); + attrs.extend(custom_attributes); + attrs.push(attributes::derives(&derives)); }