Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ shadow the isolation, and CI's `target-dir-guard` job
(.github/workflows/ci.yml) fails the build outright if the var is set
project-wide. The residual gap is a bare shell opened inside a worktree
without going through `agentflare run` — `cargo` there will still honor an
ambient `CARGO_TARGET_DIR`.
ambient `CARGO_TARGET_DIR`. If `sccache` is on `PATH`, the isolated config also sets it as `rustc-wrapper` with `SCCACHE_BASEDIRS` pointed at the worktree's own path, so registry-dependency compiles still share a cache across worktrees.

## Git

Expand Down
69 changes: 65 additions & 4 deletions crates/flare-git-core/src/worktree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,39 @@ fn warn_if_ambient_target_dir() {
/// Local workspace crates must NOT be shared across worktrees (silent
/// contamination); registry deps are safe but are better served by a shared
/// sccache. A relative `target-dir = "target"` resolves per-checkout, giving
/// each worktree its own isolated cache. Soft-fails (eprintln) — never blocks
/// a claim.
/// each worktree its own isolated cache. When `sccache` is on `PATH`, also
/// wires it up as the `rustc-wrapper` with `SCCACHE_BASEDIRS` set to this
/// worktree's own absolute path — sccache hashes absolute source paths into
/// its cache key by default, so without stripping that prefix, identical
/// dependency source in a sibling worktree would never hit
/// (mozilla/sccache#196; a `--remap-path-prefix` rustflag looks tempting but
/// itself varies per worktree and defeats the cache key instead). Soft-fails
/// (eprintln) — never blocks a claim.
fn isolate_worktree_target_dir(worktree_path: &Path) {
let cargo_dir = worktree_path.join(".cargo");
let _ = std::fs::create_dir_all(&cargo_dir);
let config_path = cargo_dir.join("config.toml");
if config_path.exists() {
return; // don't clobber an intentional worktree-local override
}
let content = "[build]\n# Isolated per worktree (see item #133). Registry deps are\n\
let mut content = "[build]\n# Isolated per worktree (see item #133). Registry deps are\n\
# better shared via sccache (RUSTC_WRAPPER + SCCACHE_BASEDIRS),\n\
# not a shared CARGO_TARGET_DIR, which leaks artifacts across worktrees.\n\
target-dir = \"target\"\n";
target-dir = \"target\"\n"
.to_string();
if sccache_available() {
// TOML literal strings ('...') can't escape a single quote, so a
// worktree path containing one (e.g. "C:\Users\John's PC\repo")
// would produce invalid TOML. Use a basic string instead, with
// backslashes and double quotes escaped.
let escaped_path = worktree_path
.to_string_lossy()
.replace('\\', "\\\\")
.replace('"', "\\\"");
content.push_str(&format!(
"rustc-wrapper = \"sccache\"\n\n[env]\nSCCACHE_BASEDIRS = \"{escaped_path}\"\n"
));
}
if let Err(e) = std::fs::write(&config_path, content) {
eprintln!(
"worktree: could not write isolated .cargo/config.toml for {}: {e}",
Expand All @@ -164,6 +184,17 @@ fn isolate_worktree_target_dir(worktree_path: &Path) {
}
}

/// True when the `sccache` binary is reachable on `PATH`.
fn sccache_available() -> bool {
Command::new("sccache")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}

/// Creates an isolated git worktree for `item` against `target_branch`.
///
/// Deliberately takes an already-resolved `target_branch` instead of a
Expand Down Expand Up @@ -538,6 +569,36 @@ mod tests {
);
}

#[test]
fn isolate_worktree_target_dir_wires_sccache_when_available() {
let tmp = TempDir::new().unwrap();
let wt = tmp.path().join(".worktrees").join("task").join("1");
std::fs::create_dir_all(&wt).unwrap();
isolate_worktree_target_dir(&wt);
let config = wt.join(".cargo").join("config.toml");
let content = std::fs::read_to_string(&config).unwrap();
if sccache_available() {
assert!(
content.contains("rustc-wrapper = \"sccache\""),
"expected sccache wired up as rustc-wrapper, got: {content}"
);
let escaped = wt
.to_string_lossy()
.replace('\\', "\\\\")
.replace('"', "\\\"");
let basedir_line = format!("SCCACHE_BASEDIRS = \"{escaped}\"");
assert!(
content.contains(&basedir_line),
"expected SCCACHE_BASEDIRS to strip this worktree's own path, got: {content}"
);
} else {
assert!(
!content.contains("rustc-wrapper") && !content.contains("SCCACHE_BASEDIRS"),
"must not reference sccache when it isn't on PATH, got: {content}"
);
}
}

#[test]
fn warn_if_ambient_target_dir_warns_when_set() {
// Just asserts the function runs without panicking whether or not the
Expand Down
Loading