Skip to content
Merged
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
94 changes: 78 additions & 16 deletions crates/socket-patch-core/src/utils/cargo_build_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

use std::path::{Path, PathBuf};

use crate::formats::cargo::source_replacement::config_layer_paths;

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

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

impl ConfigDirs {
fn read(cwd: &Path, cargo_home: Option<&Path>) -> Self {
let mut files: Vec<(PathBuf, PathBuf)> = cwd
.ancestors()
.map(|d| (d.join(".cargo"), d.to_path_buf()))
.collect();
if let Some(home) = cargo_home {
let base = home.parent().map(Path::to_path_buf).unwrap_or_default();
files.push((home.to_path_buf(), base));
}
let mut out = ConfigDirs::default();
for (dot_cargo, base) in files {
let text = ["config.toml", "config"].iter().find_map(|f| {
crate::utils::fs::read_regular_to_string_sync(&dot_cargo.join(f)).ok()
});
for (file, base) in config_layer_paths(cwd, cargo_home, |p| {
crate::utils::group_commit::exists(p).unwrap_or_else(|| p.is_file())
}) {
let text = crate::utils::fs::read_regular_to_string_sync(&file).ok();
let Some(doc) = text.and_then(|t| t.parse::<toml_edit::DocumentMut>().ok()) else {
continue;
};
Expand Down Expand Up @@ -424,6 +423,69 @@ mod tests {
}
}

/// With both `.cargo/config` and `.cargo/config.toml`, cargo reads the
/// extensionless one; so must the cleaner.
#[test]
fn build_dirs_read_the_legacy_config_when_both_exist() {
let t = tempfile::tempdir().unwrap();
let root = t.path().join("ws");
std::fs::create_dir_all(root.join(".cargo")).unwrap();
std::fs::create_dir_all(root.join("legacy")).unwrap();
std::fs::create_dir_all(root.join("modern")).unwrap();
std::fs::write(
root.join(".cargo/config"),
"[build]\ntarget-dir = \"legacy\"\n",
)
.unwrap();
std::fs::write(
root.join(".cargo/config.toml"),
"[build]\ntarget-dir = \"modern\"\n",
)
.unwrap();
let d = build_dirs_with(&root, &|_| None, None);
assert_eq!(
d.dirs,
vec![std::fs::canonicalize(root.join("legacy")).unwrap()]
);
}

/// A relative cwd (the default `--cwd .`, or `../ws/member`) finds the
/// workspace root and the ancestor config above it, and nothing else.
#[test]
#[serial_test::serial]
fn build_dirs_walk_above_a_relative_cwd() {
let t = tempfile::tempdir().unwrap();
let root = t.path().join("ws");
let member = root.join("member");
std::fs::create_dir_all(root.join(".cargo")).unwrap();
std::fs::create_dir_all(root.join("out")).unwrap();
std::fs::create_dir_all(&member).unwrap();
std::fs::write(root.join("Cargo.lock"), "").unwrap();
std::fs::write(
root.join(".cargo/config.toml"),
"[build]\ntarget-dir = \"out\"\n",
)
.unwrap();
// `--cwd ../ws/member` from a sibling whose config must not count.
let left = t.path().join("left");
std::fs::create_dir_all(left.join(".cargo")).unwrap();
std::fs::create_dir_all(left.join("wrong")).unwrap();
std::fs::write(
left.join(".cargo/config.toml"),
"[build]\nbuild-dir = \"wrong\"\n",
)
.unwrap();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(&member).unwrap();
let from_member = build_dirs_with(Path::new("."), &|_| None, None);
std::env::set_current_dir(&left).unwrap();
let from_sibling = build_dirs_with(Path::new("../ws/member"), &|_| None, None);
std::env::set_current_dir(prev).unwrap();
let out = vec![std::fs::canonicalize(root.join("out")).unwrap()];
assert_eq!(from_member.dirs, out);
assert_eq!(from_sibling.dirs, out);
}

#[test]
fn build_dirs_honours_env_config_and_workspace_root() {
let t = tempfile::tempdir().unwrap();
Expand Down
Loading