Skip to content

Commit 9ad19bd

Browse files
committed
Allow a list of roots in CARGO_ROOTS and repeat --root args
This generalizes `CARGO_ROOTS` so it can accept a list of directories, similar to `GIT_CEILING_DIRECTORIES`. This way a default configuration could come from a user's `.bashrc` and could be extended later by specific build tools. Similarly this also makes it so `--root` can be passed repeatedly.
1 parent 11b6f3b commit 9ad19bd

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

src/bin/cargo/cli.rs

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,23 @@ use cargo::util::style;
2020

2121
fn closest_valid_root<'a>(
2222
cwd: &std::path::Path,
23-
config_root: Option<&'a std::path::Path>,
24-
env_root: Option<&'a std::path::Path>,
25-
cli_root: Option<&'a std::path::Path>,
23+
roots: &[&'a std::path::Path],
2624
) -> anyhow::Result<Option<&'a std::path::Path>> {
27-
for (name, root) in [
28-
(".cargo/root", config_root),
29-
("CARGO_ROOT", env_root),
30-
("--root", cli_root),
31-
] {
32-
if let Some(root) = root {
33-
if !cwd.starts_with(root) {
34-
return Err(anyhow::format_err!(
35-
"the {} `{}` is not a parent of the current working directory `{}`",
36-
name,
37-
root.display(),
38-
cwd.display()
39-
));
40-
}
41-
}
42-
}
43-
Ok([config_root, env_root, cli_root]
44-
.into_iter()
45-
.flatten()
46-
.max_by_key(|root| root.components().count()))
25+
let cwd = cwd
26+
.canonicalize()
27+
.context("could not canonicalize current working directory")?;
28+
29+
// Assumes that all roots are canonicalized.
30+
let ancestor_roots =
31+
roots
32+
.iter()
33+
.filter_map(|r| if cwd.starts_with(r) { Some(*r) } else { None });
34+
35+
Ok(ancestor_roots.into_iter().max_by_key(|root| {
36+
// Prefer the root that is closest to the current working directory.
37+
// This is done by counting the number of components in the path.
38+
root.components().count()
39+
}))
4740
}
4841

4942
#[tracing::instrument(skip_all)]
@@ -54,7 +47,6 @@ pub fn main(gctx: &mut GlobalContext) -> CliResult {
5447

5548
let args = cli(gctx).try_get_matches()?;
5649

57-
let mut need_reload = false;
5850
// Update the process-level notion of cwd
5951
if let Some(new_cwd) = args.get_one::<std::path::PathBuf>("directory") {
6052
// This is a temporary hack.
@@ -76,47 +68,77 @@ pub fn main(gctx: &mut GlobalContext) -> CliResult {
7668
.into());
7769
}
7870
std::env::set_current_dir(&new_cwd).context("could not change to requested directory")?;
79-
need_reload = true;
8071
}
8172

82-
// A root directory can be specified via CARGO_ROOT, --root or the existence of a `.cargo/root` file.
83-
// If more than one is specified, the effective root is the one closest to the current working directory.
73+
// A root directories can be specified via CARGO_ROOTS, --root or the existence of a `.cargo/root` files.
74+
// If more than one root is specified, the effective root is the one closest to the current working directory.
75+
// If CARGO_ROOTS is not set, the user's home directory is used as a default root.
8476

8577
let cwd = std::env::current_dir().context("could not get current working directory")?;
8678
// Windows UNC paths are OK here
8779
let cwd = cwd
8880
.canonicalize()
8981
.context("could not canonicalize current working directory")?;
90-
let config_root = paths::ancestors(&cwd, gctx.search_stop_path())
82+
83+
// XXX: before looking for `.cargo/root` should we first try and resolve roots
84+
// from `CARGO_ROOTS` + --root so we can avoid triggering automounter issues?
85+
let root_marker = paths::ancestors(&cwd, gctx.search_stop_path())
9186
.find(|current| current.join(".cargo").join("root").exists());
92-
let env_root = gctx
93-
.get_env_os("CARGO_ROOT")
94-
.map(std::path::PathBuf::from)
95-
.map(|p| {
96-
p.canonicalize()
97-
.context("could not canonicalize CARGO_ROOT")
98-
})
99-
.transpose()?;
100-
let env_root = env_root.as_deref();
101-
102-
let cli_root = args
103-
.get_one::<std::path::PathBuf>("root")
104-
.map(|p| {
105-
p.canonicalize()
106-
.context("could not canonicalize requested root directory")
107-
})
108-
.transpose()?;
109-
let cli_root = cli_root.as_deref();
11087

111-
if let Some(root) = closest_valid_root(&cwd, config_root, env_root, cli_root)? {
88+
let mut roots: Vec<std::path::PathBuf> = Vec::new();
89+
90+
if let Some(root_marker) = root_marker {
91+
let pb = root_marker
92+
.canonicalize()
93+
.context("could not canonicalize .cargo/root")?;
94+
roots.push(pb);
95+
}
96+
97+
if let Some(paths_os) = gctx.get_env_os("CARGO_ROOTS") {
98+
for path in std::env::split_paths(&paths_os) {
99+
let pb = path.canonicalize().context(format!(
100+
"could not canonicalize CARGO_ROOTS entry `{}`",
101+
path.display()
102+
))?;
103+
roots.push(pb);
104+
}
105+
} else if let Some(home) = std::env::home_dir() {
106+
// To be safe by default, and not attempt to read config files outside of the
107+
// user's home directory, we implicitly add the home directory as a root.
108+
// Ref: https://github.com/rust-lang/rfcs/pull/3279
109+
let home = home
110+
.canonicalize()
111+
.context("could not canonicalize home directory")?;
112+
tracing::debug!(
113+
"implicitly adding home directory as root: {}",
114+
home.display()
115+
);
116+
roots.push(home);
117+
}
118+
119+
if let Some(cli_roots) = args.get_many::<std::path::PathBuf>("root") {
120+
for cli_root in cli_roots {
121+
let pb = cli_root
122+
.canonicalize()
123+
.context("could not canonicalize requested root directory")?;
124+
roots.push(pb);
125+
}
126+
}
127+
128+
let roots: Vec<_> = roots.iter().map(|p| p.as_path()).collect();
129+
130+
if let Some(root) = closest_valid_root(&cwd, &roots)? {
112131
tracing::debug!("root directory: {}", root.display());
113132
gctx.set_search_stop_path(root);
114-
need_reload = true;
133+
} else {
134+
tracing::debug!("root limited to cwd: {}", cwd.display());
135+
// If we are not running with _any_ root then we are conservative and don't
136+
// allow any ancestor traversal.
137+
gctx.set_search_stop_path(&cwd);
115138
}
116139

117-
if need_reload {
118-
gctx.reload_cwd()?;
119-
}
140+
// Reload now that we have established the cwd and root
141+
gctx.reload_cwd()?;
120142

121143
let (expanded_args, global_args) = expand_aliases(gctx, args, vec![])?;
122144

@@ -714,6 +736,7 @@ See '<cyan,bold>cargo help</> <cyan><<command>></>' for more information on a sp
714736
.help("Define a root that limits searching for workspaces and .cargo/ directories")
715737
.long("root")
716738
.value_name("ROOT")
739+
.action(ArgAction::Append)
717740
.value_hint(clap::ValueHint::DirPath)
718741
.value_parser(clap::builder::ValueParser::path_buf()),
719742
)

0 commit comments

Comments
 (0)