use autocxx_parser::file_locations::FileLocationStrategy;
use miette::Diagnostic;
use thiserror::Error;
use crate::{generate_rs_single, CodegenOptions};
use crate::{get_cxx_header_bytes, CppCodegenOptions, ParseError, RebuildDependencyRecorder};
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fs::File;
use std::io::Write;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
#[derive(Error, Diagnostic, Debug)]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub enum BuilderError {
#[error("cxx couldn't handle our generated bindings - could be a bug in autocxx: {0}")]
InvalidCxx(cxx_gen::Error),
#[error(transparent)]
#[diagnostic(transparent)]
ParseError(ParseError),
#[error("we couldn't write the generated code to disk at {1}: {0}")]
FileWriteFail(std::io::Error, PathBuf),
#[error("no include_cpp! macro was found")]
NoIncludeCxxMacrosFound,
#[error("could not create a directory {1}: {0}")]
UnableToCreateDirectory(std::io::Error, PathBuf),
}
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub type BuilderBuild = cc::Build;
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub struct BuilderSuccess(pub BuilderBuild, pub Vec<PathBuf>, pub Vec<PathBuf>);
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub type BuilderResult = Result<BuilderSuccess, BuilderError>;
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub trait BuilderContext {
fn setup() {}
fn get_dependency_recorder() -> Option<Box<dyn RebuildDependencyRecorder>>;
}
#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
pub struct Builder<'a, BuilderContext> {
rs_file: PathBuf,
autocxx_incs: Vec<OsString>,
extra_clang_args: Vec<String>,
dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
custom_gendir: Option<PathBuf>,
auto_allowlist: bool,
codegen_options: CodegenOptions<'a>,
ctx: PhantomData<BuilderContext>,
}
impl<CTX: BuilderContext> Builder<'_, CTX> {
pub fn new(
rs_file: impl AsRef<Path>,
autocxx_incs: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Self {
CTX::setup();
Self {
rs_file: rs_file.as_ref().to_path_buf(),
autocxx_incs: autocxx_incs
.into_iter()
.map(|s| s.as_ref().to_os_string())
.collect(),
extra_clang_args: Vec::new(),
dependency_recorder: CTX::get_dependency_recorder(),
custom_gendir: None,
auto_allowlist: false,
codegen_options: CodegenOptions::default(),
ctx: PhantomData,
}
}
pub fn extra_clang_args(mut self, extra_clang_args: &[&str]) -> Self {
self.extra_clang_args = extra_clang_args.iter().map(|s| s.to_string()).collect();
self
}
pub fn custom_gendir(mut self, custom_gendir: PathBuf) -> Self {
self.custom_gendir = Some(custom_gendir);
self
}
pub fn cpp_codegen_options<F>(mut self, modifier: F) -> Self
where
F: FnOnce(&mut CppCodegenOptions),
{
modifier(&mut self.codegen_options.cpp_codegen_options);
self
}
pub fn auto_allowlist(mut self, do_it: bool) -> Self {
self.auto_allowlist = do_it;
self
}
#[doc(hidden)]
pub fn force_wrapper_generation(mut self, do_it: bool) -> Self {
self.codegen_options.force_wrapper_gen = do_it;
self
}
pub fn suppress_system_headers(mut self, do_it: bool) -> Self {
self.codegen_options
.cpp_codegen_options
.suppress_system_headers = do_it;
self
}
pub fn cxx_impl_annotations(mut self, cxx_impl_annotations: Option<String>) -> Self {
self.codegen_options
.cpp_codegen_options
.cxx_impl_annotations = cxx_impl_annotations;
self
}
pub fn build(self) -> Result<BuilderBuild, BuilderError> {
self.build_listing_files().map(|r| r.0)
}
pub fn build_listing_files(self) -> Result<BuilderSuccess, BuilderError> {
let clang_args = &self
.extra_clang_args
.iter()
.map(|s| &s[..])
.collect::<Vec<_>>();
rust_version_check();
let gen_location_strategy = match self.custom_gendir {
None => FileLocationStrategy::new(),
Some(custom_dir) => FileLocationStrategy::Custom(custom_dir),
};
let incdir = gen_location_strategy.get_include_dir();
ensure_created(&incdir)?;
let cxxdir = gen_location_strategy.get_cxx_dir();
ensure_created(&cxxdir)?;
let rsdir = gen_location_strategy.get_rs_dir();
ensure_created(&rsdir)?;
write_to_file(
&incdir,
"cxx.h",
&get_cxx_header_bytes(
self.codegen_options
.cpp_codegen_options
.suppress_system_headers,
),
)?;
let autocxx_inc = build_autocxx_inc(self.autocxx_incs, &incdir);
gen_location_strategy.set_cargo_env_vars_for_build();
let mut parsed_file = crate::parse_file(self.rs_file, self.auto_allowlist)
.map_err(BuilderError::ParseError)?;
parsed_file
.resolve_all(
autocxx_inc,
clang_args,
self.dependency_recorder,
&self.codegen_options,
)
.map_err(BuilderError::ParseError)?;
let mut counter = 0;
let mut builder = cc::Build::new();
builder.cpp(true);
if std::env::var_os("AUTOCXX_ASAN").is_some() {
builder.flag_if_supported("-fsanitize=address");
}
let mut generated_rs = Vec::new();
let mut generated_cpp = Vec::new();
builder.includes(parsed_file.include_dirs());
for include_cpp in parsed_file.get_cpp_buildables() {
let generated_code = include_cpp
.generate_h_and_cxx(&self.codegen_options.cpp_codegen_options)
.map_err(BuilderError::InvalidCxx)?;
for filepair in generated_code.0 {
let fname = format!("gen{counter}.cxx");
counter += 1;
if let Some(implementation) = &filepair.implementation {
let gen_cxx_path = write_to_file(&cxxdir, &fname, implementation)?;
builder.file(&gen_cxx_path);
generated_cpp.push(gen_cxx_path);
}
write_to_file(&incdir, &filepair.header_name, &filepair.header)?;
generated_cpp.push(incdir.join(filepair.header_name));
}
}
for rs_output in parsed_file.get_rs_outputs() {
let rs = generate_rs_single(rs_output);
generated_rs.push(write_to_file(&rsdir, &rs.filename, rs.code.as_bytes())?);
}
if counter == 0 {
Err(BuilderError::NoIncludeCxxMacrosFound)
} else {
Ok(BuilderSuccess(builder, generated_rs, generated_cpp))
}
}
}
fn ensure_created(dir: &Path) -> Result<(), BuilderError> {
std::fs::create_dir_all(dir)
.map_err(|e| BuilderError::UnableToCreateDirectory(e, dir.to_path_buf()))
}
fn build_autocxx_inc<I, T>(paths: I, extra_path: &Path) -> Vec<PathBuf>
where
I: IntoIterator<Item = T>,
T: AsRef<OsStr>,
{
paths
.into_iter()
.map(|p| PathBuf::from(p.as_ref()))
.chain(std::iter::once(extra_path.to_path_buf()))
.collect()
}
fn write_to_file(dir: &Path, filename: &str, content: &[u8]) -> Result<PathBuf, BuilderError> {
let path = dir.join(filename);
if let Ok(existing_contents) = std::fs::read(&path) {
if existing_contents == content {
return Ok(path);
}
}
try_write_to_file(&path, content).map_err(|e| BuilderError::FileWriteFail(e, path.clone()))?;
Ok(path)
}
fn try_write_to_file(path: &Path, content: &[u8]) -> std::io::Result<()> {
let mut f = File::create(path)?;
f.write_all(content)
}
fn rust_version_check() {
if !version_check::is_min_version("1.54.0").unwrap_or(false) {
panic!("Rust 1.54 or later is required.")
}
}