use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use crate::key::{hash, Plan};
use crate::snapshot::Freshness;
struct Fragment {
key: String,
parts: Vec<PathBuf>,
}
pub fn seed(plan: &Plan, freshness: Freshness) -> usize {
let wanted = foreign_packages(plan);
let seeded: usize = plan
.profile_dirs
.iter()
.map(|dir| seed_one(plan, freshness, dir, &wanted))
.sum();
if seeded > 0 {
eprintln!("cargo-turbo: supplied {seeded} prebuilt units");
}
seeded
}
fn seed_one(
plan: &Plan,
freshness: Freshness,
profile_dir: &str,
wanted: &HashSet<String>,
) -> usize {
let store = unit_store(plan, freshness, profile_dir);
if !store.is_dir() {
return 0;
}
let profile = plan.target_dir.join(profile_dir);
if populated(&profile) {
return 0;
}
let mut seeded = 0;
let Ok(entries) = fs::read_dir(&store) else {
return 0;
};
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
let Some(crate_name) = name.rsplit_once('-').map(|(c, _)| c) else {
continue;
};
if !wanted.contains(crate_name) {
continue;
}
if merge_into(&entry.path(), &profile).is_ok() {
seeded += 1;
}
}
seeded
}
pub fn record(plan: &Plan, freshness: Freshness, compiled: bool) {
let shareable = foreign_packages(plan);
for dir in &plan.profile_dirs {
record_one(plan, freshness, dir, compiled, &shareable);
}
}
fn record_one(
plan: &Plan,
freshness: Freshness,
profile_dir: &str,
compiled: bool,
shareable: &HashSet<String>,
) {
let profile = plan.target_dir.join(profile_dir);
if !profile.is_dir() {
return;
}
let store = unit_store(plan, freshness, profile_dir);
if !compiled && store.is_dir() {
return;
}
for fragment in fragments(&profile) {
let Some(crate_name) = fragment.key.rsplit_once('-').map(|(c, _)| c) else {
continue;
};
if !shareable.contains(crate_name) {
continue;
}
let entry = store.join(&fragment.key);
if entry.exists() {
continue;
}
let staging = store.join(format!(".staging-{}-{}", std::process::id(), fragment.key));
let _ = fs::remove_dir_all(&staging);
if fs::create_dir_all(&staging).is_err() {
continue;
}
let mut ok = true;
for part in &fragment.parts {
let to = staging.join(part);
if let Some(parent) = to.parent() {
let _ = fs::create_dir_all(parent);
}
if crate::snapshot::clone_tree(&profile.join(part), &to).is_err() {
ok = false;
break;
}
}
if !ok || fs::rename(&staging, &entry).is_err() {
let _ = fs::remove_dir_all(&staging);
}
}
}
fn populated(profile: &Path) -> bool {
fs::read_dir(profile.join("build")).is_ok_and(|mut entries| entries.next().is_some())
}
fn fragments(profile: &Path) -> Vec<Fragment> {
let mut found = Vec::new();
if !profile.join(".fingerprint").is_dir() {
let Ok(packages) = fs::read_dir(profile.join("build")) else {
return found;
};
for package in packages.flatten() {
let name = package.file_name().to_string_lossy().into_owned();
if !package.path().is_dir() {
continue;
}
let Ok(hashes) = fs::read_dir(package.path()) else {
continue;
};
for entry in hashes.flatten() {
if !entry.path().is_dir() {
continue;
}
let hash = entry.file_name().to_string_lossy().into_owned();
found.push(Fragment {
key: format!("{name}-{hash}"),
parts: vec![Path::new("build").join(&name).join(&hash)],
});
}
}
return found;
}
let Ok(prints) = fs::read_dir(profile.join(".fingerprint")) else {
return found;
};
for print in prints.flatten() {
if !print.path().is_dir() {
continue;
}
let key = print.file_name().to_string_lossy().into_owned();
if !key.contains('-') {
continue;
}
let mut parts = vec![Path::new(".fingerprint").join(&key)];
if profile.join("build").join(&key).is_dir() {
parts.push(Path::new("build").join(&key));
}
if let Some((_, hash)) = key.rsplit_once('-') {
if let Ok(deps) = fs::read_dir(profile.join("deps")) {
for dep in deps.flatten() {
let file = dep.file_name().to_string_lossy().into_owned();
if file.split('.').next().is_some_and(|s| s.ends_with(hash)) {
parts.push(Path::new("deps").join(&file));
}
}
}
}
found.push(Fragment { key, parts });
}
found
}
fn merge_into(entry: &Path, profile: &Path) -> Result<(), String> {
let Ok(items) = fs::read_dir(entry) else {
return Err("unreadable entry".into());
};
for item in items.flatten() {
let from = item.path();
let to = profile.join(item.file_name());
if to.exists() {
if from.is_dir() && to.is_dir() {
merge_into(&from, &to)?;
}
continue;
}
crate::snapshot::clone_tree(&from, &to)?;
}
Ok(())
}
fn unit_store(plan: &Plan, freshness: Freshness, profile_dir: &str) -> PathBuf {
let scope = hash(format!("{}|{profile_dir}|{}", plan.toolchain, freshness.label()).as_bytes());
plan.store.join("units").join(format!("{scope:016x}"))
}
fn partition_packages(lock: &str) -> (HashSet<String>, HashSet<String>) {
let mut local = HashSet::new();
let mut foreign = HashSet::new();
for block in lock.split("[[package]]").skip(1) {
let mut name = None;
let mut has_source = false;
for line in block.lines() {
let line = line.trim();
if line.starts_with("[[") || line.starts_with('[') && line.ends_with(']') {
break;
}
if let Some(rest) = line.strip_prefix("name = ") {
name = Some(rest.trim_matches('"').to_owned());
} else if line.starts_with("source = ") {
has_source = true;
}
}
if let Some(name) = name {
let target = if has_source { &mut foreign } else { &mut local };
target.insert(name.replace('-', "_"));
target.insert(name);
}
}
(local, foreign)
}
fn foreign_packages(plan: &Plan) -> HashSet<String> {
partition_packages(&plan.lock_contents).1
}
#[cfg(test)]
mod tests {
use super::*;
const LOCK: &str = r#"
version = 4
[[package]]
name = "my-app"
version = "0.1.0"
dependencies = ["serde"]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc"
"#;
#[test]
fn only_a_resolved_third_party_package_may_be_stored() {
let (_, foreign) = partition_packages(LOCK);
assert!(foreign.contains("serde"), "a resolved dependency");
assert!(!foreign.contains("my_app"), "this workspace's own crate");
assert!(
!foreign.contains("some_other_projects_crate"),
"a crate left behind by a near-match restore"
);
}
#[test]
fn a_workspace_crate_is_never_shared() {
let (local, foreign) = partition_packages(LOCK);
assert!(local.contains("my_app"));
assert!(!foreign.contains("my_app"));
assert!(foreign.contains("serde"));
assert!(!local.contains("serde"));
}
#[test]
fn a_dashed_package_matches_the_name_cargo_writes() {
let (local, _) = partition_packages(LOCK);
assert!(local.contains("my-app"), "the lock file spelling");
assert!(local.contains("my_app"), "the artifact spelling");
}
#[test]
fn the_nightly_layout_yields_one_entry_per_hash() {
let profile = scratch("units-new");
for (pkg, hash) in [("serde", "aaaa"), ("serde", "bbbb"), ("quote", "cccc")] {
fs::create_dir_all(profile.join("build").join(pkg).join(hash)).unwrap();
}
let mut keys: Vec<String> = fragments(&profile).into_iter().map(|f| f.key).collect();
keys.sort();
assert_eq!(keys, ["quote-cccc", "serde-aaaa", "serde-bbbb"]);
let _ = fs::remove_dir_all(&profile);
}
#[test]
fn a_package_name_containing_a_dash_is_still_shared() {
let profile = scratch("units-dash");
fs::create_dir_all(profile.join("build").join("proc-macro2").join("aaaa")).unwrap();
let found = fragments(&profile);
assert_eq!(found.len(), 1);
assert_eq!(found[0].key, "proc-macro2-aaaa");
assert_eq!(
found[0].key.rsplit_once('-').map(|(c, _)| c),
Some("proc-macro2")
);
let _ = fs::remove_dir_all(&profile);
}
#[test]
fn the_layout_is_settled_by_the_fingerprint_directory() {
let profile = scratch("units-both");
fs::create_dir_all(profile.join(".fingerprint").join("serde-1b84")).unwrap();
fs::create_dir_all(profile.join("build").join("serde").join("aaaa")).unwrap();
let keys: Vec<String> = fragments(&profile).into_iter().map(|f| f.key).collect();
assert_eq!(keys, ["serde-1b84"]);
let _ = fs::remove_dir_all(&profile);
}
#[test]
fn the_stable_layout_collects_all_three_places() {
let profile = scratch("units-old");
fs::create_dir_all(profile.join(".fingerprint").join("serde_core-1b84")).unwrap();
fs::create_dir_all(profile.join("build").join("serde_core-1b84")).unwrap();
fs::create_dir_all(profile.join("deps")).unwrap();
for file in [
"libserde_core-1b84.rmeta",
"serde_core-1b84.d",
"libother-9999.rmeta",
] {
fs::write(profile.join("deps").join(file), b"x").unwrap();
}
let found = fragments(&profile);
assert_eq!(found.len(), 1);
let mut parts: Vec<String> = found[0]
.parts
.iter()
.map(|p| p.display().to_string())
.collect();
parts.sort();
assert_eq!(
parts,
[
".fingerprint/serde_core-1b84",
"build/serde_core-1b84",
"deps/libserde_core-1b84.rmeta",
"deps/serde_core-1b84.d",
],
"another unit's artifacts must not be dragged in"
);
let _ = fs::remove_dir_all(&profile);
}
#[test]
fn a_directory_cargo_has_built_in_is_left_alone() {
let profile = scratch("units-populated");
assert!(!populated(&profile), "nothing there at all");
fs::create_dir_all(profile.join("build")).unwrap();
assert!(
!populated(&profile),
"an empty one is left by an interrupted build and is worth filling"
);
fs::create_dir_all(profile.join("build").join("serde").join("aaaa")).unwrap();
assert!(populated(&profile));
let _ = fs::remove_dir_all(&profile);
}
#[test]
fn a_seed_reaches_below_a_directory_that_already_exists() {
let root = scratch("units-deep");
let profile = root.join("profile");
fs::create_dir_all(profile.join("build").join("serde").join("aaaa")).unwrap();
let entry = root.join("entry");
fs::create_dir_all(entry.join("build").join("serde").join("bbbb")).unwrap();
fs::write(
entry.join("build").join("serde").join("bbbb").join("out"),
b"x",
)
.unwrap();
merge_into(&entry, &profile).unwrap();
assert!(profile.join("build").join("serde").join("aaaa").exists());
assert!(
profile
.join("build")
.join("serde")
.join("bbbb")
.join("out")
.exists(),
"a second variant of an already-present package must still arrive"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn a_seed_never_overwrites_what_the_build_already_has() {
let root = scratch("units-merge");
let entry = root.join("entry");
let profile = root.join("profile");
fs::create_dir_all(entry.join("deps")).unwrap();
fs::write(entry.join("deps").join("libx-1.rmeta"), b"stored").unwrap();
fs::write(entry.join("deps").join("libnew-2.rmeta"), b"stored").unwrap();
fs::create_dir_all(profile.join("deps")).unwrap();
fs::write(profile.join("deps").join("libx-1.rmeta"), b"mine").unwrap();
merge_into(&entry, &profile).unwrap();
let kept = fs::read_to_string(profile.join("deps").join("libx-1.rmeta")).unwrap();
assert_eq!(kept, "mine", "an existing artifact must survive a seed");
assert!(profile.join("deps").join("libnew-2.rmeta").exists());
let _ = fs::remove_dir_all(&root);
}
fn scratch(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("turbo-{name}-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
dir
}
}