diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs index a20179f26..701f77fd9 100644 --- a/gen/cmd/src/main.rs +++ b/gen/cmd/src/main.rs @@ -35,6 +35,15 @@ struct Opt { #[structopt(long)] header: bool, + /// Optional annotation for implementations of C++ function + /// wrappers that may be exposed to Rust. You may for example + /// need to provide __declspec(dllexport) or + /// __attribute__((visibility("default"))) if Rust code from + /// one shared object or executable depends on these C++ functions + /// in another. + #[structopt(long)] + cxx_impl_annotations: Option, + /// Any additional headers to #include #[structopt(short, long)] include: Vec, @@ -49,6 +58,7 @@ fn main() { let gen = gen::Opt { include: opt.include, + cxx_impl_annotations: opt.cxx_impl_annotations, }; match (opt.input, opt.header) { diff --git a/gen/src/mod.rs b/gen/src/mod.rs index b9d35d70d..77a0c6580 100644 --- a/gen/src/mod.rs +++ b/gen/src/mod.rs @@ -24,38 +24,81 @@ struct Input { pub(super) struct Opt { /// Any additional headers to #include pub include: Vec, + /// Whether to set __attribute__((visibility("default"))) + /// or similar annotations on function implementations. + pub cxx_impl_annotations: Option, } pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec { let header = false; - generate(path, opt, header) + generate_from_path(path, opt, header) } pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec { let header = true; - generate(path, opt, header) + generate_from_path(path, opt, header) } -fn generate(path: &Path, opt: Opt, header: bool) -> Vec { +fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec { let source = match fs::read_to_string(path) { Ok(source) => source, Err(err) => format_err(path, "", Error::Io(err)), }; - match (|| -> Result<_> { - proc_macro2::fallback::force(); - let ref mut errors = Errors::new(); - let syntax = syn::parse_file(&source)?; - let bridge = find::find_bridge_mod(syntax)?; - let ref namespace = bridge.namespace; - let ref apis = syntax::parse_items(errors, bridge.module); - let ref types = Types::collect(errors, apis); - errors.propagate()?; - check::typecheck(errors, namespace, apis, types); - errors.propagate()?; - let out = write::gen(namespace, apis, types, opt, header); - Ok(out) - })() { - Ok(out) => out.content(), + match generate(&source, opt, header) { + Ok(out) => out, Err(err) => format_err(path, &source, err), } } + +fn generate(source: &str, opt: Opt, header: bool) -> Result> { + proc_macro2::fallback::force(); + let ref mut errors = Errors::new(); + let syntax = syn::parse_file(&source)?; + let bridge = find::find_bridge_mod(syntax)?; + let ref namespace = bridge.namespace; + let ref apis = syntax::parse_items(errors, bridge.module); + let ref types = Types::collect(errors, apis); + errors.propagate()?; + check::typecheck(errors, namespace, apis, types); + errors.propagate()?; + let out = write::gen(namespace, apis, types, opt, header); + Ok(out.content()) +} + +#[cfg(test)] +mod tests { + use crate::gen::{generate, Opt}; + + const CPP_EXAMPLE: &'static str = r#" + #[cxx::bridge] + mod ffi { + extern "C" { + pub fn do_cpp_thing(foo: &str); + } + } + "#; + + #[test] + fn test_cpp() { + let opts = Opt { + include: Vec::new(), + cxx_impl_annotations: None, + }; + let output = generate(CPP_EXAMPLE, opts, false).unwrap(); + let output = std::str::from_utf8(&output).unwrap(); + // To avoid continual breakage we won't test every byte. + // Let's look for the major features. + assert!(output.contains("void cxxbridge03$do_cpp_thing(::rust::Str::Repr foo)")); + } + + #[test] + fn test_annotation() { + let opts = Opt { + include: Vec::new(), + cxx_impl_annotations: Some("ANNOTATION".to_string()), + }; + let output = generate(CPP_EXAMPLE, opts, false).unwrap(); + let output = std::str::from_utf8(&output).unwrap(); + assert!(output.contains("ANNOTATION void cxxbridge03$do_cpp_thing(::rust::Str::Repr foo)")); + } +} diff --git a/gen/src/write.rs b/gen/src/write.rs index 46b6151c1..04d8e3e2a 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -86,13 +86,13 @@ pub(super) fn gen( out.begin_block("extern \"C\""); write_exception_glue(out, apis); for api in apis { - let (efn, write): (_, fn(_, _, _)) = match api { + let (efn, write): (_, fn(_, _, _, _)) = match api { Api::CxxFunction(efn) => (efn, write_cxx_function_shim), Api::RustFunction(efn) => (efn, write_rust_function_decl), _ => continue, }; out.next_section(); - write(out, efn, types); + write(out, efn, types, &opt.cxx_impl_annotations); } out.end_block("extern \"C\""); } @@ -399,7 +399,17 @@ fn write_exception_glue(out: &mut OutFile, apis: &[Api]) { } } -fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { +fn write_cxx_function_shim( + out: &mut OutFile, + efn: &ExternFn, + types: &Types, + impl_annotations: &Option, +) { + if !out.header { + if let Some(annotation) = impl_annotations { + write!(out, "{} ", annotation); + } + } if efn.throws { write!(out, "::rust::Str::Repr "); } else { @@ -560,7 +570,7 @@ fn write_function_pointer_trampoline( write_rust_function_shim_impl(out, &c_trampoline, f, types, &r_trampoline, indirect_call); } -fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) { +fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types, _: &Option) { let link_name = mangle::extern_fn(&out.namespace, efn); let indirect_call = false; write_rust_function_decl_impl(out, &link_name, efn, types, indirect_call);