Skip to content

Commit 92ca3bf

Browse files
committed
Track the first manifest path as an implicit, fallback root
To allow a package to be unpacked outside of all root directories, such as `/tmp/my-package` and allow a build to start from `/tmp/my-package/sub/dir`, Cargo is allowed to traverse parent directories until it finds its first manifest, and this then becomes a fallback root directory that will stop Cargo from looking any higher for a workspace (which could attempt to load `/tmp/Cargo.toml` that may not be safe). XXX: Instead of having a special case for the first manifest; maybe it could be better to instead _always_ add the directory of a manifest as a root directory if it's not a subdirectory of any existing root. That would have a mostly-equivalent result to the current behaviour but _might_ be simpler to document?
1 parent be2503f commit 92ca3bf

File tree

3 files changed

+37
-1
lines changed

3 files changed

+37
-1
lines changed

src/bin/cargo/commands/locate_project.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
3030
let workspace;
3131
let root = match WhatToFind::parse(args) {
3232
WhatToFind::CurrentManifest => {
33+
tracing::trace!("locate-project::exec(): finding root manifest");
3334
root_manifest = args.root_manifest(gctx)?;
3435
&root_manifest
3536
}
3637
WhatToFind::Workspace => {
38+
tracing::trace!("locate-project::exec(): finding workspace root manifest");
3739
workspace = args.workspace(gctx)?;
3840
workspace.root_manifest()
3941
}

src/cargo/util/context/mod.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ pub struct GlobalContext {
202202
/// The full set of root directories that limit config file searching.
203203
/// Sorted by path length, longest first.
204204
sorted_roots: Vec<PathBuf>,
205+
/// In case we are running outside of any user-configured root directory, we
206+
/// add the directory of the first manifest as as root directory.
207+
fallback_manifest_root: RefCell<Option<PathBuf>>,
205208
/// Directories to search for config files (invalidated if roots or cwd changes).
206209
config_search_route: RefCell<Option<SearchRoute>>,
207210
/// Directories to search for manifest files (invalidated if roots or starting point changes).
@@ -319,6 +322,7 @@ impl GlobalContext {
319322
shell: RefCell::new(shell),
320323
cwd,
321324
sorted_roots: Vec::new(),
325+
fallback_manifest_root: RefCell::new(None),
322326
config_search_route: RefCell::new(None),
323327
manifest_search_route: RefCell::new(None),
324328
values: LazyCell::new(),
@@ -617,6 +621,24 @@ impl GlobalContext {
617621
Ok(())
618622
}
619623

624+
pub fn ensure_fallback_root<P: AsRef<Path>>(&self, path: P) {
625+
let path = path
626+
.as_ref()
627+
.canonicalize()
628+
.expect("failed to canonicalize path");
629+
if self.fallback_manifest_root.borrow_mut().is_none() {
630+
tracing::debug!("Setting fallback manifest root to {:?}", path);
631+
*self.fallback_manifest_root.borrow_mut() = Some(path);
632+
*self.config_search_route.borrow_mut() = None;
633+
*self.manifest_search_route.borrow_mut() = None;
634+
} else {
635+
tracing::debug!(
636+
"Fallback manifest root already set to {:?}, not changing",
637+
self.fallback_manifest_root.borrow()
638+
);
639+
}
640+
}
641+
620642
fn find_nearest_root<P: AsRef<Path>>(
621643
&self,
622644
start: P,
@@ -626,7 +648,10 @@ impl GlobalContext {
626648
.canonicalize()
627649
.context("couldn't canonicalize path")?;
628650

629-
tracing::debug!("Searching sorted roots for start {:?}", self.sorted_roots);
651+
tracing::debug!(
652+
"Searching sorted roots for closest ancestor {:?}",
653+
self.sorted_roots
654+
);
630655
let root = self
631656
.sorted_roots
632657
.iter()
@@ -636,6 +661,9 @@ impl GlobalContext {
636661
if let Some(root) = root {
637662
tracing::debug!("Found candidate root {:?}", root);
638663
Ok(Some((start, root)))
664+
} else if let Some(fallback_root) = self.fallback_manifest_root.borrow().as_ref() {
665+
tracing::debug!("Using manifest path as fallback root");
666+
Ok(Some((start, fallback_root.clone())))
639667
} else {
640668
tracing::debug!("No candidate root found for start {:?}", start);
641669
Ok(None)

src/cargo/util/important_paths.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pub fn find_root_manifest_for_wd(gctx: &GlobalContext, cwd: &Path) -> CargoResul
1414
for current in paths::ancestors(&search_route.start, search_route.root.as_deref()) {
1515
let manifest = current.join(valid_cargo_toml_file_name);
1616
if manifest.exists() {
17+
// In case we are running outside of any root directory, the directory for the
18+
// first root manifest we find will become the fallback root. This is part of
19+
// a safety trade-off that allows us to traverse unknown ancestors to find
20+
// a package, but limits the risk of continuing to traverse and load manifests
21+
// that we might not own (such as `/tmp/Cargo.toml`)
22+
gctx.ensure_fallback_root(current);
1723
return Ok(manifest);
1824
}
1925
if current.join(invalid_cargo_toml_file_name).exists() {

0 commit comments

Comments
 (0)