Skip to content

Allow DSO export for C Rust bindings. #231

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

Merged
merged 2 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions gen/cmd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

/// Any additional headers to #include
#[structopt(short, long)]
include: Vec<String>,
Expand All @@ -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) {
Expand Down
79 changes: 61 additions & 18 deletions gen/src/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,81 @@ struct Input {
pub(super) struct Opt {
/// Any additional headers to #include
pub include: Vec<String>,
/// Whether to set __attribute__((visibility("default")))
/// or similar annotations on function implementations.
pub cxx_impl_annotations: Option<String>,
}

pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
let header = false;
generate(path, opt, header)
generate_from_path(path, opt, header)
}

pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec<u8> {
let header = true;
generate(path, opt, header)
generate_from_path(path, opt, header)
}

fn generate(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
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<Vec<u8>> {
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)"));
}
}
18 changes: 14 additions & 4 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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\"");
}
Expand Down Expand Up @@ -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<String>,
) {
if !out.header {
if let Some(annotation) = impl_annotations {
write!(out, "{} ", annotation);
}
}
if efn.throws {
write!(out, "::rust::Str::Repr ");
} else {
Expand Down Expand Up @@ -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<String>) {
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);
Expand Down