mirror of
https://gitee.com/openharmony/third_party_rust_bindgen
synced 2024-12-02 20:47:18 +00:00
2be14a3345
* Generate extern wrappers for inlined functions If bindgen finds an inlined function and the `--generate-extern-functions` options is enabled, then: - It will generate two new source and header files with external functions that wrap the inlined functions. - Rerun `Bindings::generate` using the new header file to include these wrappers in the generated bindings. The following additional options were added: - `--extern-function-suffix=<suffix>`: Adds <suffix> to the name of each external wrapper function (`__extern` is used by default). - `--extern-functions-file-name=<name>`: Uses <name> as the file name for the header and source files (`extern` is used by default). - `--extern-function-directory=<dir>`: Creates the source and header files inside <dir> (`/tmp/bindgen` is used by default). The C code serialization is experimental and only supports a very limited set of C functions. Fixes #1090. --------- Co-authored-by: Amanjeev Sethi <aj@amanjeev.com>
279 lines
9.3 KiB
Rust
279 lines
9.3 KiB
Rust
extern crate bindgen;
|
|
extern crate cc;
|
|
|
|
use bindgen::callbacks::{
|
|
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
|
|
};
|
|
use bindgen::{Builder, CargoCallbacks, EnumVariation};
|
|
use std::collections::HashSet;
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::sync::{Arc, Mutex, RwLock};
|
|
|
|
#[derive(Debug)]
|
|
struct MacroCallback {
|
|
macros: Arc<RwLock<HashSet<String>>>,
|
|
seen_hellos: Mutex<u32>,
|
|
seen_funcs: Mutex<u32>,
|
|
}
|
|
|
|
impl ParseCallbacks for MacroCallback {
|
|
fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior {
|
|
self.macros.write().unwrap().insert(name.into());
|
|
|
|
if name == "MY_ANNOYING_MACRO" {
|
|
return MacroParsingBehavior::Ignore;
|
|
}
|
|
|
|
MacroParsingBehavior::Default
|
|
}
|
|
|
|
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
|
|
match name {
|
|
"TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
|
|
name: "crate::MacroInteger",
|
|
is_signed: true,
|
|
}),
|
|
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn str_macro(&self, name: &str, value: &[u8]) {
|
|
match name {
|
|
"TESTMACRO_STRING_EXPR" => {
|
|
assert_eq!(value, b"string");
|
|
*self.seen_hellos.lock().unwrap() += 1;
|
|
}
|
|
"TESTMACRO_STRING_EXPANDED" |
|
|
"TESTMACRO_STRING" |
|
|
"TESTMACRO_INTEGER" => {
|
|
// The integer test macro is, actually, not expected to show up here at all -- but
|
|
// should produce an error if it does.
|
|
assert_eq!(
|
|
value, b"Hello Preprocessor!",
|
|
"str_macro handle received unexpected value"
|
|
);
|
|
*self.seen_hellos.lock().unwrap() += 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn func_macro(&self, name: &str, value: &[&[u8]]) {
|
|
match name {
|
|
"TESTMACRO_NONFUNCTIONAL" => {
|
|
panic!("func_macro was called for a non-functional macro");
|
|
}
|
|
"TESTMACRO_FUNCTIONAL_NONEMPTY(TESTMACRO_INTEGER)" => {
|
|
// Spaces are inserted into the right-hand side of a functional
|
|
// macro during reconstruction from the tokenization. This might
|
|
// change in the future, but it is safe by the definition of a
|
|
// token in C, whereas leaving the spaces out could change
|
|
// tokenization.
|
|
assert_eq!(value, &[b"-" as &[u8], b"TESTMACRO_INTEGER"]);
|
|
*self.seen_funcs.lock().unwrap() += 1;
|
|
}
|
|
"TESTMACRO_FUNCTIONAL_EMPTY(TESTMACRO_INTEGER)" => {
|
|
assert_eq!(value, &[] as &[&[u8]]);
|
|
*self.seen_funcs.lock().unwrap() += 1;
|
|
}
|
|
"TESTMACRO_FUNCTIONAL_TOKENIZED(a,b,c,d,e)" => {
|
|
assert_eq!(
|
|
value,
|
|
&[b"a" as &[u8], b"/", b"b", b"c", b"d", b"##", b"e"]
|
|
);
|
|
*self.seen_funcs.lock().unwrap() += 1;
|
|
}
|
|
"TESTMACRO_FUNCTIONAL_SPLIT(a,b)" => {
|
|
assert_eq!(value, &[b"b", b",", b"a"]);
|
|
*self.seen_funcs.lock().unwrap() += 1;
|
|
}
|
|
"TESTMACRO_STRING_FUNC_NON_UTF8(x)" => {
|
|
assert_eq!(
|
|
value,
|
|
&[b"(" as &[u8], b"x", b"\"\xff\xff\"", b")"]
|
|
);
|
|
*self.seen_funcs.lock().unwrap() += 1;
|
|
}
|
|
_ => {
|
|
// The system might provide lots of functional macros.
|
|
// Ensure we did not miss handling one that we meant to handle.
|
|
assert!(!name.starts_with("TESTMACRO_"), "name = {}", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn item_name(&self, original_item_name: &str) -> Option<String> {
|
|
if original_item_name.starts_with("my_prefixed_") {
|
|
Some(
|
|
original_item_name
|
|
.trim_start_matches("my_prefixed_")
|
|
.to_string(),
|
|
)
|
|
} else if original_item_name.starts_with("MY_PREFIXED_") {
|
|
Some(
|
|
original_item_name
|
|
.trim_start_matches("MY_PREFIXED_")
|
|
.to_string(),
|
|
)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
// Test the "custom derives" capability by adding `PartialEq` to the `Test` struct.
|
|
fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
|
|
if info.name == "Test" {
|
|
vec!["PartialEq".into()]
|
|
} else if info.name == "MyOrderedEnum" {
|
|
vec!["std::cmp::PartialOrd".into()]
|
|
} else {
|
|
vec![]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for MacroCallback {
|
|
fn drop(&mut self) {
|
|
assert_eq!(
|
|
*self.seen_hellos.lock().unwrap(),
|
|
3,
|
|
"str_macro handle was not called once for all relevant macros"
|
|
);
|
|
assert_eq!(
|
|
*self.seen_funcs.lock().unwrap(),
|
|
5,
|
|
"func_macro handle was not called once for all relevant macros"
|
|
);
|
|
}
|
|
}
|
|
|
|
fn setup_macro_test() {
|
|
cc::Build::new()
|
|
.cpp(true)
|
|
.file("cpp/Test.cc")
|
|
.include("include")
|
|
.compile("libtest.a");
|
|
|
|
let macros = Arc::new(RwLock::new(HashSet::new()));
|
|
|
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
let out_rust_file = out_path.join("test.rs");
|
|
let out_rust_file_relative = out_rust_file
|
|
.strip_prefix(std::env::current_dir().unwrap().parent().unwrap())
|
|
.unwrap();
|
|
let out_dep_file = out_path.join("test.d");
|
|
|
|
let bindings = Builder::default()
|
|
.rustfmt_bindings(false)
|
|
.enable_cxx_namespaces()
|
|
.default_enum_style(EnumVariation::Rust {
|
|
non_exhaustive: false,
|
|
})
|
|
.raw_line("pub use self::root::*;")
|
|
.raw_line("extern { fn my_prefixed_function_to_remove(i: i32); }")
|
|
.module_raw_line("root::testing", "pub type Bar = i32;")
|
|
.header("cpp/Test.h")
|
|
.clang_args(&["-x", "c++", "-std=c++11", "-I", "include"])
|
|
.parse_callbacks(Box::new(MacroCallback {
|
|
macros: macros.clone(),
|
|
seen_hellos: Mutex::new(0),
|
|
seen_funcs: Mutex::new(0),
|
|
}))
|
|
.blocklist_function("my_prefixed_function_to_remove")
|
|
.constified_enum("my_prefixed_enum_to_be_constified")
|
|
.opaque_type("my_prefixed_templated_foo<my_prefixed_baz>")
|
|
.depfile(out_rust_file_relative.display().to_string(), &out_dep_file)
|
|
.generate()
|
|
.expect("Unable to generate bindings");
|
|
|
|
assert!(macros.read().unwrap().contains("TESTMACRO"));
|
|
bindings
|
|
.write_to_file(&out_rust_file)
|
|
.expect("Couldn't write bindings!");
|
|
|
|
let observed_deps =
|
|
std::fs::read_to_string(out_dep_file).expect("Couldn't read depfile!");
|
|
let expected_deps = format!(
|
|
"{}: cpp/Test.h include/stub.h",
|
|
out_rust_file_relative.display()
|
|
);
|
|
assert_eq!(
|
|
observed_deps, expected_deps,
|
|
"including stub via include dir must produce correct dep path",
|
|
);
|
|
}
|
|
|
|
fn setup_wrap_static_fns_test() {
|
|
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
|
|
// set output directory under /target so it is easy to clean generated files
|
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
let out_rust_file = out_path.join("extern.rs");
|
|
|
|
let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/")
|
|
.canonicalize()
|
|
.expect("Cannot canonicalize libdir path");
|
|
let input_header_file_path = input_header_dir.join("wrap-static-fns.h");
|
|
let input_header_file_path_str = input_header_file_path
|
|
.to_str()
|
|
.expect("Path could not be converted to a str");
|
|
|
|
// generate external bindings with the external .c and .h files
|
|
let bindings = Builder::default()
|
|
.header(input_header_file_path_str)
|
|
.parse_callbacks(Box::new(CargoCallbacks))
|
|
.wrap_static_fns(true)
|
|
.wrap_static_fns_path(
|
|
out_path.join("wrap_static_fns").display().to_string(),
|
|
)
|
|
.generate()
|
|
.expect("Unable to generate bindings");
|
|
|
|
println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern
|
|
println!("bindings generated: {}", bindings);
|
|
|
|
let obj_path = out_path.join("wrap_static_fns.o");
|
|
let lib_path = out_path.join("libwrap_static_fns.a");
|
|
|
|
// build the external files to check if they work
|
|
let clang_output = std::process::Command::new("clang")
|
|
.arg("-c")
|
|
.arg("-o")
|
|
.arg(&obj_path)
|
|
.arg(out_path.join("wrap_static_fns.c"))
|
|
.arg("-include")
|
|
.arg(input_header_file_path)
|
|
.output()
|
|
.expect("`clang` command error");
|
|
if !clang_output.status.success() {
|
|
panic!(
|
|
"Could not compile object file:\n{}",
|
|
String::from_utf8_lossy(&clang_output.stderr)
|
|
);
|
|
}
|
|
|
|
let ar_output = std::process::Command::new("ar")
|
|
.arg("rcs")
|
|
.arg(lib_path)
|
|
.arg(obj_path)
|
|
.output()
|
|
.expect("`ar` command error");
|
|
|
|
if !ar_output.status.success() {
|
|
panic!(
|
|
"Could not emit library file:\n{}",
|
|
String::from_utf8_lossy(&ar_output.stderr)
|
|
);
|
|
}
|
|
|
|
bindings
|
|
.write_to_file(out_rust_file)
|
|
.expect("Cound not write bindings to the Rust file");
|
|
}
|
|
|
|
fn main() {
|
|
setup_macro_test();
|
|
setup_wrap_static_fns_test();
|
|
}
|