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

Allow wrapper function generation for functional macros #2578

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@
for every input header file by default.
- Added the `CargoCallbacks::rerun_on_header_files` method to configure whether
a cargo-rerun line should be emitted for every input header file.
- Added `--macro-function` argument and corresponding `macro_function` builder function.
These allow defining macros for which a wrapper function will be generated,
which allows to call these macros from Rust.
## Changed
- The `--wrap-static-fns` feature was updated so function types that has no
argument use `void` as its sole argument.
Expand All @@ -270,6 +273,10 @@
`CargoCallbacks` constant was added to mitigate the breaking nature of this
change. This constant has been marked as deprecated and users will have to
use the new `CargoCallbacks::new` method in the future.
- Renamed `--wrap-static-fns-path` argument to `--wrapper-code-generation-path` and the
corresponding `wrap_static_fns_path` builder function to `wrapper_code_generation_path`.
- Renamed `--wrap-static-fns-suffix` argument to `--wrapper-function-suffix` and the
corresponding `wrap_static_fns_suffix` builder function to `wrapper_function_suffix`.
## Removed
## Fixed
- Allow compiling `bindgen-cli` with a static libclang.
Expand Down Expand Up @@ -328,7 +335,7 @@ This version was skipped due to some problems on the release workflow.
* The `--wrap-static-fns` option can now wrap `va_list` functions as variadic functions
with the experimental `ParseCallbacks::wrap_as_variadic_fn` method.
* Add target mappings for riscv32imc and riscv32imac.
* Add the `ParseCallbacks::field_visibility` method to modify field visibility.
* Add the `ParseCallbacks::field_visibility` method to modify field visibility.

## Changed

Expand All @@ -352,7 +359,7 @@ This version was skipped due to some problems on the release workflow.
* Compute visibility of bitfield unit based on actual field visibility: A
bitfield unit field and its related functions now have their visibility
determined based on the most private between the default visibility and the
actual visibility of the bitfields within the unit.
actual visibility of the bitfields within the unit.

## Removed
* Remove redundant Cargo features, which were all implicit:
Expand Down Expand Up @@ -408,7 +415,7 @@ This version was skipped due to some problems on the release workflow.
types. (#2463)
* The `Builder::rustfmt_bindings` methods and the `--no-rustfmt-bindings` flag
are now deprecated in favor of the formatter API. (#2453)

## Removed
* The following deprecated flags were removed: `--use-msvc-mangling`,
`--rustfmt-bindings` and `--size_t-is-usize`. (#2408)
Expand Down
98 changes: 86 additions & 12 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bindgen::callbacks::TypeKind;
use bindgen::function_types::{FunctionType, TypeKind as CTypeKind};
use bindgen::{
builder, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation,
FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle,
Expand Down Expand Up @@ -415,14 +416,17 @@ struct BindgenCommand {
/// Generate wrappers for `static` and `static inline` functions.
#[arg(long, requires = "experimental")]
wrap_static_fns: bool,
/// Sets the PATH for the source file that must be created due to the presence of `static` and
/// `static inline` functions.
/// Sets the path of the file where generated code for wrapper functions will be emitted.
#[arg(long, requires = "experimental", value_name = "PATH")]
wrap_static_fns_path: Option<PathBuf>,
/// Sets the SUFFIX added to the extern wrapper functions generated for `static` and `static
/// inline` functions.
wrapper_code_generation_path: Option<PathBuf>,
/// Sets the SUFFIX added to the wrapper functions generated for `static` and `static inline`
/// functions and functional macros.
#[arg(long, requires = "experimental", value_name = "SUFFIX")]
wrap_static_fns_suffix: Option<String>,
wrapper_function_suffix: Option<String>,
/// Create a wrapper function for the macro. The MACRO value must be of the shape
/// `[<return type>] <macro name>[(<comma separated list of arguments>)]`.
#[arg(long, requires = "experimental", value_name = "MACRO")]
macro_function: Option<Vec<String>>,
Abestanis marked this conversation as resolved.
Show resolved Hide resolved
/// Set the default VISIBILITY of fields, including bitfields and accessor methods for
/// bitfields. This flag is ignored if the `--respect-cxx-access-specs` flag is used.
#[arg(long, value_name = "VISIBILITY")]
Expand Down Expand Up @@ -559,8 +563,9 @@ where
with_derive_custom_enum,
with_derive_custom_union,
wrap_static_fns,
wrap_static_fns_path,
wrap_static_fns_suffix,
wrapper_code_generation_path,
wrapper_function_suffix,
macro_function,
default_visibility,
emit_diagnostics,
generate_shell_completions,
Expand Down Expand Up @@ -1102,12 +1107,56 @@ where
builder = builder.wrap_static_fns(true);
}

if let Some(path) = wrap_static_fns_path {
builder = builder.wrap_static_fns_path(path);
if let Some(path) = wrapper_code_generation_path {
builder = builder.wrapper_code_generation_path(path);
}

if let Some(suffix) = wrap_static_fns_suffix {
builder = builder.wrap_static_fns_suffix(suffix);
if let Some(suffix) = wrapper_function_suffix {
builder = builder.wrapper_function_suffix(suffix);
}

if let Some(macro_functions) = macro_function {
for macro_definition in macro_functions.into_iter() {
let (left_side, argument_types) = if !macro_definition
.ends_with(')')
{
(macro_definition.as_str(), Vec::new())
} else {
let (left_side, arguments) =
macro_definition.split_once('(').expect("Invalid function macro definition: No '(' for ')' at end found");
let arguments = &arguments[..arguments.len() - 1];
if arguments.trim().is_empty() {
// The empty argument list case `()`.
(left_side, Vec::new())
} else {
(
left_side,
arguments
.split(',')
.map(|argument| {
parse_c_type(argument).unwrap_or_else(|err| {
panic!(
"Invalid function macro argument type: {}",
err
)
})
})
.collect(),
)
}
};
let parts =
left_side.split_ascii_whitespace().collect::<Vec<&str>>();
let (return_type, name) = match parts.as_slice() {
[name] => (CTypeKind::Void, name),
[return_type, name] => (parse_c_type(return_type).unwrap_or_else(|err| panic!("Invalid function macro return type: {}", err)), name),
_ => panic!("Invalid function macro name: Name must not contain whitespaces"),
};
builder = builder.macro_function(
*name,
FunctionType::from(return_type, argument_types),
)
}
}

if let Some(visibility) = default_visibility {
Expand All @@ -1120,3 +1169,28 @@ where

Ok((builder, output, verbose))
}

/// Parse a [`CTypeKind`] from the given command line `input`.
fn parse_c_type(input: &str) -> Result<CTypeKind, String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really necessary? It might sound lazy but to be honest it might be easier to just let the user type whatever they want as a type and propagate those strings until they are written to the wrapper code file. This also gives you the "advantage" of supporting any type the user wants as long as it is defined in the headers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we need do do some kind of parsing because we need to generate not only a function definition but also the function body where we call the macro. I don't believe there is a nice way to create the macro invocation without separating the argument types from the definition and removing the return type, but maybe I'm missing something?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am actually a bit stuck on this. I am trying to support calling these macros with C types, which can't be referenced from build scripts:

typedef struct {
    int inner;
} my_type;

#define ACCESS_INNER(value) value.inner

I want to be able to specify that the ACCESS_INNER macro takes a my_type. To do that I was trying to switch everything over to just accept strings, but in Function::parse I need to generate a Function object which eventually needs a TypeKind for the return type and the argument types.

So my question is, is there a way to construct a Function that doesn't require me to parse the types, so I can just pass the strings through?

let input = input.trim();
Ok(match input {
"void" | "()" => CTypeKind::Void,
"bool" => CTypeKind::Bool,
"char" => CTypeKind::Char,
"int" => CTypeKind::Int,
"long" => CTypeKind::Long,
"u8" | "uint8_t" => CTypeKind::U8,
"u16" | "uint16_t" => CTypeKind::U16,
"u32" | "uint32_t" => CTypeKind::U32,
"u64" | "uint64_t" => CTypeKind::U64,
"u128" | "uint128_t" => CTypeKind::U128,
"i8" | "int8_t" => CTypeKind::I8,
"i16" | "int16_t" => CTypeKind::I16,
"i32" | "int32_t" => CTypeKind::I32,
"i64" | "int64_t" => CTypeKind::I64,
"i128" | "int128_t" => CTypeKind::I128,
"f32" | "float" => CTypeKind::F32,
"f64" | "double" => CTypeKind::F64,
_ => return Err(format!("Unsupported C type \"{input}\"")),
})
}
2 changes: 1 addition & 1 deletion bindgen-integration/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ fn setup_wrap_static_fns_test() {
.parse_callbacks(Box::new(CargoCallbacks))
.parse_callbacks(Box::new(WrappedVaListCallback))
.wrap_static_fns(true)
.wrap_static_fns_path(
.wrapper_code_generation_path(
out_path.join("wrap_static_fns").display().to_string(),
)
.clang_arg("-DUSE_VA_HEADER")
Expand Down
27 changes: 27 additions & 0 deletions bindgen-tests/tests/expectations/tests/function_macros.rs

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

11 changes: 11 additions & 0 deletions bindgen-tests/tests/expectations/tests/generated/function_macros.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <stdint.h>
#include <stdbool.h>
#include "tests/headers/function_macros.h"

// Macro function wrappers

void SIMPLE__extern(void) { SIMPLE; }
void INDIRECT_SIMPLE__extern(void) { INDIRECT_SIMPLE; }
float COMPLEX__extern(uint32_t x) { return COMPLEX(x); }
float INDIRECT_COMPLEX__extern(uint32_t x) { return INDIRECT_COMPLEX(x); }
float CONDITIONAL_COMPLEX__extern(bool condition, uint32_t x) { return CONDITIONAL_COMPLEX(condition, x); }
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <stdint.h>
#include <stdbool.h>
#include "tests/headers/wrap-static-fns.h"

// Static wrappers
Expand Down
15 changes: 15 additions & 0 deletions bindgen-tests/tests/headers/function_macros.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// bindgen-flags: --experimental --macro-function "SIMPLE" --macro-function "INDIRECT_SIMPLE" --macro-function "f32 COMPLEX(u32)" --macro-function "f32 INDIRECT_COMPLEX(u32)" --macro-function "f32 CONDITIONAL_COMPLEX(bool, u32)"

void simple(void);

#define SIMPLE simple()
#define INDIRECT_SIMPLE SIMPLE

float complex(int);

#define COMPLEX(x) complex(x)
#define INDIRECT_COMPLEX(x) COMPLEX(x)

#define CONDITIONAL_COMPLEX(condition, x) ((condition) ? COMPLEX(x) : 0)

#define SIMPLE_NOT_CONFIGURED simple()
51 changes: 49 additions & 2 deletions bindgen-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
extern crate env_logger;
extern crate shlex;

use bindgen::{clang_version, Builder};
use bindgen::{clang_version, function_types::FunctionType, Builder};
use owo_colors::{OwoColorize, Style};
use similar::{ChangeTag, TextDiff};
use std::env;
Expand Down Expand Up @@ -654,7 +654,7 @@

let flags_quoted: Vec<String> = command_line_flags
.iter()
.map(|x| format!("{}", shlex::quote(x)))

Check warning on line 657 in bindgen-tests/tests/tests.rs

View workflow job for this annotation

GitHub Actions / rustfmt-clippy

use of deprecated function `shlex::quote`: replace with `try_quote(str)?` to avoid nul byte danger
.collect();
let flags_str = flags_quoted.join(" ");
println!("{}", flags_str);
Expand Down Expand Up @@ -690,7 +690,7 @@
let _bindings = Builder::default()
.header("tests/headers/wrap-static-fns.h")
.wrap_static_fns(true)
.wrap_static_fns_path(generated_path.display().to_string())
.wrapper_code_generation_path(generated_path.display().to_string())
.parse_callbacks(Box::new(parse_callbacks::WrapAsVariadicFn))
.generate()
.expect("Failed to generate bindings");
Expand All @@ -711,3 +711,50 @@
.unwrap();
}
}

#[test]
fn test_function_macros() {
if env::current_dir().unwrap().ends_with("rust-bindgen") {
env::set_current_dir(Path::new("bindgen-tests")).unwrap();
}
if env::var("OUT_DIR").is_err() {
env::set_var("OUT_DIR", PathBuf::from("target/out"));
}
let expect_path = PathBuf::from("tests/expectations/tests/generated")
.join("function_macros");
println!("In path is ::: {}", expect_path.display());

let generated_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("function_macros");
println!("Out path is ::: {}", generated_path.display());

let _bindings = Builder::default()
.header("tests/headers/function_macros.h")
.macro_function("SIMPLE", FunctionType::new::<(), ()>())
.macro_function("INDIRECT_SIMPLE", FunctionType::new::<(), ()>())
.macro_function("COMPLEX", FunctionType::new::<f32, u32>())
.macro_function("INDIRECT_COMPLEX", FunctionType::new::<f32, u32>())
.macro_function(
"CONDITIONAL_COMPLEX",
FunctionType::new::<f32, (bool, u32)>(),
)
.wrapper_code_generation_path(generated_path.display().to_string())
.generate()
.expect("Failed to generate bindings");

let expected_c = fs::read_to_string(expect_path.with_extension("c"))
.expect("Could not read generated function_macros.c");

let actual_c = fs::read_to_string(generated_path.with_extension("c"))
.expect("Could not read actual function_macros.c");

if expected_c != actual_c {
error_diff_mismatch(
&actual_c,
&expected_c,
None,
&expect_path.with_extension("c"),
)
.unwrap();
}
}
Loading
Loading