cargo/util/context/
mod.rs

1//! Cargo's config system.
2//!
3//! The [`GlobalContext`] object contains general information about the environment,
4//! and provides access to Cargo's configuration files.
5//!
6//! ## Config value API
7//!
8//! The primary API for fetching user-defined config values is the
9//! [`GlobalContext::get`] method. It uses `serde` to translate config values to a
10//! target type.
11//!
12//! There are a variety of helper types for deserializing some common formats:
13//!
14//! - [`value::Value`]: This type provides access to the location where the
15//!   config value was defined.
16//! - [`ConfigRelativePath`]: For a path that is relative to where it is
17//!   defined.
18//! - [`PathAndArgs`]: Similar to [`ConfigRelativePath`],
19//!   but also supports a list of arguments, useful for programs to execute.
20//! - [`StringList`]: Get a value that is either a list or a whitespace split
21//!   string.
22//!
23//! # Config schemas
24//!
25//! Configuration schemas are defined in the [`schema`] module.
26//!
27//! ## Config deserialization
28//!
29//! Cargo uses a two-layer deserialization approach:
30//!
31//! 1. **External sources → `ConfigValue`** ---
32//!    Configuration files, environment variables, and CLI `--config` arguments
33//!    are parsed into [`ConfigValue`] instances via [`ConfigValue::from_toml`].
34//!    These parsed results are stored in [`GlobalContext`].
35//!
36//! 2. **`ConfigValue` → Target types** ---
37//!    The [`GlobalContext::get`] method uses a [custom serde deserializer](Deserializer)
38//!    to convert [`ConfigValue`] instances to the caller's desired type.
39//!    Precedence between [`ConfigValue`] sources is resolved during retrieval
40//!    based on [`Definition`] priority.
41//!    See the top-level documentation of the [`de`] module for more.
42//!
43//! ## Map key recommendations
44//!
45//! Handling tables that have arbitrary keys can be tricky, particularly if it
46//! should support environment variables. In general, if possible, the caller
47//! should pass the full key path into the `get()` method so that the config
48//! deserializer can properly handle environment variables (which need to be
49//! uppercased, and dashes converted to underscores).
50//!
51//! A good example is the `[target]` table. The code will request
52//! `target.$TRIPLE` and the config system can then appropriately fetch
53//! environment variables like `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER`.
54//! Conversely, it is not possible do the same thing for the `cfg()` target
55//! tables (because Cargo must fetch all of them), so those do not support
56//! environment variables.
57//!
58//! Try to avoid keys that are a prefix of another with a dash/underscore. For
59//! example `build.target` and `build.target-dir`. This is OK if these are not
60//! structs/maps, but if it is a struct or map, then it will not be able to
61//! read the environment variable due to ambiguity. (See `ConfigMapAccess` for
62//! more details.)
63
64use std::borrow::Cow;
65use std::collections::{HashMap, HashSet};
66use std::env;
67use std::ffi::{OsStr, OsString};
68use std::fmt;
69use std::fs::{self, File};
70use std::io::SeekFrom;
71use std::io::prelude::*;
72use std::mem;
73use std::path::{Path, PathBuf};
74use std::str::FromStr;
75use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
76use std::time::Instant;
77
78use self::ConfigValue as CV;
79use crate::core::compiler::rustdoc::RustdocExternMap;
80use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
81use crate::core::shell::Verbosity;
82use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
83use crate::ops::RegistryCredentialConfig;
84use crate::sources::CRATES_IO_INDEX;
85use crate::sources::CRATES_IO_REGISTRY;
86use crate::util::OnceExt as _;
87use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
88use crate::util::errors::CargoResult;
89use crate::util::network::http::configure_http_handle;
90use crate::util::network::http::http_handle;
91use crate::util::restricted_names::is_glob_pattern;
92use crate::util::{CanonicalUrl, closest_msg, internal};
93use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
94
95use annotate_snippets::Level;
96use anyhow::{Context as _, anyhow, bail, format_err};
97use cargo_credential::Secret;
98use cargo_util::paths;
99use cargo_util_schemas::manifest::RegistryName;
100use curl::easy::Easy;
101use itertools::Itertools;
102use serde::Deserialize;
103use serde::de::IntoDeserializer as _;
104use time::OffsetDateTime;
105use toml_edit::Item;
106use url::Url;
107
108mod de;
109use de::Deserializer;
110
111mod error;
112pub use error::ConfigError;
113
114mod value;
115pub use value::{Definition, OptValue, Value};
116
117mod key;
118pub use key::ConfigKey;
119
120mod config_value;
121pub use config_value::ConfigValue;
122use config_value::is_nonmergeable_list;
123
124mod path;
125pub use path::{ConfigRelativePath, PathAndArgs};
126
127mod target;
128pub use target::{TargetCfgConfig, TargetConfig};
129
130mod environment;
131use environment::Env;
132
133mod schema;
134pub use schema::*;
135
136use super::auth::RegistryConfig;
137
138/// Helper macro for creating typed access methods.
139macro_rules! get_value_typed {
140    ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
141        /// Low-level private method for getting a config value as an [`OptValue`].
142        fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
143            let cv = self.get_cv(key)?;
144            let env = self.get_config_env::<$ty>(key)?;
145            match (cv, env) {
146                (Some(CV::$variant(val, definition)), Some(env)) => {
147                    if definition.is_higher_priority(&env.definition) {
148                        Ok(Some(Value { val, definition }))
149                    } else {
150                        Ok(Some(env))
151                    }
152                }
153                (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
154                (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
155                (None, Some(env)) => Ok(Some(env)),
156                (None, None) => Ok(None),
157            }
158        }
159    };
160}
161
162pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
163    "paths",
164    "alias",
165    "build",
166    "credential-alias",
167    "doc",
168    "env",
169    "future-incompat-report",
170    "cache",
171    "cargo-new",
172    "http",
173    "install",
174    "net",
175    "patch",
176    "profile",
177    "resolver",
178    "registries",
179    "registry",
180    "source",
181    "target",
182    "term",
183];
184
185/// Indicates why a config value is being loaded.
186#[derive(Clone, Copy, Debug)]
187enum WhyLoad {
188    /// Loaded due to a request from the global cli arg `--config`
189    ///
190    /// Indirect configs loaded via [`ConfigInclude`] are also seen as from cli args,
191    /// if the initial config is being loaded from cli.
192    Cli,
193    /// Loaded due to config file discovery.
194    FileDiscovery,
195}
196
197/// A previously generated authentication token and the data needed to determine if it can be reused.
198#[derive(Debug)]
199pub struct CredentialCacheValue {
200    pub token_value: Secret<String>,
201    pub expiration: Option<OffsetDateTime>,
202    pub operation_independent: bool,
203}
204
205/// Configuration information for cargo. This is not specific to a build, it is information
206/// relating to cargo itself.
207#[derive(Debug)]
208pub struct GlobalContext {
209    /// The location of the user's Cargo home directory. OS-dependent.
210    home_path: Filesystem,
211    /// Information about how to write messages to the shell
212    shell: Mutex<Shell>,
213    /// A collection of configuration options
214    values: OnceLock<HashMap<String, ConfigValue>>,
215    /// A collection of configuration options from the credentials file
216    credential_values: OnceLock<HashMap<String, ConfigValue>>,
217    /// CLI config values, passed in via `configure`.
218    cli_config: Option<Vec<String>>,
219    /// The current working directory of cargo
220    cwd: PathBuf,
221    /// Directory where config file searching should stop (inclusive).
222    search_stop_path: Option<PathBuf>,
223    /// The location of the cargo executable (path to current process)
224    cargo_exe: OnceLock<PathBuf>,
225    /// The location of the rustdoc executable
226    rustdoc: OnceLock<PathBuf>,
227    /// Whether we are printing extra verbose messages
228    extra_verbose: bool,
229    /// `frozen` is the same as `locked`, but additionally will not access the
230    /// network to determine if the lock file is out-of-date.
231    frozen: bool,
232    /// `locked` is set if we should not update lock files. If the lock file
233    /// is missing, or needs to be updated, an error is produced.
234    locked: bool,
235    /// `offline` is set if we should never access the network, but otherwise
236    /// continue operating if possible.
237    offline: bool,
238    /// A global static IPC control mechanism (used for managing parallel builds)
239    jobserver: Option<jobserver::Client>,
240    /// Cli flags of the form "-Z something" merged with config file values
241    unstable_flags: CliUnstable,
242    /// Cli flags of the form "-Z something"
243    unstable_flags_cli: Option<Vec<String>>,
244    /// A handle on curl easy mode for http calls
245    easy: OnceLock<Mutex<Easy>>,
246    /// Cache of the `SourceId` for crates.io
247    crates_io_source_id: OnceLock<SourceId>,
248    /// If false, don't cache `rustc --version --verbose` invocations
249    cache_rustc_info: bool,
250    /// Creation time of this config, used to output the total build time
251    creation_time: Instant,
252    /// Target Directory via resolved Cli parameter
253    target_dir: Option<Filesystem>,
254    /// Environment variable snapshot.
255    env: Env,
256    /// Tracks which sources have been updated to avoid multiple updates.
257    updated_sources: Mutex<HashSet<SourceId>>,
258    /// Cache of credentials from configuration or credential providers.
259    /// Maps from url to credential value.
260    credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
261    /// Cache of registry config from the `[registries]` table.
262    registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
263    /// Locks on the package and index caches.
264    package_cache_lock: CacheLocker,
265    /// Cached configuration parsed by Cargo
266    http_config: OnceLock<CargoHttpConfig>,
267    future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
268    net_config: OnceLock<CargoNetConfig>,
269    build_config: OnceLock<CargoBuildConfig>,
270    target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
271    doc_extern_map: OnceLock<RustdocExternMap>,
272    progress_config: ProgressConfig,
273    env_config: OnceLock<Arc<HashMap<String, OsString>>>,
274    /// This should be false if:
275    /// - this is an artifact of the rustc distribution process for "stable" or for "beta"
276    /// - this is an `#[test]` that does not opt in with `enable_nightly_features`
277    /// - this is an integration test that uses `ProcessBuilder`
278    ///      that does not opt in with `masquerade_as_nightly_cargo`
279    /// This should be true if:
280    /// - this is an artifact of the rustc distribution process for "nightly"
281    /// - this is being used in the rustc distribution process internally
282    /// - this is a cargo executable that was built from source
283    /// - this is an `#[test]` that called `enable_nightly_features`
284    /// - this is an integration test that uses `ProcessBuilder`
285    ///       that called `masquerade_as_nightly_cargo`
286    /// It's public to allow tests use nightly features.
287    /// NOTE: this should be set before `configure()`. If calling this from an integration test,
288    /// consider using `ConfigBuilder::enable_nightly_features` instead.
289    pub nightly_features_allowed: bool,
290    /// `WorkspaceRootConfigs` that have been found
291    ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
292    /// The global cache tracker is a database used to track disk cache usage.
293    global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
294    /// A cache of modifications to make to [`GlobalContext::global_cache_tracker`],
295    /// saved to disk in a batch to improve performance.
296    deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
297}
298
299impl GlobalContext {
300    /// Creates a new config instance.
301    ///
302    /// This is typically used for tests or other special cases. `default` is
303    /// preferred otherwise.
304    ///
305    /// This does only minimal initialization. In particular, it does not load
306    /// any config files from disk. Those will be loaded lazily as-needed.
307    pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
308        static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
309        static INIT: Once = Once::new();
310
311        // This should be called early on in the process, so in theory the
312        // unsafety is ok here. (taken ownership of random fds)
313        INIT.call_once(|| unsafe {
314            if let Some(client) = jobserver::Client::from_env() {
315                GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
316            }
317        });
318
319        let env = Env::new();
320
321        let cache_key = "CARGO_CACHE_RUSTC_INFO";
322        let cache_rustc_info = match env.get_env_os(cache_key) {
323            Some(cache) => cache != "0",
324            _ => true,
325        };
326
327        GlobalContext {
328            home_path: Filesystem::new(homedir),
329            shell: Mutex::new(shell),
330            cwd,
331            search_stop_path: None,
332            values: Default::default(),
333            credential_values: Default::default(),
334            cli_config: None,
335            cargo_exe: Default::default(),
336            rustdoc: Default::default(),
337            extra_verbose: false,
338            frozen: false,
339            locked: false,
340            offline: false,
341            jobserver: unsafe {
342                if GLOBAL_JOBSERVER.is_null() {
343                    None
344                } else {
345                    Some((*GLOBAL_JOBSERVER).clone())
346                }
347            },
348            unstable_flags: CliUnstable::default(),
349            unstable_flags_cli: None,
350            easy: Default::default(),
351            crates_io_source_id: Default::default(),
352            cache_rustc_info,
353            creation_time: Instant::now(),
354            target_dir: None,
355            env,
356            updated_sources: Default::default(),
357            credential_cache: Default::default(),
358            registry_config: Default::default(),
359            package_cache_lock: CacheLocker::new(),
360            http_config: Default::default(),
361            future_incompat_config: Default::default(),
362            net_config: Default::default(),
363            build_config: Default::default(),
364            target_cfgs: Default::default(),
365            doc_extern_map: Default::default(),
366            progress_config: ProgressConfig::default(),
367            env_config: Default::default(),
368            nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
369            ws_roots: Default::default(),
370            global_cache_tracker: Default::default(),
371            deferred_global_last_use: Default::default(),
372        }
373    }
374
375    /// Creates a new instance, with all default settings.
376    ///
377    /// This does only minimal initialization. In particular, it does not load
378    /// any config files from disk. Those will be loaded lazily as-needed.
379    pub fn default() -> CargoResult<GlobalContext> {
380        let shell = Shell::new();
381        let cwd =
382            env::current_dir().context("couldn't get the current directory of the process")?;
383        let homedir = homedir(&cwd).ok_or_else(|| {
384            anyhow!(
385                "Cargo couldn't find your home directory. \
386                 This probably means that $HOME was not set."
387            )
388        })?;
389        Ok(GlobalContext::new(shell, cwd, homedir))
390    }
391
392    /// Gets the user's Cargo home directory (OS-dependent).
393    pub fn home(&self) -> &Filesystem {
394        &self.home_path
395    }
396
397    /// Returns a path to display to the user with the location of their home
398    /// config file (to only be used for displaying a diagnostics suggestion,
399    /// such as recommending where to add a config value).
400    pub fn diagnostic_home_config(&self) -> String {
401        let home = self.home_path.as_path_unlocked();
402        let path = match self.get_file_path(home, "config", false) {
403            Ok(Some(existing_path)) => existing_path,
404            _ => home.join("config.toml"),
405        };
406        path.to_string_lossy().to_string()
407    }
408
409    /// Gets the Cargo Git directory (`<cargo_home>/git`).
410    pub fn git_path(&self) -> Filesystem {
411        self.home_path.join("git")
412    }
413
414    /// Gets the directory of code sources Cargo checkouts from Git bare repos
415    /// (`<cargo_home>/git/checkouts`).
416    pub fn git_checkouts_path(&self) -> Filesystem {
417        self.git_path().join("checkouts")
418    }
419
420    /// Gets the directory for all Git bare repos Cargo clones
421    /// (`<cargo_home>/git/db`).
422    pub fn git_db_path(&self) -> Filesystem {
423        self.git_path().join("db")
424    }
425
426    /// Gets the Cargo base directory for all registry information (`<cargo_home>/registry`).
427    pub fn registry_base_path(&self) -> Filesystem {
428        self.home_path.join("registry")
429    }
430
431    /// Gets the Cargo registry index directory (`<cargo_home>/registry/index`).
432    pub fn registry_index_path(&self) -> Filesystem {
433        self.registry_base_path().join("index")
434    }
435
436    /// Gets the Cargo registry cache directory (`<cargo_home>/registry/cache`).
437    pub fn registry_cache_path(&self) -> Filesystem {
438        self.registry_base_path().join("cache")
439    }
440
441    /// Gets the Cargo registry source directory (`<cargo_home>/registry/src`).
442    pub fn registry_source_path(&self) -> Filesystem {
443        self.registry_base_path().join("src")
444    }
445
446    /// Gets the default Cargo registry.
447    pub fn default_registry(&self) -> CargoResult<Option<String>> {
448        Ok(self
449            .get_string("registry.default")?
450            .map(|registry| registry.val))
451    }
452
453    /// Gets a reference to the shell, e.g., for writing error messages.
454    pub fn shell(&self) -> MutexGuard<'_, Shell> {
455        self.shell.lock().unwrap()
456    }
457
458    /// Assert [`Self::shell`] is not in use
459    ///
460    /// Testing might not identify bugs with two accesses to `shell` at once
461    /// due to conditional logic,
462    /// so place this outside of the conditions to catch these bugs in more situations.
463    pub fn debug_assert_shell_not_borrowed(&self) {
464        if cfg!(debug_assertions) {
465            match self.shell.try_lock() {
466                Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
467                Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
468            }
469        }
470    }
471
472    /// Gets the path to the `rustdoc` executable.
473    pub fn rustdoc(&self) -> CargoResult<&Path> {
474        self.rustdoc
475            .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
476            .map(AsRef::as_ref)
477    }
478
479    /// Gets the path to the `rustc` executable.
480    pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
481        let cache_location =
482            ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
483        let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
484        let rustc_workspace_wrapper = self.maybe_get_tool(
485            "rustc_workspace_wrapper",
486            &self.build_config()?.rustc_workspace_wrapper,
487        );
488
489        Rustc::new(
490            self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
491            wrapper,
492            rustc_workspace_wrapper,
493            &self
494                .home()
495                .join("bin")
496                .join("rustc")
497                .into_path_unlocked()
498                .with_extension(env::consts::EXE_EXTENSION),
499            if self.cache_rustc_info {
500                cache_location
501            } else {
502                None
503            },
504            self,
505        )
506    }
507
508    /// Gets the path to the `cargo` executable.
509    pub fn cargo_exe(&self) -> CargoResult<&Path> {
510        self.cargo_exe
511            .try_borrow_with(|| {
512                let from_env = || -> CargoResult<PathBuf> {
513                    // Try re-using the `cargo` set in the environment already. This allows
514                    // commands that use Cargo as a library to inherit (via `cargo <subcommand>`)
515                    // or set (by setting `$CARGO`) a correct path to `cargo` when the current exe
516                    // is not actually cargo (e.g., `cargo-*` binaries, Valgrind, `ld.so`, etc.).
517                    let exe = self
518                        .get_env_os(crate::CARGO_ENV)
519                        .map(PathBuf::from)
520                        .ok_or_else(|| anyhow!("$CARGO not set"))?;
521                    Ok(exe)
522                };
523
524                fn from_current_exe() -> CargoResult<PathBuf> {
525                    // Try fetching the path to `cargo` using `env::current_exe()`.
526                    // The method varies per operating system and might fail; in particular,
527                    // it depends on `/proc` being mounted on Linux, and some environments
528                    // (like containers or chroots) may not have that available.
529                    let exe = env::current_exe()?;
530                    Ok(exe)
531                }
532
533                fn from_argv() -> CargoResult<PathBuf> {
534                    // Grab `argv[0]` and attempt to resolve it to an absolute path.
535                    // If `argv[0]` has one component, it must have come from a `PATH` lookup,
536                    // so probe `PATH` in that case.
537                    // Otherwise, it has multiple components and is either:
538                    // - a relative path (e.g., `./cargo`, `target/debug/cargo`), or
539                    // - an absolute path (e.g., `/usr/local/bin/cargo`).
540                    let argv0 = env::args_os()
541                        .map(PathBuf::from)
542                        .next()
543                        .ok_or_else(|| anyhow!("no argv[0]"))?;
544                    paths::resolve_executable(&argv0)
545                }
546
547                // Determines whether `path` is a cargo binary.
548                // See: https://github.com/rust-lang/cargo/issues/15099#issuecomment-2666737150
549                fn is_cargo(path: &Path) -> bool {
550                    path.file_stem() == Some(OsStr::new("cargo"))
551                }
552
553                let from_current_exe = from_current_exe();
554                if from_current_exe.as_deref().is_ok_and(is_cargo) {
555                    return from_current_exe;
556                }
557
558                let from_argv = from_argv();
559                if from_argv.as_deref().is_ok_and(is_cargo) {
560                    return from_argv;
561                }
562
563                let exe = from_env()
564                    .or(from_current_exe)
565                    .or(from_argv)
566                    .context("couldn't get the path to cargo executable")?;
567                Ok(exe)
568            })
569            .map(AsRef::as_ref)
570    }
571
572    /// Which package sources have been updated, used to ensure it is only done once.
573    pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
574        self.updated_sources.lock().unwrap()
575    }
576
577    /// Cached credentials from credential providers or configuration.
578    pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
579        self.credential_cache.lock().unwrap()
580    }
581
582    /// Cache of already parsed registries from the `[registries]` table.
583    pub(crate) fn registry_config(
584        &self,
585    ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
586        self.registry_config.lock().unwrap()
587    }
588
589    /// Gets all config values from disk.
590    ///
591    /// This will lazy-load the values as necessary. Callers are responsible
592    /// for checking environment variables. Callers outside of the `config`
593    /// module should avoid using this.
594    pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
595        self.values.try_borrow_with(|| self.load_values())
596    }
597
598    /// Gets a mutable copy of the on-disk config values.
599    ///
600    /// This requires the config values to already have been loaded. This
601    /// currently only exists for `cargo vendor` to remove the `source`
602    /// entries. This doesn't respect environment variables. You should avoid
603    /// using this if possible.
604    pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
605        let _ = self.values()?;
606        Ok(self.values.get_mut().expect("already loaded config values"))
607    }
608
609    // Note: this is used by RLS, not Cargo.
610    pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
611        if self.values.get().is_some() {
612            bail!("config values already found")
613        }
614        match self.values.set(values.into()) {
615            Ok(()) => Ok(()),
616            Err(_) => bail!("could not fill values"),
617        }
618    }
619
620    /// Sets the path where ancestor config file searching will stop. The
621    /// given path is included, but its ancestors are not.
622    pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
623        let path = path.into();
624        debug_assert!(self.cwd.starts_with(&path));
625        self.search_stop_path = Some(path);
626    }
627
628    /// Switches the working directory to [`std::env::current_dir`]
629    ///
630    /// There is not a need to also call [`Self::reload_rooted_at`].
631    pub fn reload_cwd(&mut self) -> CargoResult<()> {
632        let cwd =
633            env::current_dir().context("couldn't get the current directory of the process")?;
634        let homedir = homedir(&cwd).ok_or_else(|| {
635            anyhow!(
636                "Cargo couldn't find your home directory. \
637                 This probably means that $HOME was not set."
638            )
639        })?;
640
641        self.cwd = cwd;
642        self.home_path = Filesystem::new(homedir);
643        self.reload_rooted_at(self.cwd.clone())?;
644        Ok(())
645    }
646
647    /// Reloads on-disk configuration values, starting at the given path and
648    /// walking up its ancestors.
649    pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
650        let values = self.load_values_from(path.as_ref())?;
651        self.values.replace(values);
652        self.merge_cli_args()?;
653        self.load_unstable_flags_from_config()?;
654        Ok(())
655    }
656
657    /// The current working directory.
658    pub fn cwd(&self) -> &Path {
659        &self.cwd
660    }
661
662    /// The `target` output directory to use.
663    ///
664    /// Returns `None` if the user has not chosen an explicit directory.
665    ///
666    /// Callers should prefer [`Workspace::target_dir`] instead.
667    pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
668        if let Some(dir) = &self.target_dir {
669            Ok(Some(dir.clone()))
670        } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
671            // Check if the CARGO_TARGET_DIR environment variable is set to an empty string.
672            if dir.is_empty() {
673                bail!(
674                    "the target directory is set to an empty string in the \
675                     `CARGO_TARGET_DIR` environment variable"
676                )
677            }
678
679            Ok(Some(Filesystem::new(self.cwd.join(dir))))
680        } else if let Some(val) = &self.build_config()?.target_dir {
681            let path = val.resolve_path(self);
682
683            // Check if the target directory is set to an empty string in the config.toml file.
684            if val.raw_value().is_empty() {
685                bail!(
686                    "the target directory is set to an empty string in {}",
687                    val.value().definition
688                )
689            }
690
691            Ok(Some(Filesystem::new(path)))
692        } else {
693            Ok(None)
694        }
695    }
696
697    /// The directory to use for intermediate build artifacts.
698    ///
699    /// Callers should prefer [`Workspace::build_dir`] instead.
700    pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
701        let Some(val) = &self.build_config()?.build_dir else {
702            return Ok(None);
703        };
704        self.custom_build_dir(val, workspace_manifest_path)
705            .map(Some)
706    }
707
708    /// The directory to use for intermediate build artifacts.
709    ///
710    /// Callers should prefer [`Workspace::build_dir`] instead.
711    pub fn custom_build_dir(
712        &self,
713        val: &ConfigRelativePath,
714        workspace_manifest_path: &Path,
715    ) -> CargoResult<Filesystem> {
716        let replacements = [
717            (
718                "{workspace-root}",
719                workspace_manifest_path
720                    .parent()
721                    .unwrap()
722                    .to_str()
723                    .context("workspace root was not valid utf-8")?
724                    .to_string(),
725            ),
726            (
727                "{cargo-cache-home}",
728                self.home()
729                    .as_path_unlocked()
730                    .to_str()
731                    .context("cargo home was not valid utf-8")?
732                    .to_string(),
733            ),
734            ("{workspace-path-hash}", {
735                let real_path = std::fs::canonicalize(workspace_manifest_path)
736                    .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
737                let hash = crate::util::hex::short_hash(&real_path);
738                format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
739            }),
740        ];
741
742        let template_variables = replacements
743            .iter()
744            .map(|(key, _)| key[1..key.len() - 1].to_string())
745            .collect_vec();
746
747        let path = val
748                .resolve_templated_path(self, replacements)
749                .map_err(|e| match e {
750                    path::ResolveTemplateError::UnexpectedVariable {
751                        variable,
752                        raw_template,
753                    } => {
754                        let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
755                        if suggestion == "" {
756                            let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
757                            suggestion = format!("\n\nhelp: available template variables are {variables}");
758                        }
759                        anyhow!(
760                            "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
761                        )
762                    },
763                    path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
764                        let (btype, literal) = match bracket_type {
765                            path::BracketType::Opening => ("opening", "{"),
766                            path::BracketType::Closing => ("closing", "}"),
767                        };
768
769                        anyhow!(
770                            "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
771                        )
772                    }
773                })?;
774
775        // Check if the target directory is set to an empty string in the config.toml file.
776        if val.raw_value().is_empty() {
777            bail!(
778                "the build directory is set to an empty string in {}",
779                val.value().definition
780            )
781        }
782
783        Ok(Filesystem::new(path))
784    }
785
786    /// Get a configuration value by key.
787    ///
788    /// This does NOT look at environment variables. See `get_cv_with_env` for
789    /// a variant that supports environment variables.
790    fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
791        if let Some(vals) = self.credential_values.get() {
792            let val = self.get_cv_helper(key, vals)?;
793            if val.is_some() {
794                return Ok(val);
795            }
796        }
797        self.get_cv_helper(key, &*self.values()?)
798    }
799
800    fn get_cv_helper(
801        &self,
802        key: &ConfigKey,
803        vals: &HashMap<String, ConfigValue>,
804    ) -> CargoResult<Option<ConfigValue>> {
805        tracing::trace!("get cv {:?}", key);
806        if key.is_root() {
807            // Returning the entire root table (for example `cargo config get`
808            // with no key). The definition here shouldn't matter.
809            return Ok(Some(CV::Table(
810                vals.clone(),
811                Definition::Path(PathBuf::new()),
812            )));
813        }
814        let mut parts = key.parts().enumerate();
815        let Some(mut val) = vals.get(parts.next().unwrap().1) else {
816            return Ok(None);
817        };
818        for (i, part) in parts {
819            match val {
820                CV::Table(map, _) => {
821                    val = match map.get(part) {
822                        Some(val) => val,
823                        None => return Ok(None),
824                    }
825                }
826                CV::Integer(_, def)
827                | CV::String(_, def)
828                | CV::List(_, def)
829                | CV::Boolean(_, def) => {
830                    let mut key_so_far = ConfigKey::new();
831                    for part in key.parts().take(i) {
832                        key_so_far.push(part);
833                    }
834                    bail!(
835                        "expected table for configuration key `{}`, \
836                         but found {} in {}",
837                        key_so_far,
838                        val.desc(),
839                        def
840                    )
841                }
842            }
843        }
844        Ok(Some(val.clone()))
845    }
846
847    /// This is a helper for getting a CV from a file or env var.
848    pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
849        // Determine if value comes from env, cli, or file, and merge env if
850        // possible.
851        let cv = self.get_cv(key)?;
852        if key.is_root() {
853            // Root table can't have env value.
854            return Ok(cv);
855        }
856        let env = self.env.get_str(key.as_env_key());
857        let env_def = Definition::Environment(key.as_env_key().to_string());
858        let use_env = match (&cv, env) {
859            // Lists are always merged.
860            (Some(CV::List(..)), Some(_)) => true,
861            (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
862            (None, Some(_)) => true,
863            _ => false,
864        };
865
866        if !use_env {
867            return Ok(cv);
868        }
869
870        // Future note: If you ever need to deserialize a non-self describing
871        // map type, this should implement a starts_with check (similar to how
872        // ConfigMapAccess does).
873        let env = env.unwrap();
874        if env == "true" {
875            Ok(Some(CV::Boolean(true, env_def)))
876        } else if env == "false" {
877            Ok(Some(CV::Boolean(false, env_def)))
878        } else if let Ok(i) = env.parse::<i64>() {
879            Ok(Some(CV::Integer(i, env_def)))
880        } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
881            match cv {
882                Some(CV::List(mut cv_list, cv_def)) => {
883                    // Merge with config file.
884                    self.get_env_list(key, &mut cv_list)?;
885                    Ok(Some(CV::List(cv_list, cv_def)))
886                }
887                Some(cv) => {
888                    // This can't assume StringList.
889                    // Return an error, which is the behavior of merging
890                    // multiple config.toml files with the same scenario.
891                    bail!(
892                        "unable to merge array env for config `{}`\n\
893                        file: {:?}\n\
894                        env: {}",
895                        key,
896                        cv,
897                        env
898                    );
899                }
900                None => {
901                    let mut cv_list = Vec::new();
902                    self.get_env_list(key, &mut cv_list)?;
903                    Ok(Some(CV::List(cv_list, env_def)))
904                }
905            }
906        } else {
907            // Try to merge if possible.
908            match cv {
909                Some(CV::List(mut cv_list, cv_def)) => {
910                    // Merge with config file.
911                    self.get_env_list(key, &mut cv_list)?;
912                    Ok(Some(CV::List(cv_list, cv_def)))
913                }
914                _ => {
915                    // Note: CV::Table merging is not implemented, as env
916                    // vars do not support table values. In the future, we
917                    // could check for `{}`, and interpret it as TOML if
918                    // that seems useful.
919                    Ok(Some(CV::String(env.to_string(), env_def)))
920                }
921            }
922        }
923    }
924
925    /// Helper primarily for testing.
926    pub fn set_env(&mut self, env: HashMap<String, String>) {
927        self.env = Env::from_map(env);
928    }
929
930    /// Returns all environment variables as an iterator,
931    /// keeping only entries where both the key and value are valid UTF-8.
932    pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
933        self.env.iter_str()
934    }
935
936    /// Returns all environment variable keys, filtering out keys that are not valid UTF-8.
937    fn env_keys(&self) -> impl Iterator<Item = &str> {
938        self.env.keys_str()
939    }
940
941    fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
942    where
943        T: FromStr,
944        <T as FromStr>::Err: fmt::Display,
945    {
946        match self.env.get_str(key.as_env_key()) {
947            Some(value) => {
948                let definition = Definition::Environment(key.as_env_key().to_string());
949                Ok(Some(Value {
950                    val: value
951                        .parse()
952                        .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
953                    definition,
954                }))
955            }
956            None => {
957                self.check_environment_key_case_mismatch(key);
958                Ok(None)
959            }
960        }
961    }
962
963    /// Get the value of environment variable `key` through the snapshot in
964    /// [`GlobalContext`].
965    ///
966    /// This can be used similarly to [`std::env::var`].
967    pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
968        self.env.get_env(key)
969    }
970
971    /// Get the value of environment variable `key` through the snapshot in
972    /// [`GlobalContext`].
973    ///
974    /// This can be used similarly to [`std::env::var_os`].
975    pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
976        self.env.get_env_os(key)
977    }
978
979    /// Check if the [`GlobalContext`] contains a given [`ConfigKey`].
980    ///
981    /// See `ConfigMapAccess` for a description of `env_prefix_ok`.
982    fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
983        if self.env.contains_key(key.as_env_key()) {
984            return Ok(true);
985        }
986        if env_prefix_ok {
987            let env_prefix = format!("{}_", key.as_env_key());
988            if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
989                return Ok(true);
990            }
991        }
992        if self.get_cv(key)?.is_some() {
993            return Ok(true);
994        }
995        self.check_environment_key_case_mismatch(key);
996
997        Ok(false)
998    }
999
1000    fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1001        if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1002            let _ = self.shell().warn(format!(
1003                "environment variables are expected to use uppercase letters and underscores, \
1004                the variable `{}` will be ignored and have no effect",
1005                env_key
1006            ));
1007        }
1008    }
1009
1010    /// Get a string config value.
1011    ///
1012    /// See `get` for more details.
1013    pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
1014        self.get::<OptValue<String>>(key)
1015    }
1016
1017    fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1018        let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1019        if is_path {
1020            definition.root(self.cwd()).join(value)
1021        } else {
1022            // A pathless name.
1023            PathBuf::from(value)
1024        }
1025    }
1026
1027    /// Internal method for getting an environment variable as a list.
1028    /// If the key is a non-mergeable list and a value is found in the environment, existing values are cleared.
1029    fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1030        let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1031            self.check_environment_key_case_mismatch(key);
1032            return Ok(());
1033        };
1034
1035        let env_def = Definition::Environment(key.as_env_key().to_string());
1036
1037        if is_nonmergeable_list(&key) {
1038            assert!(
1039                output
1040                    .windows(2)
1041                    .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1042                "non-mergeable list must have only one definition: {output:?}",
1043            );
1044
1045            // Keep existing config if higher priority than env (e.g., --config CLI),
1046            // otherwise clear for env
1047            if output
1048                .first()
1049                .map(|o| o.definition() > &env_def)
1050                .unwrap_or_default()
1051            {
1052                return Ok(());
1053            } else {
1054                output.clear();
1055            }
1056        }
1057
1058        if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1059            // Parse an environment string as a TOML array.
1060            let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1061                ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1062            })?;
1063            let values = toml_v.as_array().expect("env var was not array");
1064            for value in values {
1065                // Until we figure out how to deal with it through `-Zadvanced-env`,
1066                // complex array types are unsupported.
1067                let s = value.as_str().ok_or_else(|| {
1068                    ConfigError::new(
1069                        format!("expected string, found {}", value.type_str()),
1070                        env_def.clone(),
1071                    )
1072                })?;
1073                output.push(CV::String(s.to_string(), env_def.clone()))
1074            }
1075        } else {
1076            output.extend(
1077                env_val
1078                    .split_whitespace()
1079                    .map(|s| CV::String(s.to_string(), env_def.clone())),
1080            );
1081        }
1082        output.sort_by(|a, b| a.definition().cmp(b.definition()));
1083        Ok(())
1084    }
1085
1086    /// Low-level method for getting a config value as an `OptValue<HashMap<String, CV>>`.
1087    ///
1088    /// NOTE: This does not read from env. The caller is responsible for that.
1089    fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1090        match self.get_cv(key)? {
1091            Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1092            Some(val) => self.expected("table", key, &val),
1093            None => Ok(None),
1094        }
1095    }
1096
1097    get_value_typed! {get_integer, i64, Integer, "an integer"}
1098    get_value_typed! {get_bool, bool, Boolean, "true/false"}
1099    get_value_typed! {get_string_priv, String, String, "a string"}
1100
1101    /// Generate an error when the given value is the wrong type.
1102    fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1103        val.expected(ty, &key.to_string())
1104            .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1105    }
1106
1107    /// Update the instance based on settings typically passed in on
1108    /// the command-line.
1109    ///
1110    /// This may also load the config from disk if it hasn't already been
1111    /// loaded.
1112    pub fn configure(
1113        &mut self,
1114        verbose: u32,
1115        quiet: bool,
1116        color: Option<&str>,
1117        frozen: bool,
1118        locked: bool,
1119        offline: bool,
1120        target_dir: &Option<PathBuf>,
1121        unstable_flags: &[String],
1122        cli_config: &[String],
1123    ) -> CargoResult<()> {
1124        for warning in self
1125            .unstable_flags
1126            .parse(unstable_flags, self.nightly_features_allowed)?
1127        {
1128            self.shell().warn(warning)?;
1129        }
1130        if !unstable_flags.is_empty() {
1131            // store a copy of the cli flags separately for `load_unstable_flags_from_config`
1132            // (we might also need it again for `reload_rooted_at`)
1133            self.unstable_flags_cli = Some(unstable_flags.to_vec());
1134        }
1135        if !cli_config.is_empty() {
1136            self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1137            self.merge_cli_args()?;
1138        }
1139
1140        self.load_unstable_flags_from_config()?;
1141
1142        // Ignore errors in the configuration files. We don't want basic
1143        // commands like `cargo version` to error out due to config file
1144        // problems.
1145        let term = self.get::<TermConfig>("term").unwrap_or_default();
1146
1147        // The command line takes precedence over configuration.
1148        let extra_verbose = verbose >= 2;
1149        let verbose = verbose != 0;
1150        let verbosity = match (verbose, quiet) {
1151            (true, true) => bail!("cannot set both --verbose and --quiet"),
1152            (true, false) => Verbosity::Verbose,
1153            (false, true) => Verbosity::Quiet,
1154            (false, false) => match (term.verbose, term.quiet) {
1155                (Some(true), Some(true)) => {
1156                    bail!("cannot set both `term.verbose` and `term.quiet`")
1157                }
1158                (Some(true), _) => Verbosity::Verbose,
1159                (_, Some(true)) => Verbosity::Quiet,
1160                _ => Verbosity::Normal,
1161            },
1162        };
1163        self.shell().set_verbosity(verbosity);
1164        self.extra_verbose = extra_verbose;
1165
1166        let color = color.or_else(|| term.color.as_deref());
1167        self.shell().set_color_choice(color)?;
1168        if let Some(hyperlinks) = term.hyperlinks {
1169            self.shell().set_hyperlinks(hyperlinks)?;
1170        }
1171        if let Some(unicode) = term.unicode {
1172            self.shell().set_unicode(unicode)?;
1173        }
1174
1175        self.progress_config = term.progress.unwrap_or_default();
1176
1177        self.frozen = frozen;
1178        self.locked = locked;
1179        self.offline = offline
1180            || self
1181                .net_config()
1182                .ok()
1183                .and_then(|n| n.offline)
1184                .unwrap_or(false);
1185        let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1186        self.target_dir = cli_target_dir;
1187
1188        self.shell()
1189            .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1190
1191        Ok(())
1192    }
1193
1194    fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1195        // If nightly features are enabled, allow setting Z-flags from config
1196        // using the `unstable` table. Ignore that block otherwise.
1197        if self.nightly_features_allowed {
1198            self.unstable_flags = self
1199                .get::<Option<CliUnstable>>("unstable")?
1200                .unwrap_or_default();
1201            if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1202                // NB. It's not ideal to parse these twice, but doing it again here
1203                //     allows the CLI to override config files for both enabling
1204                //     and disabling, and doing it up top allows CLI Zflags to
1205                //     control config parsing behavior.
1206                self.unstable_flags.parse(unstable_flags_cli, true)?;
1207            }
1208        }
1209
1210        Ok(())
1211    }
1212
1213    pub fn cli_unstable(&self) -> &CliUnstable {
1214        &self.unstable_flags
1215    }
1216
1217    pub fn extra_verbose(&self) -> bool {
1218        self.extra_verbose
1219    }
1220
1221    pub fn network_allowed(&self) -> bool {
1222        !self.offline_flag().is_some()
1223    }
1224
1225    pub fn offline_flag(&self) -> Option<&'static str> {
1226        if self.frozen {
1227            Some("--frozen")
1228        } else if self.offline {
1229            Some("--offline")
1230        } else {
1231            None
1232        }
1233    }
1234
1235    pub fn set_locked(&mut self, locked: bool) {
1236        self.locked = locked;
1237    }
1238
1239    pub fn lock_update_allowed(&self) -> bool {
1240        !self.locked_flag().is_some()
1241    }
1242
1243    pub fn locked_flag(&self) -> Option<&'static str> {
1244        if self.frozen {
1245            Some("--frozen")
1246        } else if self.locked {
1247            Some("--locked")
1248        } else {
1249            None
1250        }
1251    }
1252
1253    /// Loads configuration from the filesystem.
1254    pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1255        self.load_values_from(&self.cwd)
1256    }
1257
1258    /// Like [`load_values`](GlobalContext::load_values) but without merging config values.
1259    ///
1260    /// This is primarily crafted for `cargo config` command.
1261    pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1262        let mut result = Vec::new();
1263        let mut seen = HashSet::new();
1264        let home = self.home_path.clone().into_path_unlocked();
1265        self.walk_tree(&self.cwd, &home, |path| {
1266            let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1267            self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1268            result.push(cv);
1269            Ok(())
1270        })
1271        .context("could not load Cargo configuration")?;
1272        Ok(result)
1273    }
1274
1275    /// Like [`load_includes`](GlobalContext::load_includes) but without merging config values.
1276    ///
1277    /// This is primarily crafted for `cargo config` command.
1278    fn load_unmerged_include(
1279        &self,
1280        cv: &mut CV,
1281        seen: &mut HashSet<PathBuf>,
1282        output: &mut Vec<CV>,
1283    ) -> CargoResult<()> {
1284        let includes = self.include_paths(cv, false)?;
1285        for include in includes {
1286            let Some(abs_path) = include.resolve_path(self) else {
1287                continue;
1288            };
1289
1290            let mut cv = self
1291                ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1292                .with_context(|| {
1293                    format!(
1294                        "failed to load config include `{}` from `{}`",
1295                        include.path.display(),
1296                        include.def
1297                    )
1298                })?;
1299            self.load_unmerged_include(&mut cv, seen, output)?;
1300            output.push(cv);
1301        }
1302        Ok(())
1303    }
1304
1305    /// Start a config file discovery from a path and merges all config values found.
1306    fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1307        // The root config value container isn't from any external source,
1308        // so its definition should be built-in.
1309        let mut cfg = CV::Table(HashMap::new(), Definition::BuiltIn);
1310        let home = self.home_path.clone().into_path_unlocked();
1311
1312        self.walk_tree(path, &home, |path| {
1313            let value = self.load_file(path)?;
1314            cfg.merge(value, false).with_context(|| {
1315                format!("failed to merge configuration at `{}`", path.display())
1316            })?;
1317            Ok(())
1318        })
1319        .context("could not load Cargo configuration")?;
1320
1321        match cfg {
1322            CV::Table(map, _) => Ok(map),
1323            _ => unreachable!(),
1324        }
1325    }
1326
1327    /// Loads a config value from a path.
1328    ///
1329    /// This is used during config file discovery.
1330    fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1331        self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1332    }
1333
1334    /// Loads a config value from a path with options.
1335    ///
1336    /// This is actual implementation of loading a config value from a path.
1337    ///
1338    /// * `includes` determines whether to load configs from [`ConfigInclude`].
1339    /// * `seen` is used to check for cyclic includes.
1340    /// * `why_load` tells why a config is being loaded.
1341    fn _load_file(
1342        &self,
1343        path: &Path,
1344        seen: &mut HashSet<PathBuf>,
1345        includes: bool,
1346        why_load: WhyLoad,
1347    ) -> CargoResult<ConfigValue> {
1348        if !seen.insert(path.to_path_buf()) {
1349            bail!(
1350                "config `include` cycle detected with path `{}`",
1351                path.display()
1352            );
1353        }
1354        tracing::debug!(?path, ?why_load, includes, "load config from file");
1355
1356        let contents = fs::read_to_string(path)
1357            .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1358        let toml = parse_document(&contents, path, self).with_context(|| {
1359            format!("could not parse TOML configuration in `{}`", path.display())
1360        })?;
1361        let def = match why_load {
1362            WhyLoad::Cli => Definition::Cli(Some(path.into())),
1363            WhyLoad::FileDiscovery => Definition::Path(path.into()),
1364        };
1365        let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1366            format!(
1367                "failed to load TOML configuration from `{}`",
1368                path.display()
1369            )
1370        })?;
1371        if includes {
1372            self.load_includes(value, seen, why_load)
1373        } else {
1374            Ok(value)
1375        }
1376    }
1377
1378    /// Load any `include` files listed in the given `value`.
1379    ///
1380    /// Returns `value` with the given include files merged into it.
1381    ///
1382    /// * `seen` is used to check for cyclic includes.
1383    /// * `why_load` tells why a config is being loaded.
1384    fn load_includes(
1385        &self,
1386        mut value: CV,
1387        seen: &mut HashSet<PathBuf>,
1388        why_load: WhyLoad,
1389    ) -> CargoResult<CV> {
1390        // Get the list of files to load.
1391        let includes = self.include_paths(&mut value, true)?;
1392
1393        // Accumulate all values here.
1394        let mut root = CV::Table(HashMap::new(), value.definition().clone());
1395        for include in includes {
1396            let Some(abs_path) = include.resolve_path(self) else {
1397                continue;
1398            };
1399
1400            self._load_file(&abs_path, seen, true, why_load)
1401                .and_then(|include| root.merge(include, true))
1402                .with_context(|| {
1403                    format!(
1404                        "failed to load config include `{}` from `{}`",
1405                        include.path.display(),
1406                        include.def
1407                    )
1408                })?;
1409        }
1410        root.merge(value, true)?;
1411        Ok(root)
1412    }
1413
1414    /// Converts the `include` config value to a list of absolute paths.
1415    fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1416        let CV::Table(table, _def) = cv else {
1417            unreachable!()
1418        };
1419        let include = if remove {
1420            table.remove("include").map(Cow::Owned)
1421        } else {
1422            table.get("include").map(Cow::Borrowed)
1423        };
1424        let includes = match include.map(|c| c.into_owned()) {
1425            Some(CV::List(list, _def)) => list
1426                .into_iter()
1427                .enumerate()
1428                .map(|(idx, cv)| match cv {
1429                    CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1430                    CV::Table(mut table, def) => {
1431                        // Extract `include.path`
1432                        let s = match table.remove("path") {
1433                            Some(CV::String(s, _)) => s,
1434                            Some(other) => bail!(
1435                                "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1436                                other.desc()
1437                            ),
1438                            None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1439                        };
1440
1441                        // Extract optional `include.optional` field
1442                        let optional = match table.remove("optional") {
1443                            Some(CV::Boolean(b, _)) => b,
1444                            Some(other) => bail!(
1445                                "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1446                                other.desc()
1447                            ),
1448                            None => false,
1449                        };
1450
1451                        let mut include = ConfigInclude::new(s, def);
1452                        include.optional = optional;
1453                        Ok(include)
1454                    }
1455                    other => bail!(
1456                        "expected a string or table, but found {} at `include[{idx}]` in {}",
1457                        other.desc(),
1458                        other.definition(),
1459                    ),
1460                })
1461                .collect::<CargoResult<Vec<_>>>()?,
1462            Some(other) => bail!(
1463                "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1464                other.desc(),
1465                other.definition()
1466            ),
1467            None => {
1468                return Ok(Vec::new());
1469            }
1470        };
1471
1472        for include in &includes {
1473            if include.path.extension() != Some(OsStr::new("toml")) {
1474                bail!(
1475                    "expected a config include path ending with `.toml`, \
1476                     but found `{}` from `{}`",
1477                    include.path.display(),
1478                    include.def,
1479                )
1480            }
1481
1482            if let Some(path) = include.path.to_str() {
1483                // Ignore non UTF-8 bytes as glob and template syntax are for textual config.
1484                if is_glob_pattern(path) {
1485                    bail!(
1486                        "expected a config include path without glob patterns, \
1487                         but found `{}` from `{}`",
1488                        include.path.display(),
1489                        include.def,
1490                    )
1491                }
1492                if path.contains(&['{', '}']) {
1493                    bail!(
1494                        "expected a config include path without template braces, \
1495                         but found `{}` from `{}`",
1496                        include.path.display(),
1497                        include.def,
1498                    )
1499                }
1500            }
1501        }
1502
1503        Ok(includes)
1504    }
1505
1506    /// Parses the CLI config args and returns them as a table.
1507    pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1508        let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1509        let Some(cli_args) = &self.cli_config else {
1510            return Ok(loaded_args);
1511        };
1512        let mut seen = HashSet::new();
1513        for arg in cli_args {
1514            let arg_as_path = self.cwd.join(arg);
1515            let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1516                // --config path_to_file
1517                self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1518                    .with_context(|| {
1519                        format!("failed to load config from `{}`", arg_as_path.display())
1520                    })?
1521            } else {
1522                let doc = toml_dotted_keys(arg)?;
1523                let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1524                    .with_context(|| {
1525                        format!("failed to parse value from --config argument `{arg}`")
1526                    })?;
1527
1528                if doc
1529                    .get("registry")
1530                    .and_then(|v| v.as_table())
1531                    .and_then(|t| t.get("token"))
1532                    .is_some()
1533                {
1534                    bail!("registry.token cannot be set through --config for security reasons");
1535                } else if let Some((k, _)) = doc
1536                    .get("registries")
1537                    .and_then(|v| v.as_table())
1538                    .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1539                {
1540                    bail!(
1541                        "registries.{}.token cannot be set through --config for security reasons",
1542                        k
1543                    );
1544                }
1545
1546                if doc
1547                    .get("registry")
1548                    .and_then(|v| v.as_table())
1549                    .and_then(|t| t.get("secret-key"))
1550                    .is_some()
1551                {
1552                    bail!(
1553                        "registry.secret-key cannot be set through --config for security reasons"
1554                    );
1555                } else if let Some((k, _)) = doc
1556                    .get("registries")
1557                    .and_then(|v| v.as_table())
1558                    .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1559                {
1560                    bail!(
1561                        "registries.{}.secret-key cannot be set through --config for security reasons",
1562                        k
1563                    );
1564                }
1565
1566                CV::from_toml(Definition::Cli(None), doc)
1567                    .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1568            };
1569            let tmp_table = self
1570                .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1571                .context("failed to load --config include".to_string())?;
1572            loaded_args
1573                .merge(tmp_table, true)
1574                .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1575        }
1576        Ok(loaded_args)
1577    }
1578
1579    /// Add config arguments passed on the command line.
1580    fn merge_cli_args(&mut self) -> CargoResult<()> {
1581        let cv_from_cli = self.cli_args_as_table()?;
1582        assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1583
1584        let root_cv = mem::take(self.values_mut()?);
1585        // The root config value container isn't from any external source,
1586        // so its definition should be built-in.
1587        let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1588        root_cv.merge(cv_from_cli, true)?;
1589
1590        // Put it back to gctx
1591        mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1592
1593        Ok(())
1594    }
1595
1596    /// The purpose of this function is to aid in the transition to using
1597    /// .toml extensions on Cargo's config files, which were historically not used.
1598    /// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.
1599    /// When both exist, we want to prefer the one without an extension for
1600    /// backwards compatibility, but warn the user appropriately.
1601    fn get_file_path(
1602        &self,
1603        dir: &Path,
1604        filename_without_extension: &str,
1605        warn: bool,
1606    ) -> CargoResult<Option<PathBuf>> {
1607        let possible = dir.join(filename_without_extension);
1608        let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1609
1610        if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1611            if warn {
1612                if let Ok(possible_with_extension_handle) =
1613                    same_file::Handle::from_path(&possible_with_extension)
1614                {
1615                    // We don't want to print a warning if the version
1616                    // without the extension is just a symlink to the version
1617                    // WITH an extension, which people may want to do to
1618                    // support multiple Cargo versions at once and not
1619                    // get a warning.
1620                    if possible_handle != possible_with_extension_handle {
1621                        self.shell().warn(format!(
1622                            "both `{}` and `{}` exist. Using `{}`",
1623                            possible.display(),
1624                            possible_with_extension.display(),
1625                            possible.display()
1626                        ))?;
1627                    }
1628                } else {
1629                    self.shell().print_report(&[
1630                        Level::WARNING.secondary_title(
1631                        format!(
1632                        "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1633                        possible.display(),
1634                    )).element(Level::HELP.message(
1635                        format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1636
1637                    ], false)?;
1638                }
1639            }
1640
1641            Ok(Some(possible))
1642        } else if possible_with_extension.exists() {
1643            Ok(Some(possible_with_extension))
1644        } else {
1645            Ok(None)
1646        }
1647    }
1648
1649    fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1650    where
1651        F: FnMut(&Path) -> CargoResult<()>,
1652    {
1653        let mut seen_dir = HashSet::new();
1654
1655        for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1656            let config_root = current.join(".cargo");
1657            if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1658                walk(&path)?;
1659            }
1660
1661            let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1662            seen_dir.insert(canonical_root);
1663        }
1664
1665        let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1666
1667        // Once we're done, also be sure to walk the home directory even if it's not
1668        // in our history to be sure we pick up that standard location for
1669        // information.
1670        if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1671            if let Some(path) = self.get_file_path(home, "config", true)? {
1672                walk(&path)?;
1673            }
1674        }
1675
1676        Ok(())
1677    }
1678
1679    /// Gets the index for a registry.
1680    pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1681        RegistryName::new(registry)?;
1682        if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1683            self.resolve_registry_index(&index).with_context(|| {
1684                format!(
1685                    "invalid index URL for registry `{}` defined in {}",
1686                    registry, index.definition
1687                )
1688            })
1689        } else {
1690            bail!(
1691                "registry index was not found in any configuration: `{}`",
1692                registry
1693            );
1694        }
1695    }
1696
1697    /// Returns an error if `registry.index` is set.
1698    pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1699        if self.get_string("registry.index")?.is_some() {
1700            bail!(
1701                "the `registry.index` config value is no longer supported\n\
1702                Use `[source]` replacement to alter the default index for crates.io."
1703            );
1704        }
1705        Ok(())
1706    }
1707
1708    fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1709        // This handles relative file: URLs, relative to the config definition.
1710        let base = index
1711            .definition
1712            .root(self.cwd())
1713            .join("truncated-by-url_with_base");
1714        // Parse val to check it is a URL, not a relative path without a protocol.
1715        let _parsed = index.val.into_url()?;
1716        let url = index.val.into_url_with_base(Some(&*base))?;
1717        if url.password().is_some() {
1718            bail!("registry URLs may not contain passwords");
1719        }
1720        Ok(url)
1721    }
1722
1723    /// Loads credentials config from the credentials file, if present.
1724    ///
1725    /// The credentials are loaded into a separate field to enable them
1726    /// to be lazy-loaded after the main configuration has been loaded,
1727    /// without requiring `mut` access to the [`GlobalContext`].
1728    ///
1729    /// If the credentials are already loaded, this function does nothing.
1730    pub fn load_credentials(&self) -> CargoResult<()> {
1731        if self.credential_values.filled() {
1732            return Ok(());
1733        }
1734
1735        let home_path = self.home_path.clone().into_path_unlocked();
1736        let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1737            return Ok(());
1738        };
1739
1740        let mut value = self.load_file(&credentials)?;
1741        // Backwards compatibility for old `.cargo/credentials` layout.
1742        {
1743            let (value_map, def) = value.table_mut("<root>")?;
1744
1745            if let Some(token) = value_map.remove("token") {
1746                value_map.entry("registry".into()).or_insert_with(|| {
1747                    let map = HashMap::from([("token".into(), token)]);
1748                    CV::Table(map, def.clone())
1749                });
1750            }
1751        }
1752
1753        let mut credential_values = HashMap::new();
1754        if let CV::Table(map, _) = value {
1755            let base_map = self.values()?;
1756            for (k, v) in map {
1757                let entry = match base_map.get(&k) {
1758                    Some(base_entry) => {
1759                        let mut entry = base_entry.clone();
1760                        entry.merge(v, true)?;
1761                        entry
1762                    }
1763                    None => v,
1764                };
1765                credential_values.insert(k, entry);
1766            }
1767        }
1768        self.credential_values
1769            .set(credential_values)
1770            .expect("was not filled at beginning of the function");
1771        Ok(())
1772    }
1773
1774    /// Looks for a path for `tool` in an environment variable or the given config, and returns
1775    /// `None` if it's not present.
1776    fn maybe_get_tool(
1777        &self,
1778        tool: &str,
1779        from_config: &Option<ConfigRelativePath>,
1780    ) -> Option<PathBuf> {
1781        let var = tool.to_uppercase();
1782
1783        match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1784            Some(tool_path) => {
1785                let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1786                let path = if maybe_relative {
1787                    self.cwd.join(tool_path)
1788                } else {
1789                    PathBuf::from(tool_path)
1790                };
1791                Some(path)
1792            }
1793
1794            None => from_config.as_ref().map(|p| p.resolve_program(self)),
1795        }
1796    }
1797
1798    /// Returns the path for the given tool.
1799    ///
1800    /// This will look for the tool in the following order:
1801    ///
1802    /// 1. From an environment variable matching the tool name (such as `RUSTC`).
1803    /// 2. From the given config value (which is usually something like `build.rustc`).
1804    /// 3. Finds the tool in the PATH environment variable.
1805    ///
1806    /// This is intended for tools that are rustup proxies. If you need to get
1807    /// a tool that is not a rustup proxy, use `maybe_get_tool` instead.
1808    fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1809        let tool_str = tool.as_str();
1810        self.maybe_get_tool(tool_str, from_config)
1811            .or_else(|| {
1812                // This is an optimization to circumvent the rustup proxies
1813                // which can have a significant performance hit. The goal here
1814                // is to determine if calling `rustc` from PATH would end up
1815                // calling the proxies.
1816                //
1817                // This is somewhat cautious trying to determine if it is safe
1818                // to circumvent rustup, because there are some situations
1819                // where users may do things like modify PATH, call cargo
1820                // directly, use a custom rustup toolchain link without a
1821                // cargo executable, etc. However, there is still some risk
1822                // this may make the wrong decision in unusual circumstances.
1823                //
1824                // First, we must be running under rustup in the first place.
1825                let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1826                // This currently does not support toolchain paths.
1827                // This also enforces UTF-8.
1828                if toolchain.to_str()?.contains(&['/', '\\']) {
1829                    return None;
1830                }
1831                // If the tool on PATH is the same as `rustup` on path, then
1832                // there is pretty good evidence that it will be a proxy.
1833                let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1834                let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1835                let tool_meta = tool_resolved.metadata().ok()?;
1836                let rustup_meta = rustup_resolved.metadata().ok()?;
1837                // This works on the assumption that rustup and its proxies
1838                // use hard links to a single binary. If rustup ever changes
1839                // that setup, then I think the worst consequence is that this
1840                // optimization will not work, and it will take the slow path.
1841                if tool_meta.len() != rustup_meta.len() {
1842                    return None;
1843                }
1844                // Try to find the tool in rustup's toolchain directory.
1845                let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1846                let toolchain_exe = home::rustup_home()
1847                    .ok()?
1848                    .join("toolchains")
1849                    .join(&toolchain)
1850                    .join("bin")
1851                    .join(&tool_exe);
1852                toolchain_exe.exists().then_some(toolchain_exe)
1853            })
1854            .unwrap_or_else(|| PathBuf::from(tool_str))
1855    }
1856
1857    /// Get the `paths` overrides config value.
1858    pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1859        let key = ConfigKey::from_str("paths");
1860        // paths overrides cannot be set via env config, so use get_cv here.
1861        match self.get_cv(&key)? {
1862            Some(CV::List(val, definition)) => {
1863                let val = val
1864                    .into_iter()
1865                    .map(|cv| match cv {
1866                        CV::String(s, def) => Ok((s, def)),
1867                        other => self.expected("string", &key, &other),
1868                    })
1869                    .collect::<CargoResult<Vec<_>>>()?;
1870                Ok(Some(Value { val, definition }))
1871            }
1872            Some(val) => self.expected("list", &key, &val),
1873            None => Ok(None),
1874        }
1875    }
1876
1877    pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1878        self.jobserver.as_ref()
1879    }
1880
1881    pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1882        let http = self
1883            .easy
1884            .try_borrow_with(|| http_handle(self).map(Into::into))?;
1885        {
1886            let mut http = http.lock().unwrap();
1887            http.reset();
1888            let timeout = configure_http_handle(self, &mut http)?;
1889            timeout.configure(&mut http)?;
1890        }
1891        Ok(http)
1892    }
1893
1894    pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1895        self.http_config.try_borrow_with(|| {
1896            let mut http = self.get::<CargoHttpConfig>("http")?;
1897            let curl_v = curl::Version::get();
1898            disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1899            Ok(http)
1900        })
1901    }
1902
1903    pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1904        self.future_incompat_config
1905            .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1906    }
1907
1908    pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1909        self.net_config
1910            .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1911    }
1912
1913    pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1914        self.build_config
1915            .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1916    }
1917
1918    pub fn progress_config(&self) -> &ProgressConfig {
1919        &self.progress_config
1920    }
1921
1922    /// Get the env vars from the config `[env]` table which
1923    /// are `force = true` or don't exist in the env snapshot [`GlobalContext::get_env`].
1924    pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1925        let env_config = self.env_config.try_borrow_with(|| {
1926            CargoResult::Ok(Arc::new({
1927                let env_config = self.get::<EnvConfig>("env")?;
1928                // Reasons for disallowing these values:
1929                //
1930                // - CARGO_HOME: The initial call to cargo does not honor this value
1931                //   from the [env] table. Recursive calls to cargo would use the new
1932                //   value, possibly behaving differently from the outer cargo.
1933                //
1934                // - RUSTUP_HOME and RUSTUP_TOOLCHAIN: Under normal usage with rustup,
1935                //   this will have no effect because the rustup proxy sets
1936                //   RUSTUP_HOME and RUSTUP_TOOLCHAIN, and that would override the
1937                //   [env] table. If the outer cargo is executed directly
1938                //   circumventing the rustup proxy, then this would affect calls to
1939                //   rustc (assuming that is a proxy), which could potentially cause
1940                //   problems with cargo and rustc being from different toolchains. We
1941                //   consider this to be not a use case we would like to support,
1942                //   since it will likely cause problems or lead to confusion.
1943                for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1944                    if env_config.contains_key(*disallowed) {
1945                        bail!(
1946                            "setting the `{disallowed}` environment variable is not supported \
1947                            in the `[env]` configuration table"
1948                        );
1949                    }
1950                }
1951                env_config
1952                    .into_iter()
1953                    .filter_map(|(k, v)| {
1954                        if v.is_force() || self.get_env_os(&k).is_none() {
1955                            Some((k, v.resolve(self.cwd()).to_os_string()))
1956                        } else {
1957                            None
1958                        }
1959                    })
1960                    .collect()
1961            }))
1962        })?;
1963
1964        Ok(env_config)
1965    }
1966
1967    /// This is used to validate the `term` table has valid syntax.
1968    ///
1969    /// This is necessary because loading the term settings happens very
1970    /// early, and in some situations (like `cargo version`) we don't want to
1971    /// fail if there are problems with the config file.
1972    pub fn validate_term_config(&self) -> CargoResult<()> {
1973        drop(self.get::<TermConfig>("term")?);
1974        Ok(())
1975    }
1976
1977    /// Returns a list of `target.'cfg()'` tables.
1978    ///
1979    /// The list is sorted by the table name.
1980    pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1981        self.target_cfgs
1982            .try_borrow_with(|| target::load_target_cfgs(self))
1983    }
1984
1985    pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1986        // Note: This does not support environment variables. The `Unit`
1987        // fundamentally does not have access to the registry name, so there is
1988        // nothing to query. Plumbing the name into SourceId is quite challenging.
1989        self.doc_extern_map
1990            .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1991    }
1992
1993    /// Returns true if the `[target]` table should be applied to host targets.
1994    pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1995        target::get_target_applies_to_host(self)
1996    }
1997
1998    /// Returns the `[host]` table definition for the given target triple.
1999    pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2000        target::load_host_triple(self, target)
2001    }
2002
2003    /// Returns the `[target]` table definition for the given target triple.
2004    pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2005        target::load_target_triple(self, target)
2006    }
2007
2008    /// Returns the cached [`SourceId`] corresponding to the main repository.
2009    ///
2010    /// This is the main cargo registry by default, but it can be overridden in
2011    /// a `.cargo/config.toml`.
2012    pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2013        let source_id = self.crates_io_source_id.try_borrow_with(|| {
2014            self.check_registry_index_not_set()?;
2015            let url = CRATES_IO_INDEX.into_url().unwrap();
2016            SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2017        })?;
2018        Ok(*source_id)
2019    }
2020
2021    pub fn creation_time(&self) -> Instant {
2022        self.creation_time
2023    }
2024
2025    /// Retrieves a config variable.
2026    ///
2027    /// This supports most serde `Deserialize` types. Examples:
2028    ///
2029    /// ```rust,ignore
2030    /// let v: Option<u32> = config.get("some.nested.key")?;
2031    /// let v: Option<MyStruct> = config.get("some.key")?;
2032    /// let v: Option<HashMap<String, MyStruct>> = config.get("foo")?;
2033    /// ```
2034    ///
2035    /// The key may be a dotted key, but this does NOT support TOML key
2036    /// quoting. Avoid key components that may have dots. For example,
2037    /// `foo.'a.b'.bar" does not work if you try to fetch `foo.'a.b'". You can
2038    /// fetch `foo` if it is a map, though.
2039    pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2040        let d = Deserializer {
2041            gctx: self,
2042            key: ConfigKey::from_str(key),
2043            env_prefix_ok: true,
2044        };
2045        T::deserialize(d).map_err(|e| e.into())
2046    }
2047
2048    /// Obtain a [`Path`] from a [`Filesystem`], verifying that the
2049    /// appropriate lock is already currently held.
2050    ///
2051    /// Locks are usually acquired via [`GlobalContext::acquire_package_cache_lock`]
2052    /// or [`GlobalContext::try_acquire_package_cache_lock`].
2053    #[track_caller]
2054    #[tracing::instrument(skip_all)]
2055    pub fn assert_package_cache_locked<'a>(
2056        &self,
2057        mode: CacheLockMode,
2058        f: &'a Filesystem,
2059    ) -> &'a Path {
2060        let ret = f.as_path_unlocked();
2061        assert!(
2062            self.package_cache_lock.is_locked(mode),
2063            "package cache lock is not currently held, Cargo forgot to call \
2064             `acquire_package_cache_lock` before we got to this stack frame",
2065        );
2066        assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2067        ret
2068    }
2069
2070    /// Acquires a lock on the global "package cache", blocking if another
2071    /// cargo holds the lock.
2072    ///
2073    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
2074    /// and lock modes.
2075    #[tracing::instrument(skip_all)]
2076    pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2077        self.package_cache_lock.lock(self, mode)
2078    }
2079
2080    /// Acquires a lock on the global "package cache", returning `None` if
2081    /// another cargo holds the lock.
2082    ///
2083    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
2084    /// and lock modes.
2085    #[tracing::instrument(skip_all)]
2086    pub fn try_acquire_package_cache_lock(
2087        &self,
2088        mode: CacheLockMode,
2089    ) -> CargoResult<Option<CacheLock<'_>>> {
2090        self.package_cache_lock.try_lock(self, mode)
2091    }
2092
2093    /// Returns a reference to the shared [`GlobalCacheTracker`].
2094    ///
2095    /// The package cache lock must be held to call this function (and to use
2096    /// it in general).
2097    pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2098        let tracker = self.global_cache_tracker.try_borrow_with(|| {
2099            Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2100        })?;
2101        Ok(tracker.lock().unwrap())
2102    }
2103
2104    /// Returns a reference to the shared [`DeferredGlobalLastUse`].
2105    pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2106        let deferred = self
2107            .deferred_global_last_use
2108            .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2109        Ok(deferred.lock().unwrap())
2110    }
2111
2112    /// Get the global [`WarningHandling`] configuration.
2113    pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2114        if self.unstable_flags.warnings {
2115            Ok(self.build_config()?.warnings.unwrap_or_default())
2116        } else {
2117            Ok(WarningHandling::default())
2118        }
2119    }
2120
2121    pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2122        self.ws_roots.lock().unwrap()
2123    }
2124}
2125
2126pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2127    ::home::cargo_home_with_cwd(cwd).ok()
2128}
2129
2130pub fn save_credentials(
2131    gctx: &GlobalContext,
2132    token: Option<RegistryCredentialConfig>,
2133    registry: &SourceId,
2134) -> CargoResult<()> {
2135    let registry = if registry.is_crates_io() {
2136        None
2137    } else {
2138        let name = registry
2139            .alt_registry_key()
2140            .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2141        Some(name)
2142    };
2143
2144    // If 'credentials' exists, write to that for backward compatibility reasons.
2145    // Otherwise write to 'credentials.toml'. There's no need to print the
2146    // warning here, because it would already be printed at load time.
2147    let home_path = gctx.home_path.clone().into_path_unlocked();
2148    let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2149        Some(path) => match path.file_name() {
2150            Some(filename) => Path::new(filename).to_owned(),
2151            None => Path::new("credentials.toml").to_owned(),
2152        },
2153        None => Path::new("credentials.toml").to_owned(),
2154    };
2155
2156    let mut file = {
2157        gctx.home_path.create_dir()?;
2158        gctx.home_path
2159            .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2160    };
2161
2162    let mut contents = String::new();
2163    file.read_to_string(&mut contents).with_context(|| {
2164        format!(
2165            "failed to read configuration file `{}`",
2166            file.path().display()
2167        )
2168    })?;
2169
2170    let mut toml = parse_document(&contents, file.path(), gctx)?;
2171
2172    // Move the old token location to the new one.
2173    if let Some(token) = toml.remove("token") {
2174        let map = HashMap::from([("token".to_string(), token)]);
2175        toml.insert("registry".into(), map.into());
2176    }
2177
2178    if let Some(token) = token {
2179        // login
2180
2181        let path_def = Definition::Path(file.path().to_path_buf());
2182        let (key, mut value) = match token {
2183            RegistryCredentialConfig::Token(token) => {
2184                // login with token
2185
2186                let key = "token".to_string();
2187                let value = ConfigValue::String(token.expose(), path_def.clone());
2188                let map = HashMap::from([(key, value)]);
2189                let table = CV::Table(map, path_def.clone());
2190
2191                if let Some(registry) = registry {
2192                    let map = HashMap::from([(registry.to_string(), table)]);
2193                    ("registries".into(), CV::Table(map, path_def.clone()))
2194                } else {
2195                    ("registry".into(), table)
2196                }
2197            }
2198            RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2199                // login with key
2200
2201                let key = "secret-key".to_string();
2202                let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2203                let mut map = HashMap::from([(key, value)]);
2204                if let Some(key_subject) = key_subject {
2205                    let key = "secret-key-subject".to_string();
2206                    let value = ConfigValue::String(key_subject, path_def.clone());
2207                    map.insert(key, value);
2208                }
2209                let table = CV::Table(map, path_def.clone());
2210
2211                if let Some(registry) = registry {
2212                    let map = HashMap::from([(registry.to_string(), table)]);
2213                    ("registries".into(), CV::Table(map, path_def.clone()))
2214                } else {
2215                    ("registry".into(), table)
2216                }
2217            }
2218            _ => unreachable!(),
2219        };
2220
2221        if registry.is_some() {
2222            if let Some(table) = toml.remove("registries") {
2223                let v = CV::from_toml(path_def, table)?;
2224                value.merge(v, false)?;
2225            }
2226        }
2227        toml.insert(key, value.into_toml());
2228    } else {
2229        // logout
2230        if let Some(registry) = registry {
2231            if let Some(registries) = toml.get_mut("registries") {
2232                if let Some(reg) = registries.get_mut(registry) {
2233                    let rtable = reg.as_table_mut().ok_or_else(|| {
2234                        format_err!("expected `[registries.{}]` to be a table", registry)
2235                    })?;
2236                    rtable.remove("token");
2237                    rtable.remove("secret-key");
2238                    rtable.remove("secret-key-subject");
2239                }
2240            }
2241        } else if let Some(registry) = toml.get_mut("registry") {
2242            let reg_table = registry
2243                .as_table_mut()
2244                .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2245            reg_table.remove("token");
2246            reg_table.remove("secret-key");
2247            reg_table.remove("secret-key-subject");
2248        }
2249    }
2250
2251    let contents = toml.to_string();
2252    file.seek(SeekFrom::Start(0))?;
2253    file.write_all(contents.as_bytes())
2254        .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2255    file.file().set_len(contents.len() as u64)?;
2256    set_permissions(file.file(), 0o600)
2257        .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2258
2259    return Ok(());
2260
2261    #[cfg(unix)]
2262    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2263        use std::os::unix::fs::PermissionsExt;
2264
2265        let mut perms = file.metadata()?.permissions();
2266        perms.set_mode(mode);
2267        file.set_permissions(perms)?;
2268        Ok(())
2269    }
2270
2271    #[cfg(not(unix))]
2272    #[allow(unused)]
2273    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2274        Ok(())
2275    }
2276}
2277
2278/// Represents a config-include value in the configuration.
2279///
2280/// This intentionally doesn't derive serde deserialization
2281/// to avoid any misuse of `GlobalContext::get::<ConfigInclude>()`,
2282/// which might lead to wrong config loading order.
2283struct ConfigInclude {
2284    /// Path to a config-include configuration file.
2285    /// Could be either relative or absolute.
2286    path: PathBuf,
2287    def: Definition,
2288    /// Whether this include is optional (missing files are silently ignored)
2289    optional: bool,
2290}
2291
2292impl ConfigInclude {
2293    fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2294        Self {
2295            path: p.into(),
2296            def,
2297            optional: false,
2298        }
2299    }
2300
2301    /// Resolves the absolute path for this include.
2302    ///
2303    /// For file based include,
2304    /// it is relative to parent directory of the config file includes it.
2305    /// For example, if `.cargo/config.toml has a `include = "foo.toml"`,
2306    /// Cargo will load `.cargo/foo.toml`.
2307    ///
2308    /// For CLI based include (e.g., `--config 'include = "foo.toml"'`),
2309    /// it is relative to the current working directory.
2310    ///
2311    /// Returns `None` if this is an optional include and the file doesn't exist.
2312    /// Otherwise returns `Some(PathBuf)` with the absolute path.
2313    fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2314        let abs_path = match &self.def {
2315            Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2316            Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2317        }
2318        .join(&self.path);
2319
2320        if self.optional && !abs_path.exists() {
2321            tracing::info!(
2322                "skipping optional include `{}` in `{}`:  file not found at `{}`",
2323                self.path.display(),
2324                self.def,
2325                abs_path.display(),
2326            );
2327            None
2328        } else {
2329            Some(abs_path)
2330        }
2331    }
2332}
2333
2334fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2335    // At the moment, no compatibility checks are needed.
2336    toml.parse().map_err(Into::into)
2337}
2338
2339fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2340    // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
2341    // expressions followed by a value that's not an "inline table"
2342    // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
2343    // parse the value as a toml_edit::DocumentMut, and check that the (single)
2344    // inner-most table is set via dotted keys.
2345    let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2346        format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2347    })?;
2348    fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2349        d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2350    }
2351    fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2352        non_empty(d.prefix()) || non_empty(d.suffix())
2353    }
2354    fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2355        non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2356    }
2357    let ok = {
2358        let mut got_to_value = false;
2359        let mut table = doc.as_table();
2360        let mut is_root = true;
2361        while table.is_dotted() || is_root {
2362            is_root = false;
2363            if table.len() != 1 {
2364                break;
2365            }
2366            let (k, n) = table.iter().next().expect("len() == 1 above");
2367            match n {
2368                Item::Table(nt) => {
2369                    if table.key(k).map_or(false, non_empty_key_decor)
2370                        || non_empty_decor(nt.decor())
2371                    {
2372                        bail!(
2373                            "--config argument `{arg}` \
2374                                includes non-whitespace decoration"
2375                        )
2376                    }
2377                    table = nt;
2378                }
2379                Item::Value(v) if v.is_inline_table() => {
2380                    bail!(
2381                        "--config argument `{arg}` \
2382                        sets a value to an inline table, which is not accepted"
2383                    );
2384                }
2385                Item::Value(v) => {
2386                    if table
2387                        .key(k)
2388                        .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2389                        || non_empty_decor(v.decor())
2390                    {
2391                        bail!(
2392                            "--config argument `{arg}` \
2393                                includes non-whitespace decoration"
2394                        )
2395                    }
2396                    got_to_value = true;
2397                    break;
2398                }
2399                Item::ArrayOfTables(_) => {
2400                    bail!(
2401                        "--config argument `{arg}` \
2402                        sets a value to an array of tables, which is not accepted"
2403                    );
2404                }
2405
2406                Item::None => {
2407                    bail!("--config argument `{arg}` doesn't provide a value")
2408                }
2409            }
2410        }
2411        got_to_value
2412    };
2413    if !ok {
2414        bail!(
2415            "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2416        );
2417    }
2418    Ok(doc)
2419}
2420
2421/// A type to deserialize a list of strings from a toml file.
2422///
2423/// Supports deserializing either a whitespace-separated list of arguments in a
2424/// single string or a string list itself. For example these deserialize to
2425/// equivalent values:
2426///
2427/// ```toml
2428/// a = 'a b c'
2429/// b = ['a', 'b', 'c']
2430/// ```
2431#[derive(Debug, Deserialize, Clone)]
2432pub struct StringList(Vec<String>);
2433
2434impl StringList {
2435    pub fn as_slice(&self) -> &[String] {
2436        &self.0
2437    }
2438}
2439
2440#[macro_export]
2441macro_rules! __shell_print {
2442    ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2443        let mut shell = $config.shell();
2444        let out = shell.$which();
2445        drop(out.write_fmt(format_args!($($arg)*)));
2446        if $newline {
2447            drop(out.write_all(b"\n"));
2448        }
2449    });
2450}
2451
2452#[macro_export]
2453macro_rules! drop_println {
2454    ($config:expr) => ( $crate::drop_print!($config, "\n") );
2455    ($config:expr, $($arg:tt)*) => (
2456        $crate::__shell_print!($config, out, true, $($arg)*)
2457    );
2458}
2459
2460#[macro_export]
2461macro_rules! drop_eprintln {
2462    ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2463    ($config:expr, $($arg:tt)*) => (
2464        $crate::__shell_print!($config, err, true, $($arg)*)
2465    );
2466}
2467
2468#[macro_export]
2469macro_rules! drop_print {
2470    ($config:expr, $($arg:tt)*) => (
2471        $crate::__shell_print!($config, out, false, $($arg)*)
2472    );
2473}
2474
2475#[macro_export]
2476macro_rules! drop_eprint {
2477    ($config:expr, $($arg:tt)*) => (
2478        $crate::__shell_print!($config, err, false, $($arg)*)
2479    );
2480}
2481
2482enum Tool {
2483    Rustc,
2484    Rustdoc,
2485}
2486
2487impl Tool {
2488    fn as_str(&self) -> &str {
2489        match self {
2490            Tool::Rustc => "rustc",
2491            Tool::Rustdoc => "rustdoc",
2492        }
2493    }
2494}
2495
2496/// Disable HTTP/2 multiplexing for some broken versions of libcurl.
2497///
2498/// In certain versions of libcurl when proxy is in use with HTTP/2
2499/// multiplexing, connections will continue stacking up. This was
2500/// fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc
2501///
2502/// However, Cargo can still link against old system libcurl if it is from a
2503/// custom built one or on macOS. For those cases, multiplexing needs to be
2504/// disabled when those versions are detected.
2505fn disables_multiplexing_for_bad_curl(
2506    curl_version: &str,
2507    http: &mut CargoHttpConfig,
2508    gctx: &GlobalContext,
2509) {
2510    use crate::util::network;
2511
2512    if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2513        let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2514        if bad_curl_versions
2515            .iter()
2516            .any(|v| curl_version.starts_with(v))
2517        {
2518            tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2519            http.multiplexing = Some(false);
2520        }
2521    }
2522}
2523
2524#[cfg(test)]
2525mod tests {
2526    use super::CargoHttpConfig;
2527    use super::GlobalContext;
2528    use super::Shell;
2529    use super::disables_multiplexing_for_bad_curl;
2530
2531    #[test]
2532    fn disables_multiplexing() {
2533        let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2534        gctx.set_search_stop_path(std::path::PathBuf::new());
2535        gctx.set_env(Default::default());
2536
2537        let mut http = CargoHttpConfig::default();
2538        http.proxy = Some("127.0.0.1:3128".into());
2539        disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2540        assert_eq!(http.multiplexing, Some(false));
2541
2542        let cases = [
2543            (None, None, "7.87.0", None),
2544            (None, None, "7.88.0", None),
2545            (None, None, "7.88.1", None),
2546            (None, None, "8.0.0", None),
2547            (Some("".into()), None, "7.87.0", Some(false)),
2548            (Some("".into()), None, "7.88.0", Some(false)),
2549            (Some("".into()), None, "7.88.1", Some(false)),
2550            (Some("".into()), None, "8.0.0", None),
2551            (Some("".into()), Some(false), "7.87.0", Some(false)),
2552            (Some("".into()), Some(false), "7.88.0", Some(false)),
2553            (Some("".into()), Some(false), "7.88.1", Some(false)),
2554            (Some("".into()), Some(false), "8.0.0", Some(false)),
2555        ];
2556
2557        for (proxy, multiplexing, curl_v, result) in cases {
2558            let mut http = CargoHttpConfig {
2559                multiplexing,
2560                proxy,
2561                ..Default::default()
2562            };
2563            disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2564            assert_eq!(http.multiplexing, result);
2565        }
2566    }
2567
2568    #[test]
2569    fn sync_context() {
2570        fn assert_sync<S: Sync>() {}
2571        assert_sync::<GlobalContext>();
2572    }
2573}