Skip to content

Commit dcf575e

Browse files
Read the cargo config chain once (#1832) (#1849)
* Start #1832: one cargo config chain Assisted-by: Claude Code:claude-opus-5-5 * Read cargo config files via config_layer_paths The build-cache cleaner walked the .cargo/config chain itself and preferred config.toml when both config and config.toml existed. Cargo reads the extensionless file, so the cleaner removed fingerprints from a target dir cargo never used. ConfigDirs::read now takes its file list from source_replacement::config_layer_paths, which the source replacement reader already uses, and keeps only the [build] lookup. It also stops reading $CARGO_HOME twice when an ancestor's .cargo is it. Fixes #1832 Assisted-by: Claude Code:claude-opus-5-5 * Resolve a relative cwd before the cargo walks build_dirs walked the ancestors of the cwd as given, and --cwd defaults to ".", whose ancestors stop at ".". Run from a workspace member, the cleaner found neither the workspace root nor a parent .cargo/config, so it invalidated no build dir at all. It now walks the absolute path. The config file probe also consults the open group commit, as the read that follows it does, so the two agree on captured files. Assisted-by: Claude Code:claude-opus-5-5 * Fold .. out of the cwd before the cargo walks std::path::absolute keeps .., so --cwd ../app walked the ancestors of the directory being left and could take its build-dir. Fold the absolute cwd lexically, as cargo_crawler::resolve_for does. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 77c880c commit dcf575e

1 file changed

Lines changed: 78 additions & 16 deletions

File tree

‎crates/socket-patch-core/src/utils/cargo_build_cache.rs‎

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
2222
use std::path::{Path, PathBuf};
2323

24+
use crate::formats::cargo::source_replacement::config_layer_paths;
25+
2426
/// The build directories of the project at `cwd`, and the config values
2527
/// naming one this module cannot resolve (a `{workspace-path-hash}`
2628
/// template) — the caller tells the user to clean those by hand.
@@ -56,6 +58,12 @@ pub fn build_dirs_with(
5658
env: &dyn Fn(&str) -> Option<String>,
5759
cargo_home: Option<&Path>,
5860
) -> BuildDirs {
61+
// `--cwd` defaults to `.`, whose `ancestors()` stop at `.`: walk the
62+
// absolute path so a parent's `Cargo.lock` and `.cargo/config` count,
63+
// with `..` folded so `--cwd ../app` doesn't walk the directory left.
64+
let cwd = &crate::utils::relpath::normalize_lexically_keeping_escapes(
65+
&std::path::absolute(cwd).unwrap_or_else(|_| cwd.to_path_buf()),
66+
);
5967
let env = |k: &str| env(k).filter(|v| !v.trim().is_empty());
6068
let ws_root = workspace_root(cwd);
6169
let config = ConfigDirs::read(cwd, cargo_home);
@@ -136,10 +144,9 @@ fn workspace_root(cwd: &Path) -> PathBuf {
136144
}
137145

138146
/// `build.target-dir` / `build.build-dir` from the config files cargo
139-
/// reads for `cwd`: `.cargo/config.toml` (or legacy `.cargo/config`) in
140-
/// `cwd` and every ancestor, then `$CARGO_HOME/config.toml`; the deepest
141-
/// file that sets a key wins. A relative value resolves against the
142-
/// directory holding the `.cargo` directory.
147+
/// reads for `cwd` ([`config_layer_paths`]); the deepest file that sets a
148+
/// key wins. A relative value resolves against the directory holding the
149+
/// `.cargo` directory.
143150
#[derive(Default)]
144151
struct ConfigDirs {
145152
target_dir: Option<PathBuf>,
@@ -149,19 +156,11 @@ struct ConfigDirs {
149156

150157
impl ConfigDirs {
151158
fn read(cwd: &Path, cargo_home: Option<&Path>) -> Self {
152-
let mut files: Vec<(PathBuf, PathBuf)> = cwd
153-
.ancestors()
154-
.map(|d| (d.join(".cargo"), d.to_path_buf()))
155-
.collect();
156-
if let Some(home) = cargo_home {
157-
let base = home.parent().map(Path::to_path_buf).unwrap_or_default();
158-
files.push((home.to_path_buf(), base));
159-
}
160159
let mut out = ConfigDirs::default();
161-
for (dot_cargo, base) in files {
162-
let text = ["config.toml", "config"].iter().find_map(|f| {
163-
crate::utils::fs::read_regular_to_string_sync(&dot_cargo.join(f)).ok()
164-
});
160+
for (file, base) in config_layer_paths(cwd, cargo_home, |p| {
161+
crate::utils::group_commit::exists(p).unwrap_or_else(|| p.is_file())
162+
}) {
163+
let text = crate::utils::fs::read_regular_to_string_sync(&file).ok();
165164
let Some(doc) = text.and_then(|t| t.parse::<toml_edit::DocumentMut>().ok()) else {
166165
continue;
167166
};
@@ -424,6 +423,69 @@ mod tests {
424423
}
425424
}
426425

426+
/// With both `.cargo/config` and `.cargo/config.toml`, cargo reads the
427+
/// extensionless one; so must the cleaner.
428+
#[test]
429+
fn build_dirs_read_the_legacy_config_when_both_exist() {
430+
let t = tempfile::tempdir().unwrap();
431+
let root = t.path().join("ws");
432+
std::fs::create_dir_all(root.join(".cargo")).unwrap();
433+
std::fs::create_dir_all(root.join("legacy")).unwrap();
434+
std::fs::create_dir_all(root.join("modern")).unwrap();
435+
std::fs::write(
436+
root.join(".cargo/config"),
437+
"[build]\ntarget-dir = \"legacy\"\n",
438+
)
439+
.unwrap();
440+
std::fs::write(
441+
root.join(".cargo/config.toml"),
442+
"[build]\ntarget-dir = \"modern\"\n",
443+
)
444+
.unwrap();
445+
let d = build_dirs_with(&root, &|_| None, None);
446+
assert_eq!(
447+
d.dirs,
448+
vec![std::fs::canonicalize(root.join("legacy")).unwrap()]
449+
);
450+
}
451+
452+
/// A relative cwd (the default `--cwd .`, or `../ws/member`) finds the
453+
/// workspace root and the ancestor config above it, and nothing else.
454+
#[test]
455+
#[serial_test::serial]
456+
fn build_dirs_walk_above_a_relative_cwd() {
457+
let t = tempfile::tempdir().unwrap();
458+
let root = t.path().join("ws");
459+
let member = root.join("member");
460+
std::fs::create_dir_all(root.join(".cargo")).unwrap();
461+
std::fs::create_dir_all(root.join("out")).unwrap();
462+
std::fs::create_dir_all(&member).unwrap();
463+
std::fs::write(root.join("Cargo.lock"), "").unwrap();
464+
std::fs::write(
465+
root.join(".cargo/config.toml"),
466+
"[build]\ntarget-dir = \"out\"\n",
467+
)
468+
.unwrap();
469+
// `--cwd ../ws/member` from a sibling whose config must not count.
470+
let left = t.path().join("left");
471+
std::fs::create_dir_all(left.join(".cargo")).unwrap();
472+
std::fs::create_dir_all(left.join("wrong")).unwrap();
473+
std::fs::write(
474+
left.join(".cargo/config.toml"),
475+
"[build]\nbuild-dir = \"wrong\"\n",
476+
)
477+
.unwrap();
478+
let prev = std::env::current_dir().unwrap();
479+
std::env::set_current_dir(&member).unwrap();
480+
let from_member = build_dirs_with(Path::new("."), &|_| None, None);
481+
std::env::set_current_dir(&left).unwrap();
482+
let from_sibling = build_dirs_with(Path::new("../ws/member"), &|_| None, None);
483+
std::env::set_current_dir(prev).unwrap();
484+
let out = vec![std::fs::canonicalize(root.join("out")).unwrap()];
485+
assert_eq!(from_member.dirs, out);
486+
assert_eq!(from_sibling.dirs, out);
487+
}
488+
427489
#[test]
428490
fn build_dirs_honours_env_config_and_workspace_root() {
429491
let t = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)