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