use cargo_metadata::{camino::Utf8PathBuf, Message};
use clap::Args;
use log::info;
use std::{collections::BTreeSet, io::Write};
use std::{
fs::File,
io::BufWriter,
process::{Command, Stdio},
};
use serde::{Deserialize, Serialize};
use crate::util::{log_process_error_log, log_process_output};
use crate::CheckError;
#[derive(Debug, Clone, Args)]
pub struct Cli {
#[arg(long)]
pub preparation_path: Option<Utf8PathBuf>,
#[arg(long)]
pub clean: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Preparation {
pub target_build_args: Vec<String>,
}
pub(crate) fn default_preparation_dir() -> Result<Utf8PathBuf, CheckError> {
let mut path = std::env::current_exe().map_err(CheckError::CurrentExe)?;
path.pop();
let path =
Utf8PathBuf::try_from(path.clone()).map_err(|err| CheckError::PathToUtf8(path, err))?;
Ok(path.join("machine-check-preparation"))
}
pub(crate) fn prepare(_: super::Cli, prepare: Cli) -> Result<(), CheckError> {
let preparation_dir = match prepare.preparation_path {
Some(preparation_path) => preparation_path,
None => {
default_preparation_dir()?
}
};
if prepare.clean {
info!(
"Cleaning preparation by removing directory {:?}.",
preparation_dir
);
std::fs::remove_dir_all(preparation_dir.clone())
.map_err(|err| CheckError::RemoveDirAll(preparation_dir, err))?;
return Ok(());
}
info!(
"Preparing sub-artifacts for machine executable building into {:?}.",
preparation_dir
);
let src_dir_path = preparation_dir.join("src");
std::fs::create_dir_all(&src_dir_path)
.map_err(|err| CheckError::CreateDir(src_dir_path.clone(), err))?;
let lib_path = src_dir_path.join("lib.rs");
std::fs::write(lib_path.clone(), "").map_err(|err| CheckError::WriteFile(lib_path, err))?;
let cargo_toml = include_str!("../resources/Prepare_Cargo.toml");
let cargo_toml_path = preparation_dir.join("Cargo.toml");
std::fs::write(&cargo_toml_path, cargo_toml)
.map_err(|err| CheckError::WriteFile(cargo_toml_path.clone(), err))?;
let home_dir = preparation_dir.join("home");
std::fs::create_dir_all(&home_dir)
.map_err(|err| CheckError::CreateDir(home_dir.clone(), err))?;
let target_dir = preparation_dir.join("target");
std::fs::create_dir_all(&target_dir)
.map_err(|err| CheckError::CreateDir(target_dir.clone(), err))?;
let profile = String::from("release");
let build_output = Command::new("cargo")
.arg("build")
.arg("--manifest-path")
.arg(cargo_toml_path)
.arg("--lib")
.arg("--profile")
.arg(&profile)
.arg("--message-format=json-render-diagnostics")
.arg("--target-dir")
.arg(&target_dir)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.env("CARGO_HOME", &home_dir)
.output()
.map_err(CheckError::BuildRun)?;
if !build_output.status.success() {
log_process_error_log("Preparation", &build_output.stderr);
return Err(CheckError::BuildStatus(build_output.status));
}
log_process_output("Preparation", &build_output);
let mut linked_paths = BTreeSet::new();
let mut target_build_args = vec![
String::from("--edition=2021"),
String::from("--error-format=json"),
String::from("--json=artifacts"),
String::from("--crate-type"),
String::from("bin"),
String::from("-C"),
String::from("opt-level=3"),
String::from("-C"),
String::from("embed-bitcode=no"),
String::from("-C"),
String::from("strip=symbols"),
];
let deps_dir = target_dir.join(profile).join("deps");
target_build_args.push(String::from("-L"));
target_build_args.push(format!("dependency={}", deps_dir));
let bytes: &[u8] = &build_output.stdout;
for message in cargo_metadata::Message::parse_stream(bytes) {
let message = message.map_err(CheckError::CargoParse)?;
match message {
Message::BuildScriptExecuted(build_script) => {
linked_paths.extend(build_script.linked_paths);
}
Message::CompilerArtifact(artifact) => {
if matches!(artifact.target.name.as_str(), "mck" | "machine-check-exec") {
for original_path in artifact.filenames {
let extern_target_name = artifact.target.name.replace('-', "_");
target_build_args.push(String::from("--extern"));
target_build_args.push(format!("{}={}", extern_target_name, original_path));
}
}
}
Message::BuildFinished(finished) => {
assert!(finished.success);
}
_ => (),
};
}
for linked_path in linked_paths {
target_build_args.push(String::from("-L"));
target_build_args.push(linked_path.to_string());
}
let preparation = Preparation { target_build_args };
let preparation_path = preparation_dir.join("preparation.json");
let file = File::create(&preparation_path)
.map_err(|err| CheckError::CreateFile(preparation_path.clone(), err))?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, &preparation)?;
writer
.flush()
.map_err(|err| CheckError::Flush(preparation_path, err))?;
info!("Preparation complete.");
Ok(())
}