use super::types::PlannedCompilationUnit;
use crate::core::cas::ContentAddressedStore;
use crate::core::hash::Sha256;
use crate::core::lock::FileLock;
use anyhow::{bail, Context, Result};
use std::cell::RefCell;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
const DEPS_PREFIX: &str = "deps-";
const OWNER_LOCK: &str = ".owner.lock";
static TMP_SEQ: AtomicU64 = AtomicU64::new(1);
static HELD_DEPS_LOCKS: Mutex<Vec<FileLock>> = Mutex::new(Vec::new());
thread_local! {
static ACTIVE_BUILD_DEPS: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
}
pub(crate) fn create_session_dir(cas_root: &Path, is_ephemeral_cas: bool) -> Result<PathBuf> {
let seq = TMP_SEQ.fetch_add(1, Ordering::Relaxed);
if is_ephemeral_cas {
let dir = cas_root.join(format!("{DEPS_PREFIX}{seq}"));
fs::create_dir_all(&dir)
.with_context(|| format!("Failed to create build-deps dir {}", dir.display()))?;
return Ok(dir);
}
let tmp_root = cas_root.join("tmp");
sweep_dead_in(&tmp_root);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let pid = std::process::id();
let dir = tmp_root.join(format!("{DEPS_PREFIX}{pid}-{seq}-{nanos:x}"));
fs::create_dir_all(&dir)
.with_context(|| format!("Failed to create build-deps dir {}", dir.display()))?;
let lock = FileLock::acquire_exclusive(&dir.join(OWNER_LOCK))
.with_context(|| format!("Failed to lock build-deps dir {}", dir.display()))?;
HELD_DEPS_LOCKS.lock().unwrap().push(lock);
Ok(dir)
}
pub(crate) fn sweep_dead_in(tmp_root: &Path) {
let Ok(entries) = fs::read_dir(tmp_root) else {
return;
};
for entry in entries.flatten() {
if !entry.file_name().to_string_lossy().starts_with(DEPS_PREFIX) {
continue;
}
let dir = entry.path();
if !dir.is_dir() {
continue;
}
let Ok(Some(_lock)) = FileLock::try_acquire_exclusive(&dir.join(OWNER_LOCK)) else {
continue;
};
let _ = fs::remove_dir_all(&dir);
}
}
pub(crate) struct BuildDepsScope {
prev: Option<PathBuf>,
}
impl Drop for BuildDepsScope {
fn drop(&mut self) {
let prev = self.prev.take();
ACTIVE_BUILD_DEPS.with(|slot| {
*slot.borrow_mut() = prev;
});
}
}
pub(crate) fn enter(dir: Option<&Path>) -> BuildDepsScope {
let next = dir.map(Path::to_path_buf);
let prev = ACTIVE_BUILD_DEPS.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), next));
BuildDepsScope { prev }
}
pub(crate) fn active_dir() -> Option<PathBuf> {
ACTIVE_BUILD_DEPS.with(|slot| slot.borrow().clone())
}
pub(crate) fn is_importable_crate_unit(unit: &PlannedCompilationUnit) -> bool {
super::planner_units::is_library_kind(&unit.kind)
|| unit.kind.split(',').any(|k| k.trim() == "proc-macro")
}
pub(crate) fn unit_metadata_short_key(action_key: &str) -> String {
if action_key.len() >= 16 && action_key.bytes().all(|b| b.is_ascii_hexdigit()) {
action_key[..16].to_string()
} else {
let digest = Sha256::digest(action_key.as_bytes());
digest[..16].to_string()
}
}
pub(crate) fn materialize_in_dir(
cas: &ContentAddressedStore,
dir: &Path,
file_name: &Path,
content_hash: &str,
) -> Result<PathBuf> {
let Some(base_name) = file_name.file_name() else {
bail!(
"Invalid dependency artifact file name: {}",
file_name.display()
);
};
let dest = dir.join(base_name);
let obj_path = cas.object_path(content_hash);
if let (Ok(dest_meta), Ok(obj_meta)) = (fs::metadata(&dest), fs::metadata(&obj_path)) {
if dest_meta.is_file() && dest_meta.len() == obj_meta.len() {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if dest_meta.ino() == obj_meta.ino() && dest_meta.dev() == obj_meta.dev() {
return Ok(dest);
}
}
}
}
fs::create_dir_all(dir)?;
let seq = TMP_SEQ.fetch_add(1, Ordering::Relaxed);
let tmp = dir.join(format!(
".tmp_{}_{}_{}",
std::process::id(),
seq,
base_name.to_string_lossy()
));
let _ = fs::remove_file(&tmp);
cas.restore_object(content_hash, &tmp, false)?;
if let Err(err) = fs::rename(&tmp, &dest) {
let _ = fs::remove_file(&tmp);
if !dest.is_file() {
return Err(err.into());
}
}
Ok(dest)
}
pub(crate) fn restore_importable_unit_outputs(
ctx: &super::dag_cache::ExecutionContext<'_>,
unit: &PlannedCompilationUnit,
outputs: &[crate::core::action_cache::CachedOutput],
artifact_key: &str,
) -> bool {
let active_bdeps = active_dir();
let mut staged_completed: Vec<(String, PathBuf, u8)> = Vec::new();
for out in 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 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"
&& !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.to_string()
};
let Ok(named) = ctx.cas.named_artifact_path_with_disambiguator(
&unit.target_name,
&out.cas_hash,
&disambig,
ext,
) else {
return false;
};
let extern_path = if let Some(ref bdeps) = active_bdeps {
let Ok(dep_path) =
materialize_in_dir(ctx.cas, bdeps, &out.relative_path, &out.cas_hash)
else {
return false;
};
dep_path
} else {
named
};
let priority = match ext {
"rlib" => 3,
"dylib" | "so" => 2,
"a" | "lib" => 1,
_ => 0,
};
staged_completed.push((unit.unit_id(), extern_path, priority));
}
}
if let Some((uid, extern_path, _)) = staged_completed
.into_iter()
.max_by_key(|(_, _, priority)| *priority)
{
ctx.completed_artifacts
.lock()
.unwrap()
.insert(uid, extern_path);
}
true
}