extern crate handlebars;
extern crate pathdiff;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use self::handlebars::Handlebars;
use self::pathdiff::diff_paths;
use serde_json::json;
use super::TranspilerConfig;
use PragmaSet;
use CrateSet;
#[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).expect(&format!(
"couldn't create build directory: {}",
output_dir.display()
));
}
output_dir
}
None => cc_db_dir.into(),
}
}
pub fn emit_build_files(
tcfg: &TranspilerConfig,
build_dir: &Path,
modules: Vec<PathBuf>,
pragmas: PragmaSet,
crates: CrateSet,
) -> 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();
emit_cargo_toml(tcfg, ®, &build_dir, &crates);
if tcfg.translate_valist {
emit_rust_toolchain(tcfg, &build_dir);
}
emit_build_rs(tcfg, ®, &build_dir);
emit_lib_rs(tcfg, ®, &build_dir, modules, pragmas, &crates)
}
#[derive(Serialize)]
struct Module {
path: String,
name: String,
}
fn get_root_rs_file_name(tcfg: &TranspilerConfig) -> &str {
match (&tcfg.main, &tcfg.output_dir) {
(Some(_), None) => "c2rust-main.rs",
(None, None) => "c2rust-lib.rs",
(Some(_), Some(_)) => "main.rs",
(None, Some(_)) => "lib.rs",
}
}
fn get_module_name(main: &Option<String>) -> Option<String> {
if let Some(ref name) = main {
return Some(name.replace(".", "_"));
}
None
}
fn emit_build_rs(tcfg: &TranspilerConfig, reg: &Handlebars, build_dir: &Path) -> Option<PathBuf> {
let json = json!({});
let output = reg.render("build.rs", &json).unwrap();
let output_path = build_dir.join("build.rs");
maybe_write_to_file(&output_path, output, tcfg.overwrite_existing)
}
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 = modules
.iter()
.map(|m| {
let relpath = diff_paths(m, build_dir).unwrap();
let name = m.file_stem().unwrap().to_str().unwrap().replace(".", "_");
Module {
path: relpath.to_str().unwrap().to_string(),
name: name.to_string(),
}
})
.collect::<Vec<_>>();
let file_name = get_root_rs_file_name(tcfg);
let rs_xcheck_backend = tcfg.cross_check_backend.replace("-", "_");
let json = json!({
"root_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,
"main_module": get_module_name(&tcfg.main),
"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();
maybe_write_to_file(&output_path, output, tcfg.overwrite_existing)
}
fn emit_rust_toolchain(tcfg: &TranspilerConfig, build_dir: &Path) {
let output_path = build_dir.join("rust-toolchain");
let output = include_str!("../../rust-toolchain").to_string();
maybe_write_to_file(&output_path, output, tcfg.overwrite_existing);
}
fn emit_cargo_toml(
tcfg: &TranspilerConfig,
reg: &Handlebars,
build_dir: &Path,
crates: &CrateSet
) {
let json = json!({
"crate_name": tcfg.output_dir.as_ref().and_then(
|x| x.file_name().map(|x| x.to_string_lossy())
).unwrap_or("c2rust".into()),
"root_rs_file": get_root_rs_file_name(tcfg),
"main_module": get_module_name(&tcfg.main),
"cross_checks": tcfg.cross_checks,
"cross_check_backend": tcfg.cross_check_backend,
"c2rust_bitfields": crates.contains("c2rust_bitfields"),
"f128": crates.contains("f128"),
});
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))
}