use autocxx_parser::file_locations::FileLocationStrategy;
use proc_macro2::TokenStream;
use crate::{ParseError, ParsedFile, RebuildDependencyRecorder};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{ffi::OsStr, io, process};
use std::{fmt::Display, fs::File};
#[derive(Debug)]
pub enum BuilderError {
InvalidCxx(cxx_gen::Error),
ParseError(ParseError),
FileWriteFail(std::io::Error, PathBuf),
NoIncludeCxxMacrosFound,
UnableToCreateDirectory(std::io::Error, PathBuf),
}
impl Display for BuilderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuilderError::ParseError(pe) => write!(f, "Unable to parse .rs file: {}", pe)?,
BuilderError::InvalidCxx(ee) => write!(f, "cxx was unable to understand the code generated by autocxx (likely a bug in autocxx; please report.) {}", ee)?,
BuilderError::FileWriteFail(ee, pb) => write!(f, "Unable to write to {}: {}", pb.to_string_lossy(), ee)?,
BuilderError::NoIncludeCxxMacrosFound => write!(f, "No include_cpp! macro found")?,
BuilderError::UnableToCreateDirectory(ee, pb) => write!(f, "Unable to create directory {}: {}", pb.to_string_lossy(), ee)?,
}
Ok(())
}
}
pub type BuilderBuild = cc::Build;
pub struct BuilderSuccess(pub BuilderBuild, pub Vec<PathBuf>);
pub type BuilderResult = Result<BuilderSuccess, BuilderError>;
pub fn build<P1, I, T>(
rs_file: P1,
autocxx_incs: I,
extra_clang_args: &[&str],
dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
) -> BuilderResult
where
P1: AsRef<Path>,
I: IntoIterator<Item = T>,
T: AsRef<OsStr>,
{
build_to_custom_directory(
rs_file,
autocxx_incs,
extra_clang_args,
None,
dependency_recorder,
)
}
pub fn expect_build<P1, I, T>(
rs_file: P1,
autocxx_incs: I,
extra_clang_args: &[&str],
dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
) -> BuilderSuccess
where
P1: AsRef<Path>,
I: IntoIterator<Item = T>,
T: AsRef<OsStr>,
{
build(rs_file, autocxx_incs, extra_clang_args, dependency_recorder).unwrap_or_else(|err| {
let _ = writeln!(io::stderr(), "\n\nautocxx error: {}\n\n", err);
process::exit(1);
})
}
pub(crate) fn build_to_custom_directory<P1, I, T>(
rs_file: P1,
autocxx_incs: I,
extra_clang_args: &[&str],
custom_gendir: Option<PathBuf>,
dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
) -> BuilderResult
where
P1: AsRef<Path>,
I: IntoIterator<Item = T>,
T: AsRef<OsStr>,
{
rust_version_check();
let gen_location_strategy = match 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", crate::HEADER.as_bytes())?;
let autocxx_inc = build_autocxx_inc(autocxx_incs, &incdir);
gen_location_strategy.set_cargo_env_vars_for_build();
let mut parsed_file = crate::parse_file(rs_file).map_err(BuilderError::ParseError)?;
parsed_file
.resolve_all(autocxx_inc, extra_clang_args, dependency_recorder)
.map_err(BuilderError::ParseError)?;
build_with_existing_parsed_file(parsed_file, cxxdir, incdir, rsdir)
}
pub(crate) fn build_with_existing_parsed_file(
parsed_file: ParsedFile,
cxxdir: PathBuf,
incdir: PathBuf,
rsdir: PathBuf,
) -> BuilderResult {
let mut counter = 0;
let mut builder = cc::Build::new();
builder.cpp(true);
let mut generated_rs = 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()
.map_err(BuilderError::InvalidCxx)?;
for filepair in generated_code.0 {
if let Some(implementation) = &filepair.implementation {
let fname = format!("gen{}.cxx", counter);
counter += 1;
let gen_cxx_path = write_to_file(&cxxdir, &fname, implementation)?;
builder.file(gen_cxx_path);
}
write_to_file(&incdir, &filepair.header_name, &filepair.header)?;
}
}
for include_cpp in parsed_file.get_rs_buildables() {
let fname = include_cpp.get_rs_filename();
let rs = include_cpp.generate_rs();
generated_rs.push(write_rs_to_file(&rsdir, &fname, rs)?);
}
if counter == 0 {
Err(BuilderError::NoIncludeCxxMacrosFound)
} else {
Ok(BuilderSuccess(builder, generated_rs))
}
}
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);
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 write_rs_to_file(
dir: &Path,
filename: &str,
content: TokenStream,
) -> Result<PathBuf, BuilderError> {
write_to_file(dir, filename, content.to_string().as_bytes())
}
fn rust_version_check() {
if !version_check::is_min_version("1.48.0").unwrap_or(false) {
panic!("Rust 1.48 or later is required.")
}
}