use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
const MAX_THREADS: usize = 8;
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);
if let Some(share) = thread_share() {
command.arg(format!("-Zthreads={share}"));
}
match command.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() -> 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);
let ticket = Ticket::claim()?;
let share = (jobs / ticket.siblings().max(1)).clamp(1, MAX_THREADS);
drop(ticket);
(share > 1).then_some(share)
}
struct Ticket {
path: PathBuf,
dir: PathBuf,
}
impl Ticket {
fn claim() -> Option<Self> {
let dir = env::temp_dir().join(format!("cargo-turbo-{}", 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))
}
}
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!((10_usize / 1).clamp(1, MAX_THREADS), MAX_THREADS);
}
#[test]
fn a_wide_build_leaves_every_invocation_single_threaded() {
assert_eq!((10_usize / 30).clamp(1, MAX_THREADS), 1);
assert!(!(1 > 1));
}
#[test]
fn the_share_divides_the_machine() {
assert_eq!((10_usize / 4).clamp(1, MAX_THREADS), 2);
assert_eq!((10_usize / 2).clamp(1, MAX_THREADS), 5);
}
#[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 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);
}
}