use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use handlebars::Handlebars;
use pathdiff::diff_paths;
use serde_derive::Serialize;
use serde_json::json;
use super::compile_cmds::LinkCmd;
use super::TranspilerConfig;
use crate::CrateSet;
use crate::ExternCrateDetails;
use crate::PragmaSet;
use crate::{get_module_name, rustfmt};
#[derive(Debug, Copy, Clone)]
pub enum BuildDirectoryContents {
Nothing,
Minimal,
Full,
}
impl FromStr for BuildDirectoryContents {
type Err = ();
fn from_str(s: &str) -> Result<BuildDirectoryContents, ()> {
match s {
"nothing" => Ok(BuildDirectoryContents::Nothing),
"minimal" => Ok(BuildDirectoryContents::Minimal),
"full" => Ok(BuildDirectoryContents::Full),
_ => Err(()),
}
}
}
pub fn get_build_dir(tcfg: &TranspilerConfig, cc_db: &Path) -> PathBuf {
let cc_db_dir = cc_db
.parent() .unwrap();
match &tcfg.output_dir {
Some(dir) => {
let output_dir = dir.clone();
if !output_dir.exists() {
fs::create_dir(&output_dir).unwrap_or_else(|_| {
panic!("couldn't create build directory: {}", output_dir.display())
});
}
output_dir
}
None => cc_db_dir.into(),
}
}
pub struct CrateConfig<'lcmd> {
pub crate_name: String,
pub modules: Vec<PathBuf>,
pub pragmas: PragmaSet,
pub crates: CrateSet,
pub link_cmd: &'lcmd LinkCmd,
}
pub fn emit_build_files<'lcmd>(
tcfg: &TranspilerConfig,
build_dir: &Path,
crate_cfg: Option<CrateConfig<'lcmd>>,
workspace_members: Option<Vec<String>>,
) -> Option<PathBuf> {
let mut reg = Handlebars::new();
reg.register_template_string("Cargo.toml", include_str!("Cargo.toml.hbs"))
.unwrap();
reg.register_template_string("lib.rs", include_str!("lib.rs.hbs"))
.unwrap();
reg.register_template_string("build.rs", include_str!("build.rs.hbs"))
.unwrap();
if !build_dir.exists() {
fs::create_dir_all(build_dir)
.unwrap_or_else(|_| panic!("couldn't create build directory: {}", build_dir.display()));
}
emit_cargo_toml(tcfg, ®, build_dir, &crate_cfg, workspace_members);
if tcfg.translate_valist {
emit_rust_toolchain(tcfg, build_dir);
}
crate_cfg.and_then(|ccfg| {
emit_build_rs(tcfg, ®, build_dir, ccfg.link_cmd);
emit_lib_rs(
tcfg,
®,
build_dir,
ccfg.modules,
ccfg.pragmas,
&ccfg.crates,
)
})
}
#[derive(Serialize)]
struct Module {
path: Option<String>,
name: String,
open: bool,
close: bool,
}
#[derive(Debug, Default)]
struct ModuleTree(BTreeMap<String, ModuleTree>);
impl ModuleTree {
fn linearize(&self, res: &mut Vec<Module>) {
for (name, child) in self.0.iter() {
child.linearize_internal(name, res);
}
}
fn linearize_internal(&self, name: &str, res: &mut Vec<Module>) {
if self.0.is_empty() {
res.push(Module {
name: name.to_string(),
path: None,
open: false,
close: false,
});
} else {
res.push(Module {
name: name.to_string(),
path: None,
open: true,
close: false,
});
self.linearize(res);
res.push(Module {
name: name.to_string(),
path: None,
open: false,
close: true,
});
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum ModuleSubset {
Binaries,
Libraries,
}
fn convert_module_list(
tcfg: &TranspilerConfig,
build_dir: &Path,
mut modules: Vec<PathBuf>,
module_subset: ModuleSubset,
) -> Vec<Module> {
modules.retain(|m| {
let is_binary = tcfg.is_binary(m);
let is_binary_subset = module_subset == ModuleSubset::Binaries;
is_binary == is_binary_subset
});
let mut res = vec![];
let mut module_tree = ModuleTree(BTreeMap::new());
for m in &modules {
match m.strip_prefix(build_dir) {
Ok(relpath) if !tcfg.is_binary(m) => {
let mut cur = &mut module_tree;
for sm in relpath.iter() {
let path = Path::new(sm);
let name = get_module_name(path, true, false, false).unwrap();
cur = cur.0.entry(name).or_default();
}
}
_ => {
let relpath = diff_paths(m, build_dir).unwrap();
let path = Some(relpath.to_str().unwrap().to_string());
let name = get_module_name(m, true, false, false).unwrap();
res.push(Module {
path,
name,
open: false,
close: false,
});
}
}
}
module_tree.linearize(&mut res);
res
}
fn convert_dependencies_list(
crates: CrateSet,
c2rust_dir: Option<&Path>,
) -> Vec<ExternCrateDetails> {
crates
.into_iter()
.map(|dep| dep.with_details(c2rust_dir))
.collect()
}
fn get_lib_rs_file_name(tcfg: &TranspilerConfig) -> &str {
if tcfg.output_dir.is_some() {
"lib.rs"
} else {
"c2rust-lib.rs"
}
}
fn emit_build_rs(
tcfg: &TranspilerConfig,
reg: &Handlebars,
build_dir: &Path,
link_cmd: &LinkCmd,
) -> Option<PathBuf> {
let json = json!({
"libraries": link_cmd.libs,
});
let output = reg.render("build.rs", &json).unwrap();
let output_path = build_dir.join("build.rs");
let path = maybe_write_to_file(&output_path, output, tcfg.overwrite_existing)?;
if !tcfg.disable_rustfmt {
rustfmt(&output_path, build_dir);
}
Some(path)
}
fn emit_lib_rs(
tcfg: &TranspilerConfig,
reg: &Handlebars,
build_dir: &Path,
modules: Vec<PathBuf>,
pragmas: PragmaSet,
crates: &CrateSet,
) -> Option<PathBuf> {
let plugin_args = tcfg
.cross_check_configs
.iter()
.map(|ccc| format!("config_file = \"{}\"", ccc))
.collect::<Vec<String>>()
.join(", ");
let modules = convert_module_list(tcfg, build_dir, modules, ModuleSubset::Libraries);
let crates = convert_dependencies_list(crates.clone(), tcfg.c2rust_dir.as_deref());
let file_name = get_lib_rs_file_name(tcfg);
let rs_xcheck_backend = tcfg.cross_check_backend.replace('-', "_");
let json = json!({
"lib_rs_file": file_name,
"reorganize_definitions": tcfg.reorganize_definitions,
"translate_valist": tcfg.translate_valist,
"cross_checks": tcfg.cross_checks,
"cross_check_backend": rs_xcheck_backend,
"plugin_args": plugin_args,
"modules": modules,
"pragmas": pragmas,
"crates": crates,
});
let output_path = build_dir.join(file_name);
let output = reg.render("lib.rs", &json).unwrap();
let path = maybe_write_to_file(&output_path, output, tcfg.overwrite_existing)?;
if !tcfg.disable_rustfmt {
rustfmt(&output_path, build_dir);
}
Some(path)
}
fn emit_rust_toolchain(tcfg: &TranspilerConfig, build_dir: &Path) {
let output_path = build_dir.join("rust-toolchain.toml");
let output = include_str!("generated-rust-toolchain.toml").to_string();
maybe_write_to_file(&output_path, output, tcfg.overwrite_existing);
}
fn emit_cargo_toml<'lcmd>(
tcfg: &TranspilerConfig,
reg: &Handlebars,
build_dir: &Path,
crate_cfg: &Option<CrateConfig<'lcmd>>,
workspace_members: Option<Vec<String>>,
) {
let mut json = json!({
"is_workspace": workspace_members.is_some(),
"is_crate": crate_cfg.is_some(),
"workspace_members": workspace_members.unwrap_or_default(),
});
if let Some(ccfg) = crate_cfg {
let binaries = convert_module_list(
tcfg,
build_dir,
ccfg.modules.to_owned(),
ModuleSubset::Binaries,
);
let dependencies =
convert_dependencies_list(ccfg.crates.clone(), tcfg.c2rust_dir.as_deref());
let crate_json = json!({
"crate_name": ccfg.crate_name,
"crate_rust_name": ccfg.crate_name.replace('-', "_"),
"crate_types": ccfg.link_cmd.r#type.as_cargo_types(),
"is_library": ccfg.link_cmd.r#type.is_library(),
"lib_rs_file": get_lib_rs_file_name(tcfg),
"binaries": binaries,
"cross_checks": tcfg.cross_checks,
"cross_check_backend": tcfg.cross_check_backend,
"dependencies": dependencies,
});
json.as_object_mut().unwrap().extend(
crate_json
.as_object()
.cloned() .unwrap(),
);
}
let file_name = "Cargo.toml";
let output_path = build_dir.join(file_name);
let output = reg.render(file_name, &json).unwrap();
maybe_write_to_file(&output_path, output, tcfg.overwrite_existing);
}
fn maybe_write_to_file(output_path: &Path, output: String, overwrite: bool) -> Option<PathBuf> {
if output_path.exists() && !overwrite {
eprintln!("Skipping existing file {}", output_path.display());
return None;
}
let mut file = match File::create(output_path) {
Ok(file) => file,
Err(e) => panic!("Unable to open file for writing: {}", e),
};
match file.write_all(output.as_bytes()) {
Ok(()) => (),
Err(e) => panic!("Unable to write translation to file: {}", e),
};
Some(PathBuf::from(output_path))
}