use super::build_script::BuildScriptDirectives;
use super::manifest::CargoToml;
use super::types::{BuildProgressEvent, PlannedCompilationUnit};
use crate::core::action::{Action, ActionKind};
use anyhow::Result;
use std::fs;
use std::path::{Path, PathBuf};
pub use super::dag_unit::context::*;
pub fn try_restore_from_cache(
unit: &PlannedCompilationUnit,
action_key: &str,
ctx: &ExecutionContext,
is_bin: bool,
) -> Result<bool> {
try_restore_from_cache_with_directives(unit, action_key, ctx, is_bin, None)
}
pub fn try_restore_from_cache_with_directives(
unit: &PlannedCompilationUnit,
action_key: &str,
ctx: &ExecutionContext,
is_bin: bool,
current_directives: Option<&BuildScriptDirectives>,
) -> Result<bool> {
try_restore_from_cache_with_directives_and_purity(
unit,
action_key,
ctx,
is_bin,
current_directives,
None,
)
}
pub fn try_restore_from_cache_with_directives_and_purity(
unit: &PlannedCompilationUnit,
action_key: &str,
ctx: &ExecutionContext,
is_bin: bool,
current_directives: Option<&BuildScriptDirectives>,
purity: Option<&super::build_script_purity::BuildScriptPurity>,
) -> Result<bool> {
if !ctx.is_cache_enabled || action_key.contains(":unhashable:") {
return Ok(false);
}
let is_mutable = super::unit_inputs::is_mutable_unit(unit);
let ws_root = if is_mutable {
Some(super::compiler::find_workspace_root(&unit.manifest_path))
} else {
None
};
let current_ws_hash = ws_root
.as_ref()
.map(|r| crate::core::hash::Sha256::digest(r.to_string_lossy().as_bytes()));
let mut candidate_keys = Vec::new();
if is_mutable {
if let Ok(Some(manifest)) = ctx.ac.get_manifest(action_key) {
let has_build = super::unit_env::unit_has_build_script(unit);
if has_build && current_directives.is_none() {
return Ok(false);
}
let mut unit_env = super::unit_env::compute_unit_rustc_env_with_target(
unit,
current_directives,
None,
Some(ctx.target_dir),
ctx.opt.release,
);
let out_dir = current_directives.and_then(|d| d.out_dir.as_deref());
if has_build {
let purity_hash = purity.map(|p| p.out_dir_hash.as_str());
if let Some(h) = purity_hash {
unit_env.insert("OUT_DIR".to_string(), h.to_string());
} else if let (Some(d), Some(out)) = (current_directives, out_dir) {
let pkg_dir = unit.manifest_path.parent().unwrap_or(Path::new("."));
let empty_refs = std::collections::HashSet::new();
if let Ok(p) =
super::build_script_purity::assess_build_script_purity_with_dep_info(
pkg_dir,
ws_root.as_deref(),
out,
Some(ctx.target_dir),
Some(ctx.cas.root()),
d,
Some(&empty_refs),
)
{
unit_env.insert("OUT_DIR".to_string(), p.out_dir_hash);
}
}
}
if let Some(matched_res_key) =
manifest.find_matching_entry(ws_root.as_deref(), Some(&unit_env), out_dir)
{
if let Some(matched_entry) = manifest
.entries
.iter()
.find(|e| e.result_key == matched_res_key)
{
super::dag_unit_publish::record_extra_inputs_in_ctx(
ctx,
ws_root.as_deref(),
&matched_entry.extra_inputs,
);
}
candidate_keys.push(matched_res_key.clone());
if let Some(ref h) = current_ws_hash {
candidate_keys.push(format!("{}:ws_leak:{}", matched_res_key, h));
}
}
}
} else {
candidate_keys.push(action_key.to_string());
}
if candidate_keys.is_empty() {
return Ok(false);
}
let mut matched_entry = None;
let mut effective_key = action_key.to_string();
for cand in candidate_keys {
if let Ok(Some(entry)) = ctx.ac.get(&cand) {
matched_entry = Some(entry);
effective_key = cand;
break;
}
}
let entry = match matched_entry {
Some(e) => e,
None => return Ok(false),
};
let res_key = effective_key
.split(":ws_leak:")
.next()
.unwrap_or(&effective_key)
.to_string();
let artifact_key = super::dag_unit_publish::artifact_disambiguator(&res_key, None);
let needs_linkable_lib = !is_bin
&& (unit.for_host
|| !matches!(
ctx.opt.mode,
crate::adapter::BuildMode::Check | crate::adapter::BuildMode::Clippy
));
let mut valid = !entry.outputs.is_empty()
&& (!needs_linkable_lib
|| entry.outputs.iter().any(|o| {
matches!(
o.relative_path.extension().and_then(|e| e.to_str()),
Some("rlib" | "dylib" | "so")
)
}));
for out in &entry.outputs {
if fs::metadata(ctx.cas.object_path(&out.cas_hash))
.map(|m| m.len() != out.file_size)
.unwrap_or(true)
{
valid = false;
break;
}
}
let safe_eff = crate::core::action_cache::safe_key_file_name(&effective_key);
let meta_dir = ctx.cas.root().join("metadata");
let eff_meta = meta_dir.join(format!("{safe_eff}.toml"));
let meta_path = if eff_meta.is_file() {
eff_meta
} else {
let safe_act = crate::core::action_cache::safe_key_file_name(action_key);
meta_dir.join(format!("{safe_act}.toml"))
};
let pkg_toml = CargoToml::from_file(&unit.manifest_path).unwrap_or_default();
let has_links = pkg_toml.package.as_ref().is_some_and(|p| p.links.is_some());
if has_links && !meta_path.is_file() {
return Ok(false);
}
if valid {
if is_bin {
let profile_name = if ctx.opt.release { "release" } else { "debug" };
let dest_bin =
super::dag_unit_publish::unit_dest_binary(unit, ctx.target_dir, profile_name);
for out in &entry.outputs {
if out.executable {
let rel_dest = ctx.target_dir.join(&out.relative_path);
let rel_key = out.relative_path.to_string_lossy();
if crate::core::target_index::check_or_restore(
ctx.target_index,
ctx.cas,
&out.cas_hash,
&rel_dest,
&rel_key,
true,
)
.is_err()
{
return Ok(false);
}
if rel_dest != dest_bin {
let _ = crate::core::link_intra_workspace(
&rel_dest,
&dest_bin,
&[ctx.target_dir],
true,
);
}
break;
}
}
} else {
let mut staged_completed: Vec<(String, PathBuf, u8)> = Vec::new();
for out in &entry.outputs {
let ext = out
.relative_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("rlib");
if !ctx.opt.native || unit.is_workspace {
let profile_name = if ctx.opt.release { "release" } else { "debug" };
let deps_dir = ctx.target_dir.join(profile_name).join("deps");
let _ = fs::create_dir_all(&deps_dir);
let is_staticlib = matches!(ext, "a" | "lib");
let target_dep_dest = if is_staticlib {
ctx.target_dir.join(profile_name).join(&out.relative_path)
} else {
deps_dir.join(&out.relative_path)
};
let rel_key = if is_staticlib {
format!("{}/{}", profile_name, out.relative_path.display())
} else {
format!("{}/deps/{}", profile_name, out.relative_path.display())
};
if crate::core::target_index::check_or_restore(
ctx.target_index,
ctx.cas,
&out.cas_hash,
&target_dep_dest,
&rel_key,
false,
)
.is_err()
{
return Ok(false);
}
let clean = unit.target_name.replace('-', "_");
super::dag_unit_publish::publish_library_alias(
ctx,
&target_dep_dest,
&clean,
ext,
&out.cas_hash,
);
if unit.is_workspace && !is_staticlib {
super::dag_unit_publish::publish_uplifted_library(
ctx,
&target_dep_dest,
&clean,
ext,
&out.cas_hash,
);
}
}
if matches!(ext, "rlib" | "dylib" | "so" | "a" | "lib" | "rmeta") {
let is_meta_only = ext == "rmeta"
&& !entry.outputs.iter().any(|o| {
matches!(
o.relative_path.extension().and_then(|e| e.to_str()),
Some("rlib" | "dylib" | "so" | "a" | "lib")
)
});
let disambig = if is_meta_only {
format!("{artifact_key}-m")
} else {
artifact_key.clone()
};
let Ok(named) = ctx.cas.named_artifact_path_with_disambiguator(
&unit.target_name,
&out.cas_hash,
&disambig,
ext,
) else {
return Ok(false);
};
let priority = match ext {
"rlib" => 3,
"dylib" | "so" => 2,
"a" | "lib" => 1,
_ => 0,
};
staged_completed.push((unit.unit_id(), named, priority));
}
}
if let Some((uid, named, _)) = staged_completed
.into_iter()
.max_by_key(|(_, _, priority)| *priority)
{
ctx.completed_artifacts.lock().unwrap().insert(uid, named);
}
}
if let Some(rk_map) = ctx.resolved_keys {
rk_map.lock().unwrap().insert(unit.unit_id(), res_key);
}
let effective_dirs: Option<BuildScriptDirectives> = if let Some(d) = current_directives {
Some(d.clone())
} else if meta_path.is_file() {
fs::read_to_string(&meta_path)
.ok()
.and_then(|c| toml::from_str::<BuildScriptDirectives>(&c).ok())
} else {
None
};
if let Some(ref d) = effective_dirs {
let dep_vars = d.dep_env_vars();
if !dep_vars.is_empty() {
let mut all_meta = ctx.dep_build_metadata.lock().unwrap();
all_meta.insert(unit.unit_id(), dep_vars.clone());
all_meta.insert(unit.package_name.clone(), dep_vars);
}
super::dag_unit_build_script::record_completed_directives(
&unit.unit_id(),
d,
ctx.completed_cfgs,
ctx.completed_env,
ctx.completed_link_args,
);
}
let empty_dirs = BuildScriptDirectives::default();
let _ = super::dag_unit_build_script::inherit_dep_link_searches(
unit,
effective_dirs.as_ref().unwrap_or(&empty_dirs),
ctx.completed_link_args,
);
for msg in [&entry.stdout, &entry.stderr] {
super::unit_output::publish(ctx, unit, msg, None);
}
let kind = if is_bin {
ActionKind::CompileBin
} else {
ActionKind::CompileRlib
};
let env = super::unit_env::compute_unit_rustc_env_with_target(
unit,
effective_dirs.as_ref(),
None,
Some(ctx.target_dir),
ctx.opt.release,
);
let outputs = entry
.outputs
.iter()
.map(|o| ctx.target_dir.join(&o.relative_path))
.collect();
ctx.recorded_actions.lock().unwrap().push(Action {
id: entry.action_id.clone(),
kind,
description: format!("Restored {} from CAS", entry.action_id),
toolchain: ctx.toolchain.clone(),
inputs: Vec::new(),
outputs,
args: Vec::new(),
env,
dependencies: Vec::new(),
});
if let Some(ref cb) = ctx.opt.progress {
cb.0(BuildProgressEvent::UnitFinished);
}
if let Some(c) = ctx.cached_actions {
c.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
return Ok(true);
}
Ok(false)
}