use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
const MAX_THREADS: usize = 8;
const TICKET_PREFIX: &str = "cargo-turbo-";
pub fn run() -> i32 {
let mut args = env::args_os().skip(1);
let Some(rustc) = args.next() else {
eprintln!("cargo-turbo: expected the real rustc as the first argument");
return 2;
};
let args: Vec<OsString> = args.collect();
let mut command = Command::new(&rustc);
command.args(&args);
let ticket = Ticket::claim();
if let Some(share) = thread_share(ticket.as_ref()) {
command.arg(format!("-Zthreads={share}"));
}
let status = command.status();
drop(ticket);
match status {
Ok(status) => status.code().unwrap_or(1),
Err(e) => {
eprintln!(
"cargo-turbo: could not run {}: {e}",
rustc.to_string_lossy()
);
1
}
}
}
fn thread_share(ticket: Option<&Ticket>) -> Option<usize> {
if env::var("CARGO_TURBO_THREADS").as_deref() == Ok("0") {
return None;
}
let jobs = env::var("CARGO_TURBO_JOBS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or_else(available_cores);
share_for(jobs, ticket?.siblings())
}
fn share_for(jobs: usize, siblings: usize) -> Option<usize> {
let share = (jobs / siblings.max(1)).clamp(1, MAX_THREADS);
(share > 1).then_some(share)
}
struct Ticket {
path: PathBuf,
dir: PathBuf,
}
impl Ticket {
fn claim() -> Option<Self> {
let dir = env::temp_dir().join(format!("{TICKET_PREFIX}{}", build_scope()));
fs::create_dir_all(&dir).ok()?;
let path = dir.join(std::process::id().to_string());
fs::write(&path, b"").ok()?;
Some(Self { path, dir })
}
fn siblings(&self) -> usize {
fs::read_dir(&self.dir).map_or(1, |entries| entries.count().max(1))
}
}
pub fn clean_tickets() -> usize {
clean_tickets_in(&env::temp_dir())
}
fn clean_tickets_in(dir: &std::path::Path) -> usize {
let Ok(entries) = fs::read_dir(dir) else {
return 0;
};
entries
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(TICKET_PREFIX))
})
.filter(|e| fs::remove_dir(e.path()).is_ok())
.count()
}
impl Drop for Ticket {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
}
}
fn build_scope() -> String {
let mut args = env::args();
while let Some(arg) = args.next() {
let dir = if arg == "--out-dir" {
args.next()
} else {
arg.strip_prefix("--out-dir=").map(str::to_owned)
};
if let Some(dir) = dir {
return format!("{:016x}", crate::key::hash(profile_root(&dir).as_bytes()));
}
}
"shared".into()
}
fn profile_root(out_dir: &str) -> &str {
for marker in ["/debug/", "/release/"] {
if let Some(at) = out_dir.find(marker) {
return &out_dir[..at + marker.len()];
}
}
out_dir
}
fn available_cores() -> usize {
std::thread::available_parallelism().map_or(1, |n| n.get())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_lone_invocation_gets_the_machine_up_to_the_cap() {
assert_eq!(share_for(10, 1), Some(MAX_THREADS));
assert_eq!(share_for(128, 1), Some(MAX_THREADS));
}
#[test]
fn a_wide_build_leaves_every_invocation_single_threaded() {
assert_eq!(share_for(10, 30), None);
assert_eq!(share_for(10, 10), None);
}
#[test]
fn the_share_divides_the_machine() {
assert_eq!(share_for(10, 4), Some(2));
assert_eq!(share_for(10, 2), Some(5));
}
#[test]
fn a_saturated_build_gets_no_flag_at_all() {
assert_eq!(share_for(10, 10), None);
assert_eq!(share_for(10, 11), None);
}
#[test]
fn a_miscounted_ticket_never_divides_by_zero() {
assert_eq!(share_for(10, 0), Some(MAX_THREADS));
}
#[test]
fn every_unit_of_one_build_shares_a_scope() {
let a = ["rustc", "--crate-name", "foo", "--out-dir", "/t/debug/deps"];
let b = ["rustc", "--crate-name", "bar", "--out-dir", "/t/debug/deps"];
assert_eq!(scope_of(&a), scope_of(&b));
let other = [
"rustc",
"--crate-name",
"foo",
"--out-dir",
"/other/debug/deps",
];
assert_ne!(scope_of(&a), scope_of(&other));
}
#[test]
fn a_build_script_shares_the_scope_of_the_units_around_it() {
assert_eq!(
profile_root("/t/target/debug/build/serde-abc123"),
profile_root("/t/target/debug/deps")
);
assert_ne!(
profile_root("/t/target/debug/deps"),
profile_root("/t/target/release/deps")
);
}
fn scope_of(args: &[&str]) -> String {
let mut it = args.iter();
while let Some(arg) = it.next() {
let dir = if *arg == "--out-dir" {
it.next().map(|s| (*s).to_owned())
} else {
arg.strip_prefix("--out-dir=").map(str::to_owned)
};
if let Some(dir) = dir {
return format!("{:016x}", crate::key::hash(profile_root(&dir).as_bytes()));
}
}
"shared".into()
}
#[test]
fn cleaning_reclaims_finished_builds_but_spares_running_ones() {
let tmp = env::temp_dir().join(format!("cargo-turbo-sweep-{}", std::process::id()));
let _ = fs::remove_dir_all(&tmp);
let idle = tmp.join(format!("{TICKET_PREFIX}idle"));
let busy = tmp.join(format!("{TICKET_PREFIX}busy"));
let other = tmp.join("unrelated");
for d in [&idle, &busy, &other] {
fs::create_dir_all(d).unwrap();
}
fs::write(busy.join("1234"), b"").unwrap();
clean_tickets_in(&tmp);
assert!(!idle.exists(), "a finished build's directory should be gone");
assert!(busy.exists(), "a running build must not be disturbed");
assert!(other.exists(), "unrelated temp directories must be left alone");
let _ = fs::remove_dir_all(&tmp);
}
#[test]
fn a_ticket_is_visible_while_held_and_gone_after() {
let dir = env::temp_dir().join(format!("cargo-turbo-test-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let ticket = Ticket {
path: dir.join("1"),
dir: dir.clone(),
};
fs::write(&ticket.path, b"").unwrap();
assert_eq!(ticket.siblings(), 1);
drop(ticket);
assert_eq!(fs::read_dir(&dir).unwrap().count(), 0);
let _ = fs::remove_dir_all(&dir);
}
}