mod conversion;
mod known_types;
mod parse_callbacks;
mod parse_file;
mod rust_pretty_printer;
mod types;
#[cfg(any(test, feature = "build"))]
mod builder;
#[cfg(test)]
mod integration_tests;
use autocxx_parser::{CppInclusion, IncludeCppConfig, UnsafePolicy};
use conversion::{BridgeConverter, CppCodegenResults};
use parse_callbacks::AutocxxParseCallbacks;
use proc_macro2::TokenStream as TokenStream2;
use std::{
collections::hash_map::DefaultHasher,
fmt::Display,
hash::{Hash, Hasher},
path::PathBuf,
};
use quote::ToTokens;
use syn::Result as ParseResult;
use syn::{
parse::{Parse, ParseStream},
parse_quote, ItemMod, Macro,
};
use itertools::join;
use known_types::KNOWN_TYPES;
use log::{info, warn};
use autocxx_bindgen as bindgen;
#[cfg(any(test, feature = "build"))]
pub use builder::{build, expect_build, BuilderBuild, BuilderError, BuilderResult, BuilderSuccess};
pub use parse_file::{parse_file, ParseError, ParsedFile};
pub use cxx_gen::HEADER;
pub use cxx;
pub struct CppFilePair {
pub header: Vec<u8>,
pub implementation: Vec<u8>,
pub header_name: String,
}
pub struct GeneratedCpp(pub Vec<CppFilePair>);
#[derive(Debug)]
pub enum Error {
Bindgen(()),
Parsing(syn::Error),
NoAutoCxxInc,
Conversion(conversion::ConvertError),
NoGenerationRequested,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Bindgen(_) => write!(f, "Bindgen was unable to generate the initial .rs bindings for this file. This may indicate a parsing problem with the C++ headers.")?,
Error::Parsing(err) => write!(f, "The Rust file could not be parsede: {}", err)?,
Error::NoAutoCxxInc => write!(f, "No C++ include directory was provided.")?,
Error::Conversion(err) => write!(f, "autocxx could not generate the requested bindings. {}", err)?,
Error::NoGenerationRequested => write!(f, "No 'generate' or 'generate_pod' directives were found, so we would not generate any Rust bindings despite the inclusion of C++ headers.")?,
}
Ok(())
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
struct GenerationResults {
item_mod: ItemMod,
additional_cpp_generator: Option<CppCodegenResults>,
inc_dirs: Vec<PathBuf>,
}
enum State {
NotGenerated,
ParseOnly,
Generated(Box<GenerationResults>),
}
pub trait RebuildDependencyRecorder: std::fmt::Debug {
fn record_header_file_dependency(&self, filename: &str);
}
#[cfg_attr(doc, aquamarine::aquamarine)]
pub struct IncludeCppEngine {
config: IncludeCppConfig,
state: State,
}
impl Parse for IncludeCppEngine {
fn parse(input: ParseStream) -> ParseResult<Self> {
let config = input.parse::<IncludeCppConfig>()?;
let state = if config.parse_only {
State::ParseOnly
} else {
State::NotGenerated
};
Ok(Self { config, state })
}
}
impl IncludeCppEngine {
pub fn new_from_syn(mac: Macro) -> Result<Self> {
mac.parse_body::<IncludeCppEngine>().map_err(Error::Parsing)
}
fn build_header(&self) -> String {
join(
self.config.inclusions.iter().map(|incl| match incl {
CppInclusion::Define(symbol) => format!("#define {}\n", symbol),
CppInclusion::Header(path) => format!("#include \"{}\"\n", path),
}),
"",
)
}
fn make_bindgen_builder(&self, inc_dirs: &[PathBuf]) -> bindgen::Builder {
let mut builder = bindgen::builder()
.clang_args(&["-x", "c++", "-std=c++14", "-DBINDGEN"])
.derive_copy(false)
.derive_debug(false)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.enable_cxx_namespaces()
.disable_nested_struct_naming()
.generate_inline_functions(true)
.layout_tests(false); for item in known_types::get_initial_blocklist() {
builder = builder.blocklist_item(item);
}
builder = builder.clang_args(inc_dirs.iter().map(|i| {
format!(
"-I{}",
i.to_str().expect("Non-UTF8 content in include path")
)
}));
for a in self.config.type_config.allowlist() {
builder = builder
.allowlist_type(a)
.allowlist_function(a)
.allowlist_var(a);
}
builder
}
fn inject_header_into_bindgen(&self, mut builder: bindgen::Builder) -> bindgen::Builder {
let full_header = self.build_header();
let full_header = format!("{}\n\n{}", KNOWN_TYPES.get_prelude(), full_header,);
builder = builder.header_contents("example.hpp", &full_header);
builder
}
pub fn get_rs_filename(&self) -> String {
let mut hasher = DefaultHasher::new();
self.config.hash(&mut hasher);
let id = hasher.finish();
format!("{}.rs", id)
}
pub fn generate_rs(&self) -> TokenStream2 {
match &self.state {
State::NotGenerated => panic!("Generate first"),
State::Generated(gen_results) => gen_results.item_mod.to_token_stream(),
State::ParseOnly => TokenStream2::new(),
}
}
fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
let bindings = bindings.to_string();
let bindings = format!("mod bindgen {{ {} }}", bindings);
info!("Bindings: {}", bindings);
syn::parse_str::<ItemMod>(&bindings).map_err(Error::Parsing)
}
fn generate_include_list(&self) -> Vec<String> {
let mut include_list = Vec::new();
for incl in &self.config.inclusions {
match incl {
CppInclusion::Header(ref hdr) => {
include_list.push(hdr.clone());
}
CppInclusion::Define(_) => warn!("Currently no way to define! within cxx"),
}
}
include_list
}
pub fn generate(
&mut self,
inc_dirs: Vec<PathBuf>,
dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
) -> Result<()> {
match self.state {
State::ParseOnly => return Ok(()),
State::NotGenerated => {}
State::Generated(_) => panic!("Only call generate once"),
}
if self.config.type_config.allowlist_is_empty() {
return Err(Error::NoGenerationRequested);
}
let mut builder = self.make_bindgen_builder(&inc_dirs);
if let Some(dep_recorder) = dep_recorder {
builder = builder.parse_callbacks(Box::new(AutocxxParseCallbacks(dep_recorder)));
}
let bindings = self
.inject_header_into_bindgen(builder)
.generate()
.map_err(Error::Bindgen)?;
let bindings = self.parse_bindings(bindings)?;
let include_list = self.generate_include_list();
let converter = BridgeConverter::new(&include_list, &self.config.type_config);
let conversion = converter
.convert(
bindings,
self.config.exclude_utilities,
self.config.unsafe_policy.clone(),
self.build_header(),
)
.map_err(Error::Conversion)?;
let mut items = conversion.rs;
let mut new_bindings: ItemMod = parse_quote! {
#[allow(non_snake_case)]
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
mod ffi {
}
};
new_bindings.content.as_mut().unwrap().1.append(&mut items);
info!(
"New bindings:\n{}",
rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
);
self.state = State::Generated(Box::new(GenerationResults {
item_mod: new_bindings,
additional_cpp_generator: conversion.cpp,
inc_dirs,
}));
Ok(())
}
pub fn generate_h_and_cxx(&self) -> Result<GeneratedCpp, cxx_gen::Error> {
let mut files = Vec::new();
match &self.state {
State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
State::NotGenerated => panic!("Call generate() first"),
State::Generated(gen_results) => {
let rs = gen_results.item_mod.to_token_stream();
let opt = cxx_gen::Opt::default();
let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)?;
files.push(CppFilePair {
header: cxx_generated.header,
header_name: "cxxgen.h".to_string(),
implementation: cxx_generated.implementation,
});
match gen_results.additional_cpp_generator {
None => {}
Some(ref additional_cpp) => {
let declarations = format!("#pragma once\n{}", additional_cpp.declarations);
files.push(CppFilePair {
header: declarations.as_bytes().to_vec(),
header_name: "autocxxgen.h".to_string(),
implementation: additional_cpp.definitions.as_bytes().to_vec(),
});
info!("Additional C++ decls:\n{}", declarations);
info!("Additional C++ defs:\n{}", additional_cpp.definitions);
}
}
}
};
Ok(GeneratedCpp(files))
}
pub fn include_dirs(&self) -> &Vec<PathBuf> {
match &self.state {
State::Generated(gen_results) => &gen_results.inc_dirs,
_ => panic!("Must call generate() before include_dirs()"),
}
}
}