use proc_macro2::TokenStream as TokenStream2;
use std::path::PathBuf;
use cxx_gen::GeneratedCode;
use indoc::indoc;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{ItemMod, Macro};
use log::{debug, info, warn};
use osstrtools::OsStrTools;
mod bridge_converter;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Bindgen(()),
CxxGen(cxx_gen::Error),
Parsing(syn::Error),
NoAutoCxxInc,
CouldNotCanoncalizeIncludeDir(PathBuf),
Conversion(bridge_converter::ConvertError),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub enum CppInclusion {
Define(String),
Header(String),
}
pub struct IncludeCpp {
inclusions: Vec<CppInclusion>,
allowlist: Vec<String>,
preconfigured_inc_dirs: Option<std::ffi::OsString>,
parse_only: bool,
}
impl Parse for IncludeCpp {
fn parse(input: ParseStream) -> ParseResult<Self> {
Self::new_from_parse_stream(input)
}
}
static PRELUDE: &str = indoc! {"
/**
* <div rustbindgen=\"true\" replaces=\"std::unique_ptr\">
*/
template<typename T> class UniquePtr {
T* ptr;
};
/**
* <div rustbindgen=\"true\" replaces=\"std::string\">
*/
class CxxString {
char* str_data;
};
\n"};
impl IncludeCpp {
fn new_from_parse_stream(input: ParseStream) -> syn::Result<Self> {
let mut inclusions = Vec::new();
let mut allowlist = Vec::new();
let mut parse_only = false;
while !input.is_empty() {
let ident: syn::Ident = input.parse()?;
if ident == "Header" {
let args;
syn::parenthesized!(args in input);
let hdr: syn::LitStr = args.parse()?;
inclusions.push(CppInclusion::Header(hdr.value()));
} else if ident == "Allow" {
let args;
syn::parenthesized!(args in input);
let allow: syn::LitStr = args.parse()?;
allowlist.push(allow.value());
} else if ident == "ParseOnly" {
parse_only = true;
} else {
return Err(syn::Error::new(ident.span(), "expected Header or Allow"));
}
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
}
Ok(IncludeCpp {
inclusions,
allowlist,
preconfigured_inc_dirs: None,
parse_only,
})
}
pub fn new_from_syn(mac: Macro) -> Result<Self> {
mac.parse_body::<IncludeCpp>().map_err(Error::Parsing)
}
pub fn set_include_dirs<P: AsRef<std::ffi::OsStr>>(&mut self, include_dirs: P) {
self.preconfigured_inc_dirs = Some(include_dirs.as_ref().into());
}
fn build_header(&self) -> String {
let mut s = PRELUDE.to_string();
for incl in &self.inclusions {
let text = match incl {
CppInclusion::Define(symbol) => format!("#define {}\n", symbol),
CppInclusion::Header(path) => format!("#include \"{}\"\n", path),
};
s.push_str(&text);
}
s
}
fn determine_incdirs(&self) -> Result<Vec<PathBuf>> {
let inc_dirs = match &self.preconfigured_inc_dirs {
Some(d) => d.clone(),
None => std::env::var_os("AUTOCXX_INC").ok_or(Error::NoAutoCxxInc)?,
};
let multi_path_separator = if std::path::MAIN_SEPARATOR == '/' {
b':'
} else {
b';'
};
let splitter = [multi_path_separator];
let inc_dirs = inc_dirs.split(&splitter[0..1]);
let mut inc_dir_paths = Vec::new();
for inc_dir in inc_dirs {
let p: PathBuf = inc_dir.into();
let p = p
.canonicalize()
.map_err(|_| Error::CouldNotCanoncalizeIncludeDir(p))?;
inc_dir_paths.push(p);
}
Ok(inc_dir_paths)
}
fn make_bindgen_builder(&self) -> Result<bindgen::Builder> {
let inc_dirs = self.determine_incdirs()?;
let full_header = self.build_header();
debug!("Full header: {}", full_header);
debug!("Inc dir: {:?}", inc_dirs);
let mut builder = bindgen::builder()
.clang_args(&["-x", "c++", "-std=c++14"])
.blacklist_item(".*default.*")
.blacklist_item(".*unique_ptr.*")
.blacklist_item(".*string.*")
.blacklist_item(".*std_.*")
.blacklist_item("std_.*")
.blacklist_item("std.*")
.blacklist_item(".*compressed_pair.*")
.blacklist_item(".*allocator.*")
.blacklist_item(".*wrap_iter.*")
.blacklist_item(".*reverse_iterator.*")
.blacklist_item(".*propagate_on_container.*")
.blacklist_item(".*char_traits.*")
.blacklist_item(".*size_t.*")
.blacklist_item(".*mbstate_t.*")
.derive_copy(false)
.derive_debug(false)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.layout_tests(false);
for inc_dir in inc_dirs {
builder = builder.clang_arg(format!("-I{}", inc_dir.display()));
}
builder = builder.header_contents("example.hpp", &full_header);
for a in &self.allowlist {
builder = builder.whitelist_type(a);
builder = builder.whitelist_function(a);
}
Ok(builder)
}
pub fn generate_rs(self) -> Result<TokenStream2> {
self.do_generation(true)
}
fn do_generation(self, old_rust: bool) -> Result<TokenStream2> {
if self.parse_only {
return Ok(TokenStream2::new());
}
let bindings = self
.make_bindgen_builder()?
.generate()
.map_err(Error::Bindgen)?;
let bindings = bindings.to_string();
let bindings = format!("#[cxx::bridge] mod ffi {{ {} }}", bindings);
info!("Bindings: {}", bindings);
let bindings = syn::parse_str::<ItemMod>(&bindings).map_err(Error::Parsing)?;
let mut include_list = Vec::new();
for incl in &self.inclusions {
match incl {
CppInclusion::Header(ref hdr) => {
include_list.push(hdr.clone());
}
CppInclusion::Define(_) => warn!("Currently no way to define! within cxx"),
}
}
let mut converter = bridge_converter::BridgeConverter::new(include_list, old_rust);
let new_bindings = converter.convert(bindings).map_err(Error::Conversion)?;
let new_bindings = new_bindings.to_token_stream();
info!("New bindings: {}", new_bindings.to_string());
Ok(new_bindings)
}
pub fn generate_h_and_cxx(self) -> Result<GeneratedCode> {
let rs = self.do_generation(false)?;
let opt = cxx_gen::Opt::default();
let results = cxx_gen::generate_header_and_cc(rs, &opt).map_err(Error::CxxGen);
if let Ok(ref gen) = results {
info!(
"CXX: {}",
String::from_utf8(gen.implementation.clone()).unwrap()
);
info!("header: {}", String::from_utf8(gen.header.clone()).unwrap());
}
results
}
pub fn include_dirs(&self) -> Result<Vec<PathBuf>> {
self.determine_incdirs()
}
}
#[cfg(test)]
mod tests {
use indoc::indoc;
use log::info;
use proc_macro2::TokenStream;
use quote::quote;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use tempfile::{tempdir, TempDir};
use test_env_log::test;
lazy_static::lazy_static! {
static ref BUILDER: Mutex<LinkableTryBuilder> = Mutex::new(LinkableTryBuilder::new());
}
struct LinkableTryBuilder {
temp_dir: TempDir,
test_cases: trybuild::TestCases,
}
impl LinkableTryBuilder {
fn new() -> Self {
LinkableTryBuilder {
temp_dir: tempdir().unwrap(),
test_cases: trybuild::TestCases::new(),
}
}
fn copy_libraries_into_temp_dir<P1: AsRef<Path>>(
&self,
library_path: &P1,
library_name: &str,
) {
for item in std::fs::read_dir(library_path).unwrap() {
let item = item.unwrap();
if item
.file_name()
.into_string()
.unwrap()
.contains(library_name)
{
let dest_lib = self.temp_dir.path().join(item.file_name());
std::fs::copy(item.path(), dest_lib).unwrap();
}
}
}
fn build<P1: AsRef<Path>, P2: AsRef<Path>>(
&self,
library_path: &P1,
library_name: &str,
rs_path: &P2,
) {
self.copy_libraries_into_temp_dir(library_path, library_name);
std::env::set_var(
"RUSTFLAGS",
format!("-L {}", self.temp_dir.path().to_str().unwrap()),
);
self.test_cases.pass(rs_path)
}
}
fn write_to_file(tdir: &TempDir, filename: &str, content: &str) -> PathBuf {
let path = tdir.path().join(filename);
let mut f = File::create(&path).unwrap();
info!("Writing to {:?}: {}", path, content);
f.write_all(content.as_bytes()).unwrap();
path
}
fn run_test(cxx_code: &str, header_code: &str, rust_code: TokenStream, allowed_funcs: &[&str]) {
let tdir = tempdir().unwrap();
write_to_file(&tdir, "input.h", header_code);
let allowed_funcs = allowed_funcs.iter().map(|s| {
quote! {
Allow(#s)
}
});
let expanded_rust = quote! {
use autocxx_macro::include_cxx;
include_cxx!(
Header("input.h"),
#(#allowed_funcs),*
);
fn main() {
#rust_code
}
#[link(name="autocxx-demo")]
extern {}
};
let rs_code = format!("{}", expanded_rust);
let rs_path = write_to_file(&tdir, "input.rs", &rs_code);
let cxx_code = format!("#include \"{}\"\n{}", "input.h", cxx_code);
let cxx_path = write_to_file(&tdir, "input.cxx", &cxx_code);
info!("Path is {:?}", tdir.path());
let target_dir = tdir.path().join("target");
std::fs::create_dir(&target_dir).unwrap();
let target = rust_info::get().target_triple.unwrap();
let mut b = autocxx_build::Builder::new(&rs_path, tdir.path().to_str().unwrap()).unwrap();
b.builder()
.file(cxx_path)
.out_dir(&target_dir)
.host(&target)
.target(&target)
.opt_level(1)
.flag("-std=c++14")
.include(tdir.path())
.try_compile("autocxx-demo")
.unwrap();
BUILDER
.lock()
.unwrap()
.build(&target_dir, "autocxx-demo", &rs_path);
}
#[test]
fn test_return_void() {
let cxx = indoc! {"
void do_nothing() {
}
"};
let hdr = indoc! {"
void do_nothing();
"};
let rs = quote! {
ffi::do_nothing();
};
run_test(cxx, hdr, rs, &["do_nothing"]);
}
#[test]
fn test_return_i32() {
let cxx = indoc! {"
uint32_t give_int() {
return 4;
}
"};
let hdr = indoc! {"
#include <cstdint>
uint32_t give_int();
"};
let rs = quote! {
assert_eq!(ffi::give_int(), 4);
};
run_test(cxx, hdr, rs, &["give_int"]);
}
#[test]
fn test_take_i32() {
let cxx = indoc! {"
uint32_t take_int(uint32_t a) {
return a + 3;
}
"};
let hdr = indoc! {"
#include <cstdint>
uint32_t take_int(uint32_t a);
"};
let rs = quote! {
assert_eq!(ffi::take_int(3), 6);
};
run_test(cxx, hdr, rs, &["take_int"]);
}
#[test]
#[ignore]
fn test_give_up_int() {
let cxx = indoc! {"
std::unique_ptr<uint32_t> give_up() {
return std::make_unique<uint32_t>(12);
}
"};
let hdr = indoc! {"
#include <cstdint>
#include <memory>
std::unique_ptr<uint32_t> give_up();
"};
let rs = quote! {
assert_eq!(ffi::give_up().as_ref().unwrap(), 12);
};
run_test(cxx, hdr, rs, &["give_up"]);
}
#[test]
fn test_give_string_up() {
let cxx = indoc! {"
std::unique_ptr<std::string> give_str_up() {
return std::make_unique<std::string>(\"Bob\");
}
"};
let hdr = indoc! {"
#include <memory>
#include <string>
std::unique_ptr<std::string> give_str_up();
"};
let rs = quote! {
assert_eq!(ffi::give_str_up().as_ref().unwrap().to_str().unwrap(), "Bob");
};
run_test(cxx, hdr, rs, &["give_str_up"]);
}
#[test]
#[ignore]
fn test_give_string_plain() {
let cxx = indoc! {"
std::string give_str() {
return std::string(\"Bob\");
}
"};
let hdr = indoc! {"
#include <string>
std::string give_str();
"};
let rs = quote! {
assert_eq!(ffi::give_str_up().to_str().unwrap(), "Bob");
};
run_test(cxx, hdr, rs, &["give_str"]);
}
#[test]
fn test_cycle_string_up() {
let cxx = indoc! {"
std::unique_ptr<std::string> give_str_up() {
return std::make_unique<std::string>(\"Bob\");
}
uint32_t take_str_up(std::unique_ptr<std::string> a) {
return a->length();
}
"};
let hdr = indoc! {"
#include <memory>
#include <string>
#include <cstdint>
std::unique_ptr<std::string> give_str_up();
uint32_t take_str_up(std::unique_ptr<std::string> a);
"};
let rs = quote! {
let s = ffi::give_str();
assert_eq!(ffi::take_str(s), 3);
};
run_test(cxx, hdr, rs, &["give_str_up", "take_str_up"]);
}
#[test]
#[ignore]
fn test_cycle_string() {
let cxx = indoc! {"
std::string give_str() {
return std::string(\"Bob\");
}
uint32_t take_str(std::string a) {
return a.length();
}
"};
let hdr = indoc! {"
#include <string>
#include <cstdint>
std::string give_str();
uint32_t take_str(std::string a);
"};
let rs = quote! {
let s = ffi::give_str();
assert_eq!(ffi::take_str(s), 3);
};
let allowed_funcs = &["give_str", "take_str"];
run_test(cxx, hdr, rs, allowed_funcs);
}
#[test]
fn test_cycle_string_by_ref() {
let cxx = indoc! {"
std::unique_ptr<std::string> give_str() {
return std::make_unique<std::string>(\"Bob\");
}
uint32_t take_str(const std::string& a) {
return a.length();
}
"};
let hdr = indoc! {"
#include <string>
#include <cstdint>
std::unique_ptr<std::string> give_str();
uint32_t take_str(const std::string& a);
"};
let rs = quote! {
let s = ffi::give_str();
assert_eq!(ffi::take_str(s.as_ref()), 3);
};
let allowed_funcs = &["give_str", "take_str"];
run_test(cxx, hdr, rs, allowed_funcs);
}
#[test]
fn test_cycle_string_by_mut_ref() {
let cxx = indoc! {"
std::unique_ptr<std::string> give_str() {
return std::make_unique<std::string>(\"Bob\");
}
uint32_t take_str(std::string& a) {
return a.length();
}
"};
let hdr = indoc! {"
#include <string>
#include <cstdint>
std::unique_ptr<std::string> give_str();
uint32_t take_str(std::string& a);
"};
let rs = quote! {
let s = ffi::give_str();
assert_eq!(ffi::take_str(s.as_ref()), 3);
};
let allowed_funcs = &["give_str", "take_str"];
run_test(cxx, hdr, rs, allowed_funcs);
}
#[test]
fn test_give_pod_by_value() {
let cxx = indoc! {"
Bob give_bob() {
Bob a;
a.a = 3;
a.b = 4;
return a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
uint32_t a;
uint32_t b;
};
Bob give_bob();
"};
let rs = quote! {
assert_eq!(ffi::give_bob().b, 4);
};
run_test(cxx, hdr, rs, &["give_bob", "Bob"]);
}
#[test]
#[ignore]
fn test_give_pod_class_by_value() {
let cxx = indoc! {"
Bob give_bob() {
Bob a;
a.a = 3;
a.b = 4;
return a;
}
"};
let hdr = indoc! {"
#include <cstdint>
class Bob {
public:
uint32_t a;
uint32_t b;
};
Bob give_bob();
"};
let rs = quote! {
assert_eq!(ffi::give_bob().b, 4);
};
run_test(cxx, hdr, rs, &["give_bob", "Bob"]);
}
#[test]
fn test_give_pod_by_up() {
let cxx = indoc! {"
std::unique_ptr<Bob> give_bob() {
auto a = std::make_unique<Bob>();
a->a = 3;
a->b = 4;
return a;
}
"};
let hdr = indoc! {"
#include <cstdint>
#include <memory>
struct Bob {
uint32_t a;
uint32_t b;
};
std::unique_ptr<Bob> give_bob();
"};
let rs = quote! {
assert_eq!(ffi::give_bob().as_ref().unwrap().b, 4);
};
run_test(cxx, hdr, rs, &["give_bob", "Bob"]);
}
#[test]
fn test_take_pod_by_value() {
let cxx = indoc! {"
uint32_t take_bob(Bob a) {
return a.a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
uint32_t a;
uint32_t b;
};
uint32_t take_bob(Bob a);
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13 };
assert_eq!(ffi::take_bob(a), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob"]);
}
#[test]
fn test_take_pod_by_ref() {
let cxx = indoc! {"
uint32_t take_bob(const Bob& a) {
return a.a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
uint32_t a;
uint32_t b;
};
uint32_t take_bob(const Bob& a);
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13 };
assert_eq!(ffi::take_bob(&a), 12);
};
let allowed_funcs = &["take_bob", "Bob"];
run_test(cxx, hdr, rs, allowed_funcs);
}
#[test]
fn test_take_pod_by_mut_ref() {
let cxx = indoc! {"
uint32_t take_bob(Bob& a) {
a.b = 14;
return a.a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
uint32_t a;
uint32_t b;
};
uint32_t take_bob(const Bob& a);
"};
let rs = quote! {
let mut a = ffi::Bob { a: 12, b: 13 };
assert_eq!(ffi::take_bob(&mut a), 12);
assert_eq!(a.b, 14);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob"]);
}
#[test]
fn test_take_nested_pod_by_value() {
let cxx = indoc! {"
uint32_t take_bob(Bob a) {
return a.a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Phil {
uint32_t d;
};
struct Bob {
uint32_t a;
uint32_t b;
Phil c;
};
uint32_t take_bob(Bob a);
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13, c: ffi::Phil { d: 4 } };
assert_eq!(ffi::take_bob(a), 12);
};
let allowed_funcs = &["take_bob", "Bob"];
run_test(cxx, hdr, rs, allowed_funcs);
}
#[test]
#[ignore]
fn test_cycle_pod_with_str_by_value() {
let cxx = indoc! {"
uint32_t take_bob(Bob a) {
return a.a;
}
std::string get_str() {
return \"hello\";
}
"};
let hdr = indoc! {"
#include <cstdint>
#include <string>
struct Bob {
uint32_t a;
std::string b;
};
uint32_t take_bob(Bob a);
std::string get_str();
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: ffi::get_str() };
assert_eq!(ffi::take_bob(a), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob", "get_str"]);
}
#[test]
#[ignore]
fn test_cycle_pod_with_str_by_ref() {
let cxx = indoc! {"
uint32_t take_bob(const Bob& a) {
return a.a;
}
std::string get_str() {
return \"hello\";
}
"};
let hdr = indoc! {"
#include <cstdint>
#include <string>
struct Bob {
uint32_t a;
std::string b;
};
uint32_t take_bob(const Bob& a);
std::string get_str();
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: ffi::get_str() };
assert_eq!(ffi::take_bob(&a), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob", "get_str"]);
}
#[test]
#[ignore]
fn test_make_up() {
let cxx = indoc! {"
Bob::Bob() : a(3) {
}
"};
let hdr = indoc! {"
#include <cstdint>
class Bob {
public:
Bob();
uint32_t a;
};
"};
let rs = quote! {
let a = ffi::Bob::make_unique();
assert_eq!(a.as_ref().unwrap().a, 3);
};
run_test(cxx, hdr, rs, &["Bob"]);
}
#[test]
#[ignore]
fn test_make_up_int() {
let cxx = indoc! {"
Bob::Bob(uint32_t a) : b(a) {
}
"};
let hdr = indoc! {"
#include <cstdint>
class Bob {
public:
Bob(uint32_t a);
uint32_t b;
};
"};
let rs = quote! {
let a = ffi::Bob::make_unique(3);
assert_eq!(a.as_ref().unwrap().b, 3);
};
run_test(cxx, hdr, rs, &["Bob"]);
}
#[test]
#[ignore]
fn test_enum() {
let cxx = indoc! {"
Bob give_bob() {
return Bob::BOB_VALUE_2;
}
"};
let hdr = indoc! {"
#include <cstdint>
enum Bob {
BOB_VALUE_1,
BOB_VALUE_2,
};
Bob give_bob();
"};
let rs = quote! {
let a = ffi::Bob::BOB_VALUE_2;
let b = ffi::give_bob();
assert_eq!(a, b);
};
run_test(cxx, hdr, rs, &["Bob", "give_bob"]);
}
#[test]
#[ignore]
fn test_enum_no_funcs() {
let cxx = indoc! {"
"};
let hdr = indoc! {"
enum Bob {
BOB_VALUE_1,
BOB_VALUE_2,
};
"};
let rs = quote! {
let a = ffi::Bob::BOB_VALUE_1;
let b = ffi::Bob::BOB_VALUE_2;
assert_ne!(a, b);
};
run_test(cxx, hdr, rs, &["Bob"]);
}
#[test]
#[ignore]
fn test_take_pod_class_by_value() {
let cxx = indoc! {"
uint32_t take_bob(Bob a) {
return a.a;
}
"};
let hdr = indoc! {"
#include <cstdint>
class Bob {
public:
uint32_t a;
uint32_t b;
};
uint32_t take_bob(Bob a);
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13 };
assert_eq!(ffi::take_bob(a), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob"]);
}
#[test]
fn test_pod_method() {
let cxx = indoc! {"
uint32_t Bob::get_bob() const {
return a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
public:
uint32_t a;
uint32_t b;
uint32_t get_bob() const;
};
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13 };
assert_eq!(a.get_bob(), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob"]);
}
#[test]
fn test_pod_mut_method() {
let cxx = indoc! {"
uint32_t Bob::get_bob() {
return a;
}
"};
let hdr = indoc! {"
#include <cstdint>
struct Bob {
public:
uint32_t a;
uint32_t b;
uint32_t get_bob();
};
"};
let rs = quote! {
let a = ffi::Bob { a: 12, b: 13 };
assert_eq!(a.get_bob(), 12);
};
run_test(cxx, hdr, rs, &["take_bob", "Bob"]);
}
}