use crate::adapter::rust::manifest::{CargoToml, TomlBuild};
use crate::adapter::rust::types::PlannedCompilationUnit;
use crate::core::hash::Sha256;
use anyhow::{bail, Context, Result};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
pub use super::build_script_directives::{
compute_build_script_key, detect_default_target_triple, get_target_cfgs, BuildScriptDirectives,
};
use super::dag_unit_build_script::script_inputs::collect_script_inputs;
pub fn resolve_build_script_path(unit: &PlannedCompilationUnit) -> Option<PathBuf> {
let pkg_dir = unit.manifest_path.parent().unwrap_or(Path::new("."));
let pkg_toml = CargoToml::from_file(&unit.manifest_path).unwrap_or_default();
match pkg_toml.package.as_ref().and_then(|p| p.build.as_ref()) {
Some(TomlBuild::Disabled(false)) => None,
Some(TomlBuild::Path(ref rel)) => Some(pkg_dir.join(rel)).filter(|p| p.is_file()),
_ => [
pkg_dir.join("build.rs"),
pkg_dir.join("build").join("build.rs"),
]
.into_iter()
.find(|p| p.is_file()),
}
}
pub fn try_restore_build_script(
unit: &PlannedCompilationUnit,
release: bool,
features: &[String],
cas_artifacts_dir: Option<&Path>,
extern_crates: &[(String, PathBuf)],
extra_env: Option<&HashMap<String, String>>,
) -> Result<Option<BuildScriptDirectives>> {
let pkg_dir = unit.manifest_path.parent().unwrap_or(Path::new("."));
let pkg_toml = CargoToml::from_file(&unit.manifest_path).unwrap_or_default();
let Some(build_rs) = resolve_build_script_path(unit) else {
return Ok(Some(BuildScriptDirectives::default()));
};
let target = detect_default_target_triple();
let clean_crate = unit.package_name.replace('-', "_");
let profile_name = if release { "release" } else { "debug" };
let build_key = compute_build_script_key(
unit,
&build_rs,
release,
features,
&target,
extra_env,
extern_crates,
)?;
if let Some(directives) = super::build_script_cas::try_restore_build_script_cas(
cas_artifacts_dir,
&build_key,
pkg_dir,
&pkg_toml,
profile_name,
&clean_crate,
None,
) {
crate::core::metrics::record_build_script_hit();
if std::env::var_os("MITTS_LOG_COMPILE").is_some() {
eprintln!(
"[mitts::build_script] hit {} v{} key={}",
unit.package_name,
unit.version,
&build_key[..build_key.len().min(16)]
);
}
return Ok(Some(directives));
}
Ok(None)
}
pub fn execute_build_script(
unit: &PlannedCompilationUnit,
staging_dir: &Path,
release: bool,
features: &[String],
cas_artifacts_dir: Option<&Path>,
extern_crates: &[(String, PathBuf)],
extra_env: Option<&HashMap<String, String>>,
) -> Result<BuildScriptDirectives> {
let pkg_dir = unit.manifest_path.parent().unwrap_or(Path::new("."));
let pkg_toml = CargoToml::from_file(&unit.manifest_path).unwrap_or_default();
let Some(build_rs) = resolve_build_script_path(unit) else {
return Ok(BuildScriptDirectives::default());
};
if let Some(directives) = try_restore_build_script(
unit,
release,
features,
cas_artifacts_dir,
extern_crates,
extra_env,
)? {
return Ok(directives);
}
let target = detect_default_target_triple();
let clean_crate = unit.package_name.replace('-', "_");
let profile_name = if release { "release" } else { "debug" };
let build_key = compute_build_script_key(
unit,
&build_rs,
release,
features,
&target,
extra_env,
extern_crates,
)?;
crate::core::metrics::record_build_script_miss();
if std::env::var_os("MITTS_LOG_COMPILE").is_some() {
eprintln!(
"[mitts::build_script] miss {} v{} key={}",
unit.package_name,
unit.version,
&build_key[..build_key.len().min(16)]
);
}
let out_dir = staging_dir
.join(profile_name)
.join("build")
.join(&clean_crate)
.join("out");
fs::create_dir_all(&out_dir)?;
let bin_path = staging_dir.join("build_script_build");
super::dag_unit_build_script::compile::compile_build_script_binary(
super::dag_unit_build_script::compile::BuildScriptCompileArgs {
unit,
staging_dir,
pkg_dir,
pkg_toml: &pkg_toml,
build_rs: &build_rs,
bin_path: &bin_path,
release,
features,
cas_artifacts_dir,
extern_crates,
extra_env,
target: &target,
},
)?;
let script_inputs = collect_script_inputs(&bin_path.with_extension("d"), pkg_dir, extra_env);
let mut run_cmd = super::build_script_env::configure_build_script_run_cmd(
&bin_path, pkg_dir, unit, &pkg_toml, &out_dir, &target, release, features, extra_env,
);
let run_status = run_cmd.output().context("Failed to execute build.rs")?;
if !run_status.status.success() {
let err = String::from_utf8_lossy(&run_status.stderr);
bail!("build.rs failed for '{}': {}", unit.package_name, err);
}
let stdout_str = String::from_utf8_lossy(&run_status.stdout);
let mut directives = BuildScriptDirectives::parse_stdout(&stdout_str);
directives.out_dir = Some(out_dir.clone());
directives.links = pkg_toml.package.as_ref().and_then(|p| p.links.clone());
for var in &directives.rerun_if_env_changed {
if let Ok(val) = std::env::var(var) {
directives.envs.insert(var.clone(), val);
}
}
for file_rel in &directives.rerun_if_changed {
let p = if Path::new(file_rel).is_absolute() {
PathBuf::from(file_rel)
} else {
pkg_dir.join(file_rel)
};
let h = Sha256::digest_path_recursive(&p).unwrap_or_else(|_| "<missing>".to_string());
directives
.rerun_if_changed_hashes
.insert(file_rel.clone(), h);
}
let Some(script_inputs) = script_inputs else {
return Ok(directives);
};
directives.script_inputs = script_inputs;
if directives.rerun_if_changed.is_empty()
&& directives.rerun_if_env_changed.is_empty()
&& super::build_script_keys::build_script_package_is_mutable(unit, pkg_dir)
{
match super::build_script_keys::package_inputs_digest(pkg_dir) {
Ok(digest) => directives.package_inputs_digest = Some(digest),
Err(_) => return Ok(directives),
}
}
super::build_script_cas::save_build_script_cas(
cas_artifacts_dir,
&build_key,
&mut directives,
profile_name,
&clean_crate,
&out_dir,
);
Ok(directives)
}