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