use super::planner::explain_plan;
use super::types::{
BuildProgressEvent, EffectivePlanExplanation, RustAdapterError, RustBuildOptions,
};
use crate::adapter::RustAdapter;
use crate::core::action::{Action, ActionKind, ActionResult, BuildPlan};
use crate::core::build_lock::{
FastBuildLock, FreshnessCheckArgs, FreshnessResult, RecordStateArgs, RecordStateOptions,
};
use crate::core::build_logger::BuildLogger;
use crate::core::hash::Sha256;
use anyhow::Result;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
pub fn compute_options_hash(
opt: &RustBuildOptions,
target_dir: &Path,
manifest_path: &Path,
) -> String {
let mut bin_targets = opt.bin_targets.clone();
bin_targets.sort();
let mut exclude = opt.exclude.clone();
exclude.sort();
let mut test_targets = opt.test_targets.clone();
test_targets.sort();
let mut features = opt.features.clone();
features.sort();
let payload = format!(
"manifest={}|target_dir={}|start_dir={:?}|release={}|mode={:?}|no_cache={}|native={}|cargo={}|pkg={:?}|exclude={:?}|bins={:?}|all_bins={}|lib_only={}|doc_only={}|tests={:?}|all_tests={}|features={:?}|all_features={}|no_default_features={}|extra_args={:?}|cache_dir={:?}",
manifest_path.display(),
target_dir.display(),
opt.start_dir,
opt.release,
opt.mode,
opt.no_cache,
opt.native,
opt.cargo,
opt.package,
exclude,
bin_targets,
opt.all_bins,
opt.lib_only,
opt.doc_only,
test_targets,
opt.all_tests,
features,
opt.all_features,
opt.no_default_features,
opt.extra_args,
opt.cache_dir,
);
Sha256::digest(payload.as_bytes())
}
pub fn build(opt: &RustBuildOptions) -> Result<(BuildPlan, ActionResult)> {
let manifest_path = RustAdapter::resolve_manifest(opt)?;
let target_dir = RustAdapter::resolve_target_dir(&manifest_path, opt)?;
let _ = std::fs::create_dir_all(&target_dir);
let _ = std::fs::write(target_dir.join(".mitts-target"), "mitts-target-v1\n");
let clean_lock_path = target_dir
.parent()
.unwrap_or(&target_dir)
.join(".mitts-clean.lock");
let _clean_guard = crate::core::lock::FileLock::acquire_shared(&clean_lock_path).ok();
let _gc_lease = crate::core::gc::CacheLease::acquire(opt.cache_dir.as_deref());
let project_dir = manifest_path.parent().unwrap_or(Path::new("."));
let log_path = target_dir.join("build.log");
let mut logger = BuildLogger::new(Some(log_path));
let profile = match (opt.mode, opt.release) {
(super::types::BuildMode::Check, true) => "check-release",
(super::types::BuildMode::Check, false) => "check",
(super::types::BuildMode::Clippy, true) => "clippy-release",
(super::types::BuildMode::Clippy, false) => "clippy",
(super::types::BuildMode::Test, true) => "test-release",
(super::types::BuildMode::Test, false) => "test",
(super::types::BuildMode::Build, true) => "release",
(super::types::BuildMode::Build, false) => "dev",
};
for arg in &opt.extra_args {
if arg.starts_with("--unsupported") {
return Err(RustAdapterError::UnsupportedOption(arg.clone()).into());
}
}
let toolchain = logger.measure("toolchain", "Detected rustc toolchain", || {
RustAdapter::detect_toolchain()
})?;
let options_hash = compute_options_hash(opt, &target_dir, &manifest_path);
if !opt.no_cache
&& opt.fast_lock.unwrap_or(opt.cache_dir.is_none())
&& matches!(
opt.mode,
super::types::BuildMode::Build | super::types::BuildMode::Check
)
{
let lock_start = std::time::Instant::now();
let (freshness, root_env, root_link_args) =
FastBuildLock::check_freshness_with_runtime_context(
&FreshnessCheckArgs {
project_dir,
profile,
package: &opt.package,
features: &opt.features,
no_default_features: opt.no_default_features,
all_features: opt.all_features,
toolchain_version: &toolchain.version,
},
Some(&target_dir),
Some(&options_hash),
true,
);
match freshness {
FreshnessResult::Fresh {
action_count,
root_binary,
stderr,
} => {
let dur = lock_start.elapsed();
logger.record_phase(
"lock_check",
dur,
&format!("Fast build lock HIT: {} actions up-to-date", action_count),
);
let _ = logger.write_trace(profile, 0, "Fast lock hit (0ms no-op)");
eprintln!(
"[mitts::stats] compiled=0 cached=0 tainted=0 total={} fast_lock=1",
action_count
);
if std::env::var("MITTS_STATS").as_deref() == Ok("json") {
eprintln!(
r#"{{"compiled": 0, "cached": 0, "total": {}, "fast_lock": 1}}"#,
action_count
);
}
if let Some(ref ref_stderr) = stderr {
if !ref_stderr.is_empty() {
if let Some(ref cb) = opt.progress {
cb.0(BuildProgressEvent::CompilerMessage(ref_stderr.clone()));
}
}
}
let mut actions = Vec::new();
if let Some(ref bin) = root_binary {
actions.push(Action {
id: "root-bin".to_string(),
kind: ActionKind::CompileBin,
description: format!("Existing bin {}", bin.display()),
toolchain: toolchain.clone(),
inputs: Vec::new(),
outputs: vec![bin.clone()],
args: Vec::new(),
env: root_env,
dependencies: Vec::new(),
});
}
let mut unit_link_args = BTreeMap::new();
if !root_link_args.is_empty() {
unit_link_args.insert("root-bin".to_string(), root_link_args);
}
let plan = BuildPlan {
actions,
target_directory: target_dir,
artifacts: Default::default(),
unit_cfgs: Default::default(),
unit_link_args,
..Default::default()
};
let result = ActionResult {
action_id: "fast-lock-hit".to_string(),
exit_code: 0,
success: true,
stdout: String::new(),
stderr: stderr.unwrap_or_default(),
cached: true,
};
return Ok((plan, result));
}
FreshnessResult::Stale(ref reason) => {
let dur = lock_start.elapsed();
logger.record_phase("lock_check", dur, &format!("Lock miss / stale: {}", reason));
}
}
}
let is_verbose = opt.verbose > 0 || std::env::var("MITTS_VERBOSE").is_ok();
if is_verbose {
println!(
"[mitts::plan] Planning compilation from {}",
manifest_path.display()
);
}
let explanation = logger.measure("plan", "native manifest & lockfile planning", || {
explain_plan(opt)
})?;
if is_verbose {
println!(
"[mitts::plan] Planned {} units. Deriving action keys...",
explanation.units.len()
);
}
let key_deriver = logger.measure("keys", "Action key derivation and input hashing", || {
super::key::derive_unit_action_key_context(opt, &explanation)
})?;
let unit_keys = (*key_deriver.initial_base_keys).clone();
if is_verbose {
println!(
"[mitts::dag] Starting parallel DAG execution for {} units...",
unit_keys.len()
);
}
let (plan, result) =
logger.measure("compile", "native parallel rustc DAG execution", || {
super::dag_runner::run_native_dag_build(opt, &explanation, &key_deriver)
})?;
record_state_and_trace(
project_dir,
&target_dir,
&options_hash,
profile,
opt,
&explanation,
&toolchain,
&plan,
&result,
&mut logger,
);
Ok((plan, result))
}
#[allow(clippy::too_many_arguments)]
fn record_state_and_trace(
project_dir: &Path,
target_dir: &Path,
options_hash: &str,
profile: &str,
opt: &RustBuildOptions,
explanation: &EffectivePlanExplanation,
toolchain: &crate::core::action::ToolchainInfo,
plan: &BuildPlan,
result: &ActionResult,
logger: &mut BuildLogger,
) {
if result.success
&& !opt.no_cache
&& opt.fast_lock.unwrap_or(opt.cache_dir.is_none())
&& matches!(
opt.mode,
super::types::BuildMode::Build | super::types::BuildMode::Check
)
{
let mut outputs = Vec::new();
let mut bin_candidates = Vec::new();
for action in &plan.actions {
for out in &action.outputs {
outputs.push(out.clone());
}
if action.kind == ActionKind::CompileBin {
if let Some(first) = action.outputs.first() {
bin_candidates.push(first.clone());
}
}
}
bin_candidates.sort();
let root_binary = if let Some(req_bin) = opt.bin_targets.first() {
bin_candidates
.iter()
.find(|p| {
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.trim_end_matches(".exe") == req_bin)
})
.cloned()
.or_else(|| bin_candidates.into_iter().next())
} else {
bin_candidates.into_iter().next()
};
let mut extra_pkg_dirs: Vec<PathBuf> = Vec::new();
let ws_root = super::compiler_resolve::find_workspace_root(&explanation.manifest_path);
if ws_root != project_dir {
extra_pkg_dirs.push(ws_root);
}
for unit in &explanation.units {
if super::unit_inputs::is_mutable_unit(unit) {
let pkg_dir = super::key_flags::resolve_unit_pkg_dir(unit);
if pkg_dir != project_dir && !extra_pkg_dirs.contains(&pkg_dir) {
extra_pkg_dirs.push(pkg_dir);
}
}
}
let root_binary_env = root_binary.as_ref().and_then(|rb| {
plan.actions
.iter()
.find(|a| a.outputs.contains(rb))
.map(|a| &a.env)
});
let mut root_binary_link_args: Vec<String> = plan
.unit_link_args
.values()
.flat_map(|v| v.iter().cloned())
.collect();
for art in plan.artifacts.values() {
if let Some(parent) = art.parent() {
root_binary_link_args.push(format!("-L{}", parent.display()));
}
}
let _ = FastBuildLock::record_state_with_options(
&RecordStateArgs {
project_dir,
profile,
package: &opt.package,
features: &opt.features,
no_default_features: opt.no_default_features,
all_features: opt.all_features,
toolchain_version: &toolchain.version,
action_count: plan.actions.len(),
root_binary,
outputs: &outputs,
stderr: if result.stderr.is_empty() {
None
} else {
Some(&result.stderr)
},
},
&RecordStateOptions {
target_dir: Some(target_dir),
options_hash: Some(options_hash),
extra_package_dirs: &extra_pkg_dirs,
tracked_extra_files: Some(&plan.tracked_extra_files),
tracked_env_vars: Some(&plan.tracked_env_vars),
has_complete_inputs: !plan.inputs_incomplete,
root_binary_env,
root_binary_link_args: &root_binary_link_args,
},
);
}
let outcome = if result.success {
"Build completed successfully"
} else {
"Build failed"
};
let _ = logger.write_trace(profile, result.exit_code, outcome);
}