Merge pull request 258 from adetaylor/cxx-embedding-lib
diff --git a/gen/lib/Cargo.toml b/gen/lib/Cargo.toml
new file mode 100644
index 0000000..3a5d938
--- /dev/null
+++ b/gen/lib/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "cxx-gen"
+version = "0.3.4"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+edition = "2018"
+license = "MIT OR Apache-2.0"
+description = "C++ code generator for integrating `cxx` crate into higher level tools."
+repository = "https://github.com/dtolnay/cxx"
+keywords = ["ffi"]
+categories = ["development-tools::ffi"]
+
+[dependencies]
+anyhow = "1.0"
+cc = "1.0.49"
+codespan-reporting = "0.9"
+proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] }
+quote = { version = "1.0", default-features = false }
+syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/gen/lib/LICENSE-APACHE b/gen/lib/LICENSE-APACHE
new file mode 120000
index 0000000..1cd601d
--- /dev/null
+++ b/gen/lib/LICENSE-APACHE
@@ -0,0 +1 @@
+../../LICENSE-APACHE
\ No newline at end of file
diff --git a/gen/lib/LICENSE-MIT b/gen/lib/LICENSE-MIT
new file mode 120000
index 0000000..b2cfbdc
--- /dev/null
+++ b/gen/lib/LICENSE-MIT
@@ -0,0 +1 @@
+../../LICENSE-MIT
\ No newline at end of file
diff --git a/gen/lib/src/gen b/gen/lib/src/gen
new file mode 120000
index 0000000..929cb3d
--- /dev/null
+++ b/gen/lib/src/gen
@@ -0,0 +1 @@
+../../src
\ No newline at end of file
diff --git a/gen/lib/src/lib.rs b/gen/lib/src/lib.rs
new file mode 100644
index 0000000..cecf654
--- /dev/null
+++ b/gen/lib/src/lib.rs
@@ -0,0 +1,46 @@
+//! The CXX code generator for constructing and compiling C++ code.
+//!
+//! This is intended to be embedded into higher-level code generators.
+
+mod gen;
+mod syntax;
+
+pub use crate::gen::Opt;
+use proc_macro2::TokenStream;
+
+pub use crate::gen::{Error, GeneratedCode, Result};
+
+/// Generate C++ bindings code from a Rust token stream. This should be a Rust
+/// token stream which somewhere contains a `#[cxx::bridge] mod {}`.
+pub fn generate_header_and_cc(rust_source: TokenStream, opt: Opt) -> Result<GeneratedCode> {
+ gen::do_generate_from_tokens(rust_source, opt)
+}
+
+#[cfg(test)]
+mod test {
+ use quote::quote;
+
+ #[test]
+ fn test_positive() {
+ let rs = quote! {
+ #[cxx::bridge]
+ mod ffi {
+ extern "C" {
+ fn in_C();
+ }
+ extern "Rust" {
+ fn in_rs();
+ }
+ }
+ };
+ let code = crate::generate_header_and_cc(rs).unwrap();
+ assert!(code.cxx.len() > 0);
+ assert!(code.header.len() > 0);
+ }
+
+ #[test]
+ fn test_negative() {
+ let rs = quote! {};
+ assert!(crate::generate_header_and_cc(rs).is_err())
+ }
+}
diff --git a/gen/lib/src/syntax b/gen/lib/src/syntax
new file mode 120000
index 0000000..a6fe06c
--- /dev/null
+++ b/gen/lib/src/syntax
@@ -0,0 +1 @@
+../../../syntax
\ No newline at end of file
diff --git a/gen/src/error.rs b/gen/src/error.rs
index 537351f..9a55243 100644
--- a/gen/src/error.rs
+++ b/gen/src/error.rs
@@ -11,12 +11,15 @@
use std::path::Path;
use std::process;
-pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
+pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
-pub(super) enum Error {
+pub enum Error {
+ /// No `#[cxx::bridge]` module could be found.
NoBridgeMod,
+ /// An IO error occurred when reading Rust code.
Io(io::Error),
+ /// A syntax error occurred when parsing Rust code.
Syn(syn::Error),
}
diff --git a/gen/src/mod.rs b/gen/src/mod.rs
index 68c6f85..a939760 100644
--- a/gen/src/mod.rs
+++ b/gen/src/mod.rs
@@ -10,15 +10,19 @@
#[cfg(test)]
mod tests;
-use self::error::{format_err, Error, Result};
+use self::error::format_err;
+pub use self::error::{Error, Result};
use self::file::File;
use crate::syntax::report::Errors;
use crate::syntax::{self, check, Types};
+use proc_macro2::TokenStream;
+use std::clone::Clone;
use std::fs;
use std::path::Path;
-#[derive(Default)]
-pub(super) struct Opt {
+/// Options for C++ code generation.
+#[derive(Default, Clone)]
+pub struct Opt {
/// Any additional headers to #include
pub include: Vec<String>,
/// Whether to set __attribute__((visibility("default")))
@@ -26,6 +30,14 @@
pub cxx_impl_annotations: Option<String>,
}
+/// Results of code generation.
+pub struct GeneratedCode {
+ /// The bytes of a C++ header file.
+ pub header: Vec<u8>,
+ /// The bytes of a C++ implementation file (e.g. .cc, cpp etc.)
+ pub cxx: Vec<u8>,
+}
+
pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
let header = false;
generate_from_path(path, opt, header)
@@ -36,26 +48,52 @@
generate_from_path(path, opt, header)
}
+pub(super) fn do_generate_from_tokens(
+ tokens: TokenStream,
+ opt: Opt,
+) -> std::result::Result<GeneratedCode, Error> {
+ let syntax = syn::parse2::<File>(tokens)?;
+ match generate(syntax, opt, true, true) {
+ Ok((Some(header), Some(cxx))) => Ok(GeneratedCode { header, cxx }),
+ Err(err) => Err(err),
+ _ => panic!("Unexpected generation"),
+ }
+}
+
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)),
};
- let mut source = source.as_str();
+ match generate_from_string(&source, opt, header) {
+ Ok(out) => out,
+ Err(err) => format_err(path, &source, err),
+ }
+}
+
+fn generate_from_string(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
+ let mut source = source;
if source.starts_with("#!") && !source.starts_with("#![") {
let shebang_end = source.find('\n').unwrap_or(source.len());
source = &source[shebang_end..];
}
- match generate(source, opt, header) {
- Ok(out) => out,
- Err(err) => format_err(path, source, err),
+ let syntax: File = syn::parse_str(source)?;
+ let results = generate(syntax, opt, header, !header)?;
+ match results {
+ (Some(hdr), None) => Ok(hdr),
+ (None, Some(cxx)) => Ok(cxx),
+ _ => panic!("Unexpected generation"),
}
}
-fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
+fn generate(
+ syntax: File,
+ opt: Opt,
+ gen_header: bool,
+ gen_cxx: bool,
+) -> Result<(Option<Vec<u8>>, Option<Vec<u8>>)> {
proc_macro2::fallback::force();
let ref mut errors = Errors::new();
- let syntax: File = syn::parse_str(source)?;
let bridge = syntax
.modules
.into_iter()
@@ -68,6 +106,18 @@
errors.propagate()?;
check::typecheck(errors, namespace, apis, types);
errors.propagate()?;
- let out = write::gen(namespace, apis, types, opt, header);
- Ok(out.content())
+ // Some callers may wish to generate both header and C++
+ // from the same token stream to avoid parsing twice. But others
+ // only need to generate one or the other.
+ let hdr = if gen_header {
+ Some(write::gen(namespace, apis, types, opt.clone(), true).content())
+ } else {
+ None
+ };
+ let cxx = if gen_cxx {
+ Some(write::gen(namespace, apis, types, opt, false).content())
+ } else {
+ None
+ };
+ Ok((hdr, cxx))
}
diff --git a/gen/src/tests.rs b/gen/src/tests.rs
index 0e7a910..7621643 100644
--- a/gen/src/tests.rs
+++ b/gen/src/tests.rs
@@ -1,4 +1,4 @@
-use crate::gen::{generate, Opt};
+use crate::gen::{generate_from_string, Opt};
const CPP_EXAMPLE: &'static str = r#"
#[cxx::bridge]
@@ -15,7 +15,7 @@
include: Vec::new(),
cxx_impl_annotations: None,
};
- let output = generate(CPP_EXAMPLE, opts, false).unwrap();
+ let output = generate_from_string(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.
@@ -28,7 +28,7 @@
include: Vec::new(),
cxx_impl_annotations: Some("ANNOTATION".to_string()),
};
- let output = generate(CPP_EXAMPLE, opts, false).unwrap();
+ let output = generate_from_string(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)"));
}