use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::key::{self, Plan};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Freshness {
Checksum,
Mtime,
}
impl Freshness {
pub(crate) fn label(self) -> &'static str {
match self {
Self::Checksum => "checksum",
Self::Mtime => "mtime",
}
}
fn from_label(label: &str) -> Self {
match label.trim() {
"mtime" => Self::Mtime,
_ => Self::Checksum,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Hit {
Exact,
Near,
None,
}
pub fn restore(plan: &Plan) -> (Hit, Freshness) {
if env::var("CARGO_TURBO_OFF").as_deref() == Ok("1") {
return (Hit::None, Freshness::Checksum);
}
let (snapshot, exact) = match usable(&plan.snapshot()) {
true => (plan.snapshot(), true),
false if env::var("CARGO_TURBO_NEAR").as_deref() == Ok("0") => {
return (Hit::None, Freshness::Checksum)
}
false => match nearest_in_lineage(plan) {
Some(path) => (path, false),
None => return (Hit::None, Freshness::Checksum),
},
};
let tree = snapshot.join("target");
if plan.target_dir.exists() {
return (Hit::None, Freshness::Checksum);
}
if clone_tree(&tree, &plan.target_dir).is_err() {
let _ = fs::remove_dir_all(&plan.target_dir);
return (Hit::None, Freshness::Checksum);
}
let freshness = fs::read_to_string(snapshot.join("mode"))
.map(|m| Freshness::from_label(&m))
.unwrap_or(Freshness::Checksum);
if freshness == Freshness::Checksum {
let _ = fs::write(plan.target_dir.join(MARKER), b"checksum-freshness\n");
}
if exact {
eprintln!("cargo-turbo: restored {}", plan.key);
} else {
eprintln!("cargo-turbo: restored a near match, cargo will rebuild the difference");
}
(if exact { Hit::Exact } else { Hit::Near }, freshness)
}
fn usable(snapshot: &Path) -> bool {
snapshot.join("complete").exists() && snapshot.join("target").is_dir()
}
fn nearest_in_lineage(plan: &Plan) -> Option<PathBuf> {
let mut best: Option<(std::time::SystemTime, PathBuf)> = None;
let mut stack = vec![plan.store.clone()];
while let Some(dir) = stack.pop() {
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
if !usable(&path) {
stack.push(path);
continue;
}
if fs::read_to_string(path.join("lineage"))
.ok()
.as_deref()
.map(str::trim)
!= Some(plan.lineage.as_str())
{
continue;
}
let when = fs::metadata(path.join("complete"))
.and_then(|m| m.modified())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
if best.as_ref().is_none_or(|(b, _)| when > *b) {
best = Some((when, path));
}
}
}
best.map(|(_, path)| path)
}
pub fn save(plan: &Plan, freshness: Freshness) {
if env::var("CARGO_TURBO_OFF").as_deref() == Ok("1") || !plan.target_dir.is_dir() {
return;
}
let snapshot = plan.snapshot();
if snapshot.join("complete").exists() {
return;
}
let staging = snapshot.with_extension(format!("staging-{}", std::process::id()));
let _ = fs::remove_dir_all(&staging);
if fs::create_dir_all(&staging).is_err() {
return;
}
if clone_tree(&plan.target_dir, &staging.join("target")).is_err() {
let _ = fs::remove_dir_all(&staging);
return;
}
if fs::write(staging.join("mode"), freshness.label().as_bytes()).is_err() {
let _ = fs::remove_dir_all(&staging);
return;
}
if fs::write(staging.join("lineage"), plan.lineage.as_bytes()).is_err() {
let _ = fs::remove_dir_all(&staging);
return;
}
if fs::write(staging.join("complete"), plan.key.as_bytes()).is_err() {
let _ = fs::remove_dir_all(&staging);
return;
}
if let Some(parent) = snapshot.parent() {
let _ = fs::create_dir_all(parent);
}
if fs::rename(&staging, &snapshot).is_err() {
let _ = fs::remove_dir_all(&staging);
}
}
pub fn forward(plan: &Plan, args: &[String], freshness: Freshness) -> i32 {
let mut command = cargo();
command.args(args);
if plan.nightly && env::var("CARGO_TURBO_OFF").as_deref() != Ok("1") {
install_wrapper(&mut command);
if freshness == Freshness::Checksum
&& owns_target_dir(&plan.target_dir)
&& !args.iter().any(|a| a.contains("checksum-freshness"))
{
command.args(["-Z", "checksum-freshness"]);
}
}
run(command)
}
const MARKER: &str = ".cargo-turbo-checksums";
fn owns_target_dir(target_dir: &Path) -> bool {
if target_dir.join(MARKER).exists() {
return true;
}
let empty = !target_dir.exists()
|| fs::read_dir(target_dir)
.map(|d| d.count() == 0)
.unwrap_or(false);
if empty {
let _ = fs::create_dir_all(target_dir);
return fs::write(target_dir.join(MARKER), b"checksum-freshness\n").is_ok();
}
false
}
pub fn forward_plain(args: &[String]) -> i32 {
let mut command = cargo();
command.args(args);
run(command)
}
fn install_wrapper(command: &mut Command) {
if env::var_os("RUSTC_WRAPPER").is_some() {
return;
}
if let Ok(self_path) = env::current_exe() {
command.env("RUSTC_WRAPPER", self_path);
command.env(crate::WRAPPER_MARKER, "1");
}
}
fn cargo() -> Command {
Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into()))
}
fn run(mut command: Command) -> i32 {
match command.status() {
Ok(status) => status.code().unwrap_or(1),
Err(e) => {
eprintln!("cargo-turbo: could not run cargo: {e}");
1
}
}
}
pub(crate) fn clone_tree(from: &Path, to: &Path) -> Result<(), String> {
if let Some(parent) = to.parent() {
fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
#[cfg(target_os = "macos")]
if clonefile_tree(from, to) {
return Ok(());
}
let attempts: &[&[&str]] = if cfg!(target_os = "macos") {
&[&["-Rpc"], &["-Rp"]]
} else {
&[&["-a", "--reflink=auto"], &["-a"]]
};
for flags in attempts {
let status = Command::new("cp").args(*flags).arg(from).arg(to).status();
if matches!(status, Ok(s) if s.success()) {
return Ok(());
}
let _ = fs::remove_dir_all(to);
}
Err(format!(
"could not copy {} to {}",
from.display(),
to.display()
))
}
#[cfg(target_os = "macos")]
fn clonefile_tree(from: &Path, to: &Path) -> bool {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
unsafe extern "C" {
fn clonefile(src: *const std::ffi::c_char, dst: *const std::ffi::c_char, flags: u32)
-> i32;
}
let (Ok(src), Ok(dst)) = (
CString::new(from.as_os_str().as_bytes()),
CString::new(to.as_os_str().as_bytes()),
) else {
return false;
};
let result = unsafe { clonefile(src.as_ptr(), dst.as_ptr(), 0) };
if result != 0 {
let _ = fs::remove_dir_all(to);
return false;
}
true
}
pub fn status() -> i32 {
let store = key::store_dir();
if !store.is_dir() {
println!("cargo-turbo: nothing stored yet ({})", store.display());
return 0;
}
let mut count = 0usize;
let mut stack = vec![store.clone()];
while let Some(dir) = stack.pop() {
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if entry.file_name() == "units" {
continue;
}
if path.join("complete").exists() {
count += 1;
} else if path.is_dir() {
stack.push(path);
}
}
}
let mut units = 0usize;
if let Ok(scopes) = fs::read_dir(store.join("units")) {
for scope in scopes.flatten() {
units += fs::read_dir(scope.path()).map_or(0, |e| e.count());
}
}
let du = Command::new("du").arg("-sh").arg(&store).output();
let size = du
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.and_then(|s| s.split_whitespace().next().map(str::to_owned))
.unwrap_or_else(|| "unknown".into());
println!("cargo-turbo: {count} snapshots and {units} shared dependency units");
println!(" {size} logical, shared with the target directories they came from");
println!(" in {}", store.display());
0
}
pub fn clean() -> i32 {
let store = key::store_dir();
match fs::remove_dir_all(&store) {
Ok(()) => {
println!("cargo-turbo: removed {}", store.display());
0
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => 0,
Err(e) => {
eprintln!("cargo-turbo: could not remove {}: {e}", store.display());
1
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn record(store: &Path, key: &str, lineage: &str) -> PathBuf {
let dir = store.join(&key[..2]).join(key);
fs::create_dir_all(dir.join("target")).unwrap();
fs::write(dir.join("lineage"), lineage).unwrap();
fs::write(dir.join("complete"), key).unwrap();
dir
}
fn plan_for(store: &Path, key: &str, lineage: &str) -> Plan {
Plan {
key: key.into(),
lineage: lineage.into(),
profile_dir: "debug".into(),
toolchain: "test".into(),
lock_contents: String::new(),
store: store.to_path_buf(),
target_dir: store.join("unused-target"),
nightly: true,
}
}
fn scratch(name: &str) -> PathBuf {
let dir = env::temp_dir().join(format!("turbo-{name}-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
dir
}
#[test]
fn a_near_match_is_only_taken_from_the_same_lineage() {
let store = scratch("lineage");
let mine = record(&store, "aa11", "mine");
record(&store, "bb22", "theirs");
assert_eq!(
nearest_in_lineage(&plan_for(&store, "cc33", "mine")),
Some(mine)
);
assert_eq!(
nearest_in_lineage(&plan_for(&store, "cc33", "nobody")),
None
);
let _ = fs::remove_dir_all(&store);
}
#[test]
fn an_unfinished_snapshot_is_never_a_near_match() {
let store = scratch("partial");
let dir = store.join("aa").join("aa11");
fs::create_dir_all(dir.join("target")).unwrap();
fs::write(dir.join("lineage"), "mine").unwrap();
let plan = plan_for(&store, "cc33", "mine");
assert_eq!(nearest_in_lineage(&plan), None);
fs::write(dir.join("complete"), "aa11").unwrap();
assert_eq!(nearest_in_lineage(&plan), Some(dir));
let _ = fs::remove_dir_all(&store);
}
#[test]
fn the_newest_snapshot_of_a_lineage_wins() {
let store = scratch("newest");
let older = record(&store, "aa11", "mine");
let newer = record(&store, "bb22", "mine");
let long_ago = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000);
let recently = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(2_000);
set_modified(&older.join("complete"), long_ago);
set_modified(&newer.join("complete"), recently);
assert_eq!(
nearest_in_lineage(&plan_for(&store, "cc33", "mine")),
Some(newer)
);
let _ = fs::remove_dir_all(&store);
}
#[test]
fn the_mode_a_snapshot_was_recorded_under_survives_a_round_trip() {
assert_eq!(Freshness::from_label("mtime"), Freshness::Mtime);
assert_eq!(Freshness::from_label("checksum"), Freshness::Checksum);
assert_eq!(
Freshness::from_label(Freshness::Mtime.label()),
Freshness::Mtime
);
assert_eq!(Freshness::from_label(""), Freshness::Checksum);
assert_eq!(Freshness::from_label("something else"), Freshness::Checksum);
}
#[test]
fn an_exact_key_is_preferred_over_any_near_match() {
let store = scratch("exact");
record(&store, "aa11", "mine");
let plan = plan_for(&store, "aa11", "mine");
assert!(usable(&plan.snapshot()));
let _ = fs::remove_dir_all(&store);
}
fn set_modified(path: &Path, when: std::time::SystemTime) {
let file = fs::OpenOptions::new().write(true).open(path).unwrap();
file.set_modified(when).unwrap();
}
}