Skip to content
Closed
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
21 changes: 11 additions & 10 deletions crates/cargo-util/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,18 +436,21 @@ pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncesto

pub struct PathAncestors<'a> {
current: Option<&'a Path>,
stop_at: Option<PathBuf>,
stop_at: Vec<PathBuf>,
}

impl<'a> PathAncestors<'a> {
fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
let stop_at = env::var("__CARGO_TEST_ROOT")
.ok()
.map(PathBuf::from)
.or_else(|| stop_root_at.map(|p| p.to_path_buf()));
let mut stop_at = Vec::new();
if let Some(stop_root_at) = stop_root_at {
stop_at.push(stop_root_at.to_path_buf());
}
if let Ok(test_root) = env::var("__CARGO_TEST_ROOT") {
// HACK: avoid reading `~/.cargo/config` when testing Cargo itself.
stop_at.push(PathBuf::from(test_root));
}
PathAncestors {
current: Some(path),
//HACK: avoid reading `~/.cargo/config` when testing Cargo itself.
stop_at,
}
}
Expand All @@ -460,10 +463,8 @@ impl<'a> Iterator for PathAncestors<'a> {
if let Some(path) = self.current {
self.current = path.parent();

if let Some(ref stop_at) = self.stop_at {
if path == stop_at {
self.current = None;
}
if self.stop_at.iter().any(|stop_at| path == stop_at) {
self.current = None;
}

Some(path)
Expand Down
4 changes: 3 additions & 1 deletion src/cargo/ops/cargo_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,15 @@ fn print_toml_unmerged(
//
// * CARGO
// * CARGO_HOME
// * CARGO_CONFIG_STOP_SEARCH_PATH
// * CARGO_NAME
// * CARGO_EMAIL
// * CARGO_INCREMENTAL
// * CARGO_TARGET_DIR
// * CARGO_CACHE_RUSTC_INFO
//
// All of these except CARGO, CARGO_HOME, and CARGO_CACHE_RUSTC_INFO are
// All of these except CARGO, CARGO_HOME, CARGO_CONFIG_STOP_SEARCH_PATH,
// and CARGO_CACHE_RUSTC_INFO are
// actually part of the config, but they are special-cased in the code.
//
// TODO: It might be a good idea to teach the Config loader to support
Expand Down
39 changes: 37 additions & 2 deletions src/cargo/util/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ pub use schema::*;

use super::auth::RegistryConfig;

const CARGO_CONFIG_STOP_SEARCH_PATH_ENV: &str = "CARGO_CONFIG_STOP_SEARCH_PATH";

/// Helper macro for creating typed access methods.
macro_rules! get_value_typed {
($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
Expand Down Expand Up @@ -654,6 +656,34 @@ impl GlobalContext {
self.search_stop_path = Some(path);
}

fn search_stop_path_from_env(&self) -> Option<PathBuf> {
let path = PathBuf::from(
self.get_env_os(CARGO_CONFIG_STOP_SEARCH_PATH_ENV)
.filter(|path| !path.is_empty())?,
);
let path = if path.is_absolute() {
path
} else {
self.cwd.join(path)
};
Some(paths::normalize_path(&path))
}

fn effective_search_stop_path(
&self,
pwd: &Path,
env_stop_path: Option<&Path>,
) -> Option<PathBuf> {
let pwd = paths::normalize_path(pwd);
self.search_stop_path
.iter()
.cloned()
.chain(env_stop_path.map(PathBuf::from))
.map(|path| paths::normalize_path(&path))
.filter(|path| pwd.starts_with(path))
.max_by_key(|path| path.components().count())
}

/// Switches the working directory to [`std::env::current_dir`]
///
/// There is not a need to also call [`Self::reload_rooted_at`].
Expand Down Expand Up @@ -1680,8 +1710,10 @@ impl GlobalContext {
F: FnMut(&Path) -> CargoResult<()>,
{
let mut seen_dir = HashSet::new();
let env_stop_path = self.search_stop_path_from_env();
let search_stop_path = self.effective_search_stop_path(pwd, env_stop_path.as_deref());

for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
for current in paths::ancestors(pwd, search_stop_path.as_deref()) {
let config_root = current.join(".cargo");
if let Some(path) = self.get_file_path(&config_root, "config", true)? {
walk(&path)?;
Expand All @@ -1696,7 +1728,10 @@ impl GlobalContext {
// Once we're done, also be sure to walk the home directory even if it's not
// in our history to be sure we pick up that standard location for
// information.
if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
if env_stop_path.is_none()
&& !seen_dir.contains(&canonical_home)
&& !seen_dir.contains(home)
{
if let Some(path) = self.get_file_path(home, "config", true)? {
walk(&path)?;
}
Expand Down
4 changes: 3 additions & 1 deletion src/doc/man/cargo.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ for more information about configuration files.
`.cargo/config.toml`\
&nbsp;&nbsp;&nbsp;&nbsp;Cargo automatically searches for a file named `.cargo/config.toml` in the
current directory, and all parent directories. These configuration files
will be merged with the global configuration file.
will be merged with the global configuration file. Set `CARGO_CONFIG_STOP_SEARCH_PATH`
to prevent Cargo from searching above a specific directory and from loading
the global configuration file.

`$CARGO_HOME/credentials.toml`\
&nbsp;&nbsp;&nbsp;&nbsp;Private authentication information for logging in to a registry.
Expand Down
4 changes: 3 additions & 1 deletion src/doc/src/commands/cargo.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,9 @@ for more information about configuration files.
`.cargo/config.toml`\
&nbsp;&nbsp;&nbsp;&nbsp;Cargo automatically searches for a file named `.cargo/config.toml` in the
current directory, and all parent directories. These configuration files
will be merged with the global configuration file.
will be merged with the global configuration file. Set `CARGO_CONFIG_STOP_SEARCH_PATH`
to prevent Cargo from searching above a specific directory and from loading
the global configuration file.

`$CARGO_HOME/credentials.toml`\
&nbsp;&nbsp;&nbsp;&nbsp;Private authentication information for logging in to a registry.
Expand Down
5 changes: 5 additions & 0 deletions src/doc/src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ With this structure, you can specify configuration per-package, and even
possibly check it into version control. You can also specify personal defaults
with a configuration file in your home directory.

The upward search can be stopped at a specific directory by setting the
`CARGO_CONFIG_STOP_SEARCH_PATH` environment variable. The directory named by
that variable is still searched, but its ancestors are not. When this is set,
Cargo also does not load `$CARGO_HOME/config.toml`.

If a key is specified in multiple config files, the values will get merged
together. Numbers, strings, and booleans will use the value in the deeper
config directory taking precedence over ancestor directories, where the
Expand Down
5 changes: 5 additions & 0 deletions src/doc/src/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ system:
location of this directory. Once a crate is cached it is not removed by the
clean command.
For more details refer to the [guide](../guide/cargo-home.md).
* `CARGO_CONFIG_STOP_SEARCH_PATH` --- Cargo stops searching for
`.cargo/config.toml` files after reaching this directory, inclusive. Ancestor
directories above this path will not be searched. Relative paths are resolved
relative to the current working directory. When this is set, Cargo also does
not load `$CARGO_HOME/config.toml`.
* `CARGO_TARGET_DIR` --- Location of where to place all generated artifacts,
relative to the current working directory. See [`build.target-dir`] to set
via config.
Expand Down
18 changes: 18 additions & 0 deletions tests/testsuite/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,24 @@ Caused by:
);
}

#[cargo_test]
fn config_stop_search_path_env() {
write_config_at(".cargo/config.toml", "parent = true");
write_config_at("foo/.cargo/config.toml", "project = true");
write_config_at(paths::cargo_home().join("config.toml"), "home = true");

for stop_path in [paths::root().join("foo").display().to_string(), "..".into()] {
let gctx = GlobalContextBuilder::new()
.cwd("foo/bar")
.env("CARGO_CONFIG_STOP_SEARCH_PATH", stop_path)
.build();

assert_eq!(gctx.get::<Option<bool>>("parent").unwrap(), None);
assert_eq!(gctx.get::<Option<bool>>("project").unwrap(), Some(true));
assert_eq!(gctx.get::<Option<bool>>("home").unwrap(), None);
}
}

#[cargo_test]
fn struct_with_opt_inner_struct() {
// Struct with a key that is Option of another struct.
Expand Down
Loading