mod conversion;
mod cxxbridge;
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::{IncludeCppConfig, UnsafePolicy};
use conversion::BridgeConverter;
use parse_callbacks::AutocxxParseCallbacks;
use parse_file::CppBuildable;
use proc_macro2::TokenStream as TokenStream2;
use std::{
collections::hash_map::DefaultHasher,
fmt::Display,
hash::{Hash, Hasher},
path::PathBuf,
};
use std::{
fs::File,
io::prelude::*,
path::Path,
process::{Command, Stdio},
};
use tempfile::NamedTempFile;
use quote::ToTokens;
use syn::Result as ParseResult;
use syn::{
parse::{Parse, ParseStream},
parse_quote, ItemMod, Macro,
};
use itertools::{join, Itertools};
use known_types::known_types;
use log::info;
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;
#[derive(Clone)]
pub struct CppFilePair {
pub header: Vec<u8>,
pub implementation: Option<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),
}
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)?,
}
Ok(())
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
struct GenerationResults {
item_mod: ItemMod,
cpp: Option<CppFilePair>,
inc_dirs: Vec<PathBuf>,
}
enum State {
NotGenerated,
ParseOnly,
Generated(Box<GenerationResults>),
}
const AUTOCXX_CLANG_ARGS: &[&str; 4] = &["-x", "c++", "-std=c++14", "-DBINDGEN"];
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(|path| format!("#include \"{}\"\n", path)),
"",
)
}
fn make_bindgen_builder(
&self,
inc_dirs: &[PathBuf],
extra_clang_args: &[&str],
) -> bindgen::Builder {
let mut builder = bindgen::builder()
.clang_args(make_clang_args(inc_dirs, extra_clang_args))
.derive_copy(false)
.derive_debug(false)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.enable_cxx_namespaces()
.generate_inline_functions(true)
.layout_tests(false); for item in known_types().get_initial_blocklist() {
builder = builder.blocklist_item(item);
}
if let Some(allowlist) = self.config.bindgen_allowlist() {
for a in allowlist {
builder = builder
.allowlist_type(&a)
.allowlist_function(&a)
.allowlist_var(&a);
}
}
log::info!(
"Bindgen flags would be: {}",
builder
.command_line_flags()
.into_iter()
.map(|f| format!("\"{}\"", f))
.join(" ")
);
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(),
}
}
pub fn get_mod_name(&self) -> String {
self.config.get_mod_name().to_string()
}
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)
}
pub fn generate(
&mut self,
inc_dirs: Vec<PathBuf>,
extra_clang_args: &[&str],
dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
) -> Result<()> {
match self.state {
State::ParseOnly => return Ok(()),
State::NotGenerated => {}
State::Generated(_) => panic!("Only call generate once"),
}
let mod_name = self.config.get_mod_name();
let mut builder = self.make_bindgen_builder(&inc_dirs, &extra_clang_args);
if let Some(dep_recorder) = dep_recorder {
builder = builder.parse_callbacks(Box::new(AutocxxParseCallbacks(dep_recorder)));
}
let header_contents = self.build_header();
self.dump_header_if_so_configured(&header_contents, &inc_dirs, &extra_clang_args);
let header_and_prelude = format!("{}\n\n{}", known_types().get_prelude(), header_contents);
builder = builder.header_contents("example.hpp", &header_and_prelude);
let bindings = builder.generate().map_err(Error::Bindgen)?;
let bindings = self.parse_bindings(bindings)?;
let converter = BridgeConverter::new(&self.config.inclusions, &self.config);
let conversion = converter
.convert(bindings, self.config.unsafe_policy.clone(), header_contents)
.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 #mod_name {
}
};
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,
cpp: conversion.cpp,
inc_dirs,
}));
Ok(())
}
fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
match &self.state {
State::Generated(gen_results) => gen_results.inc_dirs.iter(),
_ => panic!("Must call generate() before include_dirs()"),
}
}
fn dump_header_if_so_configured(
&self,
header: &str,
inc_dirs: &[PathBuf],
extra_clang_args: &[&str],
) {
if let Ok(output_path) = std::env::var("AUTOCXX_PREPROCESS") {
let suffix = ALL_KNOWN_SYSTEM_HEADERS
.iter()
.map(|hdr| format!("#include <{}>\n", hdr))
.join("\n");
let input = format!("/*\nautocxx config:\n\n{:?}\n\nend autocxx config.\nautocxx preprocessed input:\n*/\n\n{}\n\n/* autocxx: extra headers added below for completeness. */\n\n{}\n{}\n",
self.config, header, suffix, cxx_gen::HEADER);
let mut tf = NamedTempFile::new().unwrap();
write!(tf, "{}", input).unwrap();
let tp = tf.into_temp_path();
preprocess(&tp, &PathBuf::from(output_path), inc_dirs, extra_clang_args).unwrap();
}
}
}
static ALL_KNOWN_SYSTEM_HEADERS: &[&str] = &[
"memory",
"string",
"algorithm",
"array",
"cassert",
"cstddef",
"cstdint",
"cstring",
"exception",
"functional",
"initializer_list",
"iterator",
"memory",
"new",
"stdexcept",
"type_traits",
"utility",
"vector",
"sys/types.h",
];
pub fn do_cxx_cpp_generation(rs: TokenStream2) -> Result<CppFilePair, cxx_gen::Error> {
let opt = cxx_gen::Opt::default();
let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)?;
Ok(CppFilePair {
header: cxx_generated.header,
header_name: "cxxgen.h".into(),
implementation: Some(cxx_generated.implementation),
})
}
impl CppBuildable for IncludeCppEngine {
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();
files.push(do_cxx_cpp_generation(rs)?);
if let Some(cpp_file_pair) = &gen_results.cpp {
files.push(cpp_file_pair.clone());
}
}
};
Ok(GeneratedCpp(files))
}
}
pub fn make_clang_args<'a>(
incs: &'a [PathBuf],
extra_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
AUTOCXX_CLANG_ARGS
.iter()
.map(|s| s.to_string())
.chain(incs.iter().map(|i| format!("-I{}", i.to_str().unwrap())))
.chain(extra_args.iter().map(|s| s.to_string()))
}
pub fn preprocess(
listing_path: &Path,
preprocess_path: &Path,
incs: &[PathBuf],
extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
let mut cmd = Command::new(get_clang_path());
cmd.arg("-E");
cmd.arg("-C");
cmd.args(make_clang_args(incs, extra_clang_args));
cmd.arg(listing_path.to_str().unwrap());
cmd.stderr(Stdio::inherit());
let result = cmd.output().expect("failed to execute clang++");
if !result.status.success() {
panic!("failed to preprocess");
}
let mut file = File::create(preprocess_path)?;
file.write_all(&result.stdout)?;
Ok(())
}
pub fn get_clang_path() -> String {
std::env::var("CLANG_PATH")
.or_else(|_| std::env::var("CXX"))
.unwrap_or_else(|_| "clang++".to_string())
}