Skip to main content

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