Skip to main content

cargo/context/
mod.rs

1//! Cargo's config system.
2//!
3//! The [`GlobalContext`] object contains general information about the environment,
4//! and provides access to Cargo's configuration files.
5//!
6//! ## Config value API
7//!
8//! The primary API for fetching user-defined config values is the
9//! [`GlobalContext::get`] method. It uses `serde` to translate config values to a
10//! target type.
11//!
12//! There are a variety of helper types for deserializing some common formats:
13//!
14//! - [`value::Value`]: This type provides access to the location where the
15//!   config value was defined.
16//! - [`ConfigRelativePath`]: For a path that is relative to where it is
17//!   defined.
18//! - [`PathAndArgs`]: Similar to [`ConfigRelativePath`],
19//!   but also supports a list of arguments, useful for programs to execute.
20//! - [`StringList`]: Get a value that is either a list or a whitespace split
21//!   string.
22//!
23//! # Config schemas
24//!
25//! Configuration schemas are defined in the [`schema`] module.
26//!
27//! ## Config deserialization
28//!
29//! Cargo uses a two-layer deserialization approach:
30//!
31//! 1. **External sources → `ConfigValue`** ---
32//!    Configuration files, environment variables, and CLI `--config` arguments
33//!    are parsed into [`ConfigValue`] instances via [`ConfigValue::from_toml`].
34//!    These parsed results are stored in [`GlobalContext`].
35//!
36//! 2. **`ConfigValue` → Target types** ---
37//!    The [`GlobalContext::get`] method uses a [custom serde deserializer](Deserializer)
38//!    to convert [`ConfigValue`] instances to the caller's desired type.
39//!    Precedence between [`ConfigValue`] sources is resolved during retrieval
40//!    based on [`Definition`] priority.
41//!    See the top-level documentation of the [`de`] module for more.
42//!
43//! ## Map key recommendations
44//!
45//! Handling tables that have arbitrary keys can be tricky, particularly if it
46//! should support environment variables. In general, if possible, the caller
47//! should pass the full key path into the `get()` method so that the config
48//! deserializer can properly handle environment variables (which need to be
49//! uppercased, and dashes converted to underscores).
50//!
51//! A good example is the `[target]` table. The code will request
52//! `target.$TUPLE` and the config system can then appropriately fetch
53//! environment variables like `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER`.
54//! Conversely, it is not possible do the same thing for the `cfg()` target
55//! tables (because Cargo must fetch all of them), so those do not support
56//! environment variables.
57//!
58//! Try to avoid keys that are a prefix of another with a dash/underscore. For
59//! example `build.target` and `build.target-dir`. This is OK if these are not
60//! structs/maps, but if it is a struct or map, then it will not be able to
61//! read the environment variable due to ambiguity. (See `ConfigMapAccess` for
62//! more details.)
63
64use crate::util::data_structures::{HashMap, HashSet};
65use std::borrow::Cow;
66use std::env;
67use std::ffi::{OsStr, OsString};
68use std::fmt;
69use std::fs::{self, File};
70use std::io::SeekFrom;
71use std::io::prelude::*;
72use std::mem;
73use std::path::{Path, PathBuf};
74use std::str::FromStr;
75use std::sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock};
76use std::time::Instant;
77
78use self::ConfigValue as CV;
79use crate::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 network_allowed(&self) -> bool {
1263        !self.offline_flag().is_some()
1264    }
1265
1266    pub fn offline_flag(&self) -> Option<&'static str> {
1267        if self.frozen {
1268            Some("--frozen")
1269        } else if self.offline {
1270            Some("--offline")
1271        } else {
1272            None
1273        }
1274    }
1275
1276    pub fn set_locked(&mut self, locked: bool) {
1277        self.locked = locked;
1278    }
1279
1280    pub fn lock_update_allowed(&self) -> bool {
1281        !self.locked_flag().is_some()
1282    }
1283
1284    pub fn locked_flag(&self) -> Option<&'static str> {
1285        if self.frozen {
1286            Some("--frozen")
1287        } else if self.locked {
1288            Some("--locked")
1289        } else {
1290            None
1291        }
1292    }
1293
1294    /// Loads configuration from the filesystem.
1295    pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1296        self.load_values_from(&self.cwd)
1297    }
1298
1299    /// Like [`load_values`](GlobalContext::load_values) but without merging config values.
1300    ///
1301    /// This is primarily crafted for `cargo config` command.
1302    pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1303        let mut result = Vec::new();
1304        let mut seen = HashSet::default();
1305        let home = self.home_path.clone().into_path_unlocked();
1306        self.walk_tree(&self.cwd, &home, |path| {
1307            let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1308            self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1309            result.push(cv);
1310            Ok(())
1311        })
1312        .context("could not load Cargo configuration")?;
1313        Ok(result)
1314    }
1315
1316    /// Like [`load_includes`](GlobalContext::load_includes) but without merging config values.
1317    ///
1318    /// This is primarily crafted for `cargo config` command.
1319    fn load_unmerged_include(
1320        &self,
1321        cv: &mut CV,
1322        seen: &mut HashSet<PathBuf>,
1323        output: &mut Vec<CV>,
1324    ) -> CargoResult<()> {
1325        let includes = self.include_paths(cv, false)?;
1326        for include in includes {
1327            let Some(abs_path) = include.resolve_path(self) else {
1328                continue;
1329            };
1330
1331            let mut cv = self
1332                ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1333                .with_context(|| {
1334                    format!(
1335                        "failed to load config include `{}` from `{}`",
1336                        include.path.display(),
1337                        include.def
1338                    )
1339                })?;
1340            self.load_unmerged_include(&mut cv, seen, output)?;
1341            output.push(cv);
1342        }
1343        Ok(())
1344    }
1345
1346    /// Start a config file discovery from a path and merges all config values found.
1347    fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1348        // The root config value container isn't from any external source,
1349        // so its definition should be built-in.
1350        let mut cfg = CV::Table(HashMap::default(), Definition::BuiltIn);
1351        let home = self.home_path.clone().into_path_unlocked();
1352
1353        self.walk_tree(path, &home, |path| {
1354            let value = self.load_file(path)?;
1355            cfg.merge(value, false).with_context(|| {
1356                format!("failed to merge configuration at `{}`", path.display())
1357            })?;
1358            Ok(())
1359        })
1360        .context("could not load Cargo configuration")?;
1361
1362        match cfg {
1363            CV::Table(map, _) => Ok(map),
1364            _ => unreachable!(),
1365        }
1366    }
1367
1368    /// Loads a config value from a path.
1369    ///
1370    /// This is used during config file discovery.
1371    fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1372        self._load_file(path, &mut HashSet::default(), true, WhyLoad::FileDiscovery)
1373    }
1374
1375    /// Loads a config value from a path with options.
1376    ///
1377    /// This is actual implementation of loading a config value from a path.
1378    ///
1379    /// * `includes` determines whether to load configs from [`ConfigInclude`].
1380    /// * `seen` is used to check for cyclic includes.
1381    /// * `why_load` tells why a config is being loaded.
1382    fn _load_file(
1383        &self,
1384        path: &Path,
1385        seen: &mut HashSet<PathBuf>,
1386        includes: bool,
1387        why_load: WhyLoad,
1388    ) -> CargoResult<ConfigValue> {
1389        if !seen.insert(path.to_path_buf()) {
1390            bail!(
1391                "config `include` cycle detected with path `{}`",
1392                path.display()
1393            );
1394        }
1395        tracing::debug!(?path, ?why_load, includes, "load config from file");
1396
1397        let contents = fs::read_to_string(path)
1398            .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1399        let toml = parse_document(&contents, path, self).with_context(|| {
1400            format!("could not parse TOML configuration in `{}`", path.display())
1401        })?;
1402        let def = match why_load {
1403            WhyLoad::Cli => Definition::Cli(Some(path.into())),
1404            WhyLoad::FileDiscovery => Definition::Path(path.into()),
1405        };
1406        let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1407            format!(
1408                "failed to load TOML configuration from `{}`",
1409                path.display()
1410            )
1411        })?;
1412        if includes {
1413            self.load_includes(value, seen, why_load)
1414        } else {
1415            Ok(value)
1416        }
1417    }
1418
1419    /// Load any `include` files listed in the given `value`.
1420    ///
1421    /// Returns `value` with the given include files merged into it.
1422    ///
1423    /// * `seen` is used to check for cyclic includes.
1424    /// * `why_load` tells why a config is being loaded.
1425    fn load_includes(
1426        &self,
1427        mut value: CV,
1428        seen: &mut HashSet<PathBuf>,
1429        why_load: WhyLoad,
1430    ) -> CargoResult<CV> {
1431        // Get the list of files to load.
1432        let includes = self.include_paths(&mut value, true)?;
1433
1434        // Accumulate all values here.
1435        let mut root = CV::Table(HashMap::default(), value.definition().clone());
1436        for include in includes {
1437            let Some(abs_path) = include.resolve_path(self) else {
1438                continue;
1439            };
1440
1441            self._load_file(&abs_path, seen, true, why_load)
1442                .and_then(|include| root.merge(include, true))
1443                .with_context(|| {
1444                    format!(
1445                        "failed to load config include `{}` from `{}`",
1446                        include.path.display(),
1447                        include.def
1448                    )
1449                })?;
1450        }
1451        root.merge(value, true)?;
1452        Ok(root)
1453    }
1454
1455    /// Converts the `include` config value to a list of absolute paths.
1456    fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1457        let CV::Table(table, _def) = cv else {
1458            unreachable!()
1459        };
1460        let include = if remove {
1461            table.remove("include").map(Cow::Owned)
1462        } else {
1463            table.get("include").map(Cow::Borrowed)
1464        };
1465        let includes = match include.map(|c| c.into_owned()) {
1466            Some(CV::List(list, _def)) => list
1467                .into_iter()
1468                .enumerate()
1469                .map(|(idx, cv)| match cv {
1470                    CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1471                    CV::Table(mut table, def) => {
1472                        // Extract `include.path`
1473                        let s = match table.remove("path") {
1474                            Some(CV::String(s, _)) => s,
1475                            Some(other) => bail!(
1476                                "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1477                                other.desc()
1478                            ),
1479                            None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1480                        };
1481
1482                        // Extract optional `include.optional` field
1483                        let optional = match table.remove("optional") {
1484                            Some(CV::Boolean(b, _)) => b,
1485                            Some(other) => bail!(
1486                                "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1487                                other.desc()
1488                            ),
1489                            None => false,
1490                        };
1491
1492                        let mut include = ConfigInclude::new(s, def);
1493                        include.optional = optional;
1494                        Ok(include)
1495                    }
1496                    other => bail!(
1497                        "expected a string or table, but found {} at `include[{idx}]` in {}",
1498                        other.desc(),
1499                        other.definition(),
1500                    ),
1501                })
1502                .collect::<CargoResult<Vec<_>>>()?,
1503            Some(other) => bail!(
1504                "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1505                other.desc(),
1506                other.definition()
1507            ),
1508            None => {
1509                return Ok(Vec::new());
1510            }
1511        };
1512
1513        for include in &includes {
1514            if include.path.extension() != Some(OsStr::new("toml")) {
1515                bail!(
1516                    "expected a config include path ending with `.toml`, \
1517                     but found `{}` from `{}`",
1518                    include.path.display(),
1519                    include.def,
1520                )
1521            }
1522
1523            if let Some(path) = include.path.to_str() {
1524                // Ignore non UTF-8 bytes as glob and template syntax are for textual config.
1525                if is_glob_pattern(path) {
1526                    bail!(
1527                        "expected a config include path without glob patterns, \
1528                         but found `{}` from `{}`",
1529                        include.path.display(),
1530                        include.def,
1531                    )
1532                }
1533                if path.contains(&['{', '}']) {
1534                    bail!(
1535                        "expected a config include path without template braces, \
1536                         but found `{}` from `{}`",
1537                        include.path.display(),
1538                        include.def,
1539                    )
1540                }
1541            }
1542        }
1543
1544        Ok(includes)
1545    }
1546
1547    /// Parses the CLI config args and returns them as a table.
1548    pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1549        let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1550        let Some(cli_args) = &self.cli_config else {
1551            return Ok(loaded_args);
1552        };
1553        let mut seen = HashSet::default();
1554        for arg in cli_args {
1555            let arg_as_path = self.cwd.join(arg);
1556            let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1557                // --config path_to_file
1558                self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1559                    .with_context(|| {
1560                        format!("failed to load config from `{}`", arg_as_path.display())
1561                    })?
1562            } else {
1563                let doc = toml_dotted_keys(arg)?;
1564                let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1565                    .with_context(|| {
1566                        format!("failed to parse value from --config argument `{arg}`")
1567                    })?;
1568
1569                if doc
1570                    .get("registry")
1571                    .and_then(|v| v.as_table())
1572                    .and_then(|t| t.get("token"))
1573                    .is_some()
1574                {
1575                    bail!("registry.token cannot be set through --config for security reasons");
1576                } else if let Some((k, _)) = doc
1577                    .get("registries")
1578                    .and_then(|v| v.as_table())
1579                    .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1580                {
1581                    bail!(
1582                        "registries.{}.token cannot be set through --config for security reasons",
1583                        k
1584                    );
1585                }
1586
1587                if doc
1588                    .get("registry")
1589                    .and_then(|v| v.as_table())
1590                    .and_then(|t| t.get("secret-key"))
1591                    .is_some()
1592                {
1593                    bail!(
1594                        "registry.secret-key cannot be set through --config for security reasons"
1595                    );
1596                } else if let Some((k, _)) = doc
1597                    .get("registries")
1598                    .and_then(|v| v.as_table())
1599                    .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1600                {
1601                    bail!(
1602                        "registries.{}.secret-key cannot be set through --config for security reasons",
1603                        k
1604                    );
1605                }
1606
1607                CV::from_toml(Definition::Cli(None), doc)
1608                    .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1609            };
1610            let tmp_table = self
1611                .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1612                .context("failed to load --config include".to_string())?;
1613            loaded_args
1614                .merge(tmp_table, true)
1615                .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1616        }
1617        Ok(loaded_args)
1618    }
1619
1620    /// Add config arguments passed on the command line.
1621    fn merge_cli_args(&mut self) -> CargoResult<()> {
1622        let cv_from_cli = self.cli_args_as_table()?;
1623        assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1624
1625        let root_cv = mem::take(self.values_mut()?);
1626        // The root config value container isn't from any external source,
1627        // so its definition should be built-in.
1628        let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1629        root_cv.merge(cv_from_cli, true)?;
1630
1631        // Put it back to gctx
1632        mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1633
1634        Ok(())
1635    }
1636
1637    /// The purpose of this function is to aid in the transition to using
1638    /// .toml extensions on Cargo's config files, which were historically not used.
1639    /// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.
1640    /// When both exist, we want to prefer the one without an extension for
1641    /// backwards compatibility, but warn the user appropriately.
1642    fn get_file_path(
1643        &self,
1644        dir: &Path,
1645        filename_without_extension: &str,
1646        warn: bool,
1647    ) -> CargoResult<Option<PathBuf>> {
1648        let possible = dir.join(filename_without_extension);
1649        let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1650
1651        if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1652            if warn {
1653                if let Ok(possible_with_extension_handle) =
1654                    same_file::Handle::from_path(&possible_with_extension)
1655                {
1656                    // We don't want to print a warning if the version
1657                    // without the extension is just a symlink to the version
1658                    // WITH an extension, which people may want to do to
1659                    // support multiple Cargo versions at once and not
1660                    // get a warning.
1661                    if possible_handle != possible_with_extension_handle {
1662                        self.shell().warn(format!(
1663                            "both `{}` and `{}` exist. Using `{}`",
1664                            possible.display(),
1665                            possible_with_extension.display(),
1666                            possible.display()
1667                        ))?;
1668                    }
1669                } else {
1670                    self.shell().print_report(&[
1671                        Level::WARNING.secondary_title(
1672                            format!(
1673                                "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1674                                possible.display(),
1675                            )).element(Level::HELP.message(
1676                            format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1677                    ], false)?;
1678                }
1679            }
1680
1681            Ok(Some(possible))
1682        } else if possible_with_extension.exists() {
1683            Ok(Some(possible_with_extension))
1684        } else {
1685            Ok(None)
1686        }
1687    }
1688
1689    fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1690    where
1691        F: FnMut(&Path) -> CargoResult<()>,
1692    {
1693        let mut seen_dir = HashSet::default();
1694
1695        for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1696            let config_root = current.join(".cargo");
1697            if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1698                walk(&path)?;
1699            }
1700
1701            let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1702            seen_dir.insert(canonical_root);
1703        }
1704
1705        let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1706
1707        // Once we're done, also be sure to walk the home directory even if it's not
1708        // in our history to be sure we pick up that standard location for
1709        // information.
1710        if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1711            if let Some(path) = self.get_file_path(home, "config", true)? {
1712                walk(&path)?;
1713            }
1714        }
1715
1716        Ok(())
1717    }
1718
1719    /// Gets the index for a registry.
1720    pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1721        RegistryName::new(registry)?;
1722        if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1723            self.resolve_registry_index(&index).with_context(|| {
1724                format!(
1725                    "invalid index URL for registry `{}` defined in {}",
1726                    registry, index.definition
1727                )
1728            })
1729        } else {
1730            bail!(
1731                "registry index was not found in any configuration: `{}`",
1732                registry
1733            );
1734        }
1735    }
1736
1737    /// Returns an error if `registry.index` is set.
1738    pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1739        if self.get_string("registry.index")?.is_some() {
1740            bail!(
1741                "the `registry.index` config value is no longer supported\n\
1742                Use `[source]` replacement to alter the default index for crates.io."
1743            );
1744        }
1745        Ok(())
1746    }
1747
1748    fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1749        // This handles relative file: URLs, relative to the config definition.
1750        let base = index
1751            .definition
1752            .root(self.cwd())
1753            .join("truncated-by-url_with_base");
1754        // Parse val to check it is a URL, not a relative path without a protocol.
1755        let _parsed = index.val.into_url()?;
1756        let url = index.val.into_url_with_base(Some(&*base))?;
1757        if url.password().is_some() {
1758            bail!("registry URLs may not contain passwords");
1759        }
1760        Ok(url)
1761    }
1762
1763    /// Loads credentials config from the credentials file, if present.
1764    ///
1765    /// The credentials are loaded into a separate field to enable them
1766    /// to be lazy-loaded after the main configuration has been loaded,
1767    /// without requiring `mut` access to the [`GlobalContext`].
1768    ///
1769    /// If the credentials are already loaded, this function does nothing.
1770    pub fn load_credentials(&self) -> CargoResult<()> {
1771        if self.credential_values.filled() {
1772            return Ok(());
1773        }
1774
1775        let home_path = self.home_path.clone().into_path_unlocked();
1776        let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1777            return Ok(());
1778        };
1779
1780        let mut value = self.load_file(&credentials)?;
1781        // Backwards compatibility for old `.cargo/credentials` layout.
1782        {
1783            let (value_map, def) = value.table_mut("<root>")?;
1784
1785            if let Some(token) = value_map.remove("token") {
1786                value_map.entry("registry".into()).or_insert_with(|| {
1787                    let map = HashMap::from_iter([("token".into(), token)]);
1788                    CV::Table(map, def.clone())
1789                });
1790            }
1791        }
1792
1793        let mut credential_values = HashMap::default();
1794        if let CV::Table(map, _) = value {
1795            let base_map = self.values()?;
1796            for (k, v) in map {
1797                let entry = match base_map.get(&k) {
1798                    Some(base_entry) => {
1799                        let mut entry = base_entry.clone();
1800                        entry.merge(v, true)?;
1801                        entry
1802                    }
1803                    None => v,
1804                };
1805                credential_values.insert(k, entry);
1806            }
1807        }
1808        self.credential_values
1809            .set(credential_values)
1810            .expect("was not filled at beginning of the function");
1811        Ok(())
1812    }
1813
1814    /// Looks for a path for `tool` in an environment variable or the given config, and returns
1815    /// `None` if it's not present.
1816    fn maybe_get_tool(
1817        &self,
1818        tool: &str,
1819        from_config: &Option<ConfigRelativePath>,
1820    ) -> Option<PathBuf> {
1821        let var = tool.to_uppercase();
1822
1823        match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1824            Some(tool_path) => {
1825                let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1826                let path = if maybe_relative {
1827                    self.cwd.join(tool_path)
1828                } else {
1829                    PathBuf::from(tool_path)
1830                };
1831                Some(path)
1832            }
1833
1834            None => from_config.as_ref().map(|p| p.resolve_program(self)),
1835        }
1836    }
1837
1838    /// Returns the path for the given tool.
1839    ///
1840    /// This will look for the tool in the following order:
1841    ///
1842    /// 1. From an environment variable matching the tool name (such as `RUSTC`).
1843    /// 2. From the given config value (which is usually something like `build.rustc`).
1844    /// 3. Finds the tool in the PATH environment variable.
1845    ///
1846    /// This is intended for tools that are rustup proxies. If you need to get
1847    /// a tool that is not a rustup proxy, use `maybe_get_tool` instead.
1848    fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1849        let tool_str = tool.as_str();
1850        self.maybe_get_tool(tool_str, from_config)
1851            .or_else(|| {
1852                // This is an optimization to circumvent the rustup proxies
1853                // which can have a significant performance hit. The goal here
1854                // is to determine if calling `rustc` from PATH would end up
1855                // calling the proxies.
1856                //
1857                // This is somewhat cautious trying to determine if it is safe
1858                // to circumvent rustup, because there are some situations
1859                // where users may do things like modify PATH, call cargo
1860                // directly, use a custom rustup toolchain link without a
1861                // cargo executable, etc. However, there is still some risk
1862                // this may make the wrong decision in unusual circumstances.
1863                //
1864                // First, we must be running under rustup in the first place.
1865                let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1866                // This currently does not support toolchain paths.
1867                // This also enforces UTF-8.
1868                if toolchain.to_str()?.contains(&['/', '\\']) {
1869                    return None;
1870                }
1871                // If the tool on PATH is the same as `rustup` on path, then
1872                // there is pretty good evidence that it will be a proxy.
1873                let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1874                let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1875                let tool_meta = tool_resolved.metadata().ok()?;
1876                let rustup_meta = rustup_resolved.metadata().ok()?;
1877                // This works on the assumption that rustup and its proxies
1878                // use hard links to a single binary. If rustup ever changes
1879                // that setup, then I think the worst consequence is that this
1880                // optimization will not work, and it will take the slow path.
1881                if tool_meta.len() != rustup_meta.len() {
1882                    return None;
1883                }
1884                // Try to find the tool in rustup's toolchain directory.
1885                let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1886                let toolchain_exe = home::rustup_home()
1887                    .ok()?
1888                    .join("toolchains")
1889                    .join(&toolchain)
1890                    .join("bin")
1891                    .join(&tool_exe);
1892                toolchain_exe.exists().then_some(toolchain_exe)
1893            })
1894            .unwrap_or_else(|| PathBuf::from(tool_str))
1895    }
1896
1897    /// Get the `paths` overrides config value.
1898    pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1899        let key = ConfigKey::from_str("paths");
1900        // paths overrides cannot be set via env config, so use get_cv here.
1901        match self.get_cv(&key)? {
1902            Some(CV::List(val, definition)) => {
1903                let val = val
1904                    .into_iter()
1905                    .map(|cv| match cv {
1906                        CV::String(s, def) => Ok((s, def)),
1907                        other => self.expected("string", &key, &other),
1908                    })
1909                    .collect::<CargoResult<Vec<_>>>()?;
1910                Ok(Some(Value { val, definition }))
1911            }
1912            Some(val) => self.expected("list", &key, &val),
1913            None => Ok(None),
1914        }
1915    }
1916
1917    pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1918        self.jobserver
1919    }
1920
1921    pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1922        let http = self
1923            .easy
1924            .try_borrow_with(|| http_handle(self).map(Into::into))?;
1925        {
1926            let mut http = http.lock().unwrap();
1927            http.reset();
1928            let timeout = configure_http_handle(self, &mut http)?;
1929            timeout.configure(&mut http)?;
1930        }
1931        Ok(http)
1932    }
1933
1934    pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1935        self.http_async.try_borrow_with(|| {
1936            let handle_config = HandleConfiguration::new(&self)?;
1937            Ok(http_async::Client::new(handle_config))
1938        })
1939    }
1940
1941    pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1942        self.http_config.try_borrow_with(|| {
1943            let mut http = self.get::<CargoHttpConfig>("http")?;
1944            let curl_v = curl::Version::get();
1945            disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1946            Ok(http)
1947        })
1948    }
1949
1950    pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1951        self.future_incompat_config
1952            .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1953    }
1954
1955    pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1956        self.net_config
1957            .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1958    }
1959
1960    pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1961        self.build_config
1962            .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1963    }
1964
1965    pub fn progress_config(&self) -> &ProgressConfig {
1966        &self.progress_config
1967    }
1968
1969    /// Get the env vars from the config `[env]` table which
1970    /// are `force = true` or don't exist in the env snapshot [`GlobalContext::get_env`].
1971    pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1972        let env_config = self.env_config.try_borrow_with(|| {
1973            CargoResult::Ok(Arc::new({
1974                let env_config = self.get::<EnvConfig>("env")?;
1975                // Reasons for disallowing these values:
1976                //
1977                // - CARGO_HOME: The initial call to cargo does not honor this value
1978                //   from the [env] table. Recursive calls to cargo would use the new
1979                //   value, possibly behaving differently from the outer cargo.
1980                //
1981                // - RUSTUP_HOME and RUSTUP_TOOLCHAIN: Under normal usage with rustup,
1982                //   this will have no effect because the rustup proxy sets
1983                //   RUSTUP_HOME and RUSTUP_TOOLCHAIN, and that would override the
1984                //   [env] table. If the outer cargo is executed directly
1985                //   circumventing the rustup proxy, then this would affect calls to
1986                //   rustc (assuming that is a proxy), which could potentially cause
1987                //   problems with cargo and rustc being from different toolchains. We
1988                //   consider this to be not a use case we would like to support,
1989                //   since it will likely cause problems or lead to confusion.
1990                for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1991                    if env_config.contains_key(*disallowed) {
1992                        bail!(
1993                            "setting the `{disallowed}` environment variable is not supported \
1994                            in the `[env]` configuration table"
1995                        );
1996                    }
1997                }
1998                env_config
1999                    .into_iter()
2000                    .filter_map(|(k, v)| {
2001                        if v.is_force() || self.get_env_os(&k).is_none() {
2002                            Some((k, v.resolve(self.cwd()).to_os_string()))
2003                        } else {
2004                            None
2005                        }
2006                    })
2007                    .collect()
2008            }))
2009        })?;
2010
2011        Ok(env_config)
2012    }
2013
2014    /// This is used to validate the `term` table has valid syntax.
2015    ///
2016    /// This is necessary because loading the term settings happens very
2017    /// early, and in some situations (like `cargo version`) we don't want to
2018    /// fail if there are problems with the config file.
2019    pub fn validate_term_config(&self) -> CargoResult<()> {
2020        drop(self.get::<TermConfig>("term")?);
2021        Ok(())
2022    }
2023
2024    /// Returns a list of `target.'cfg()'` tables.
2025    ///
2026    /// The list is sorted by the table name.
2027    pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2028        self.target_cfgs
2029            .try_borrow_with(|| target::load_target_cfgs(self))
2030    }
2031
2032    pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2033        // Note: This does not support environment variables. The `Unit`
2034        // fundamentally does not have access to the registry name, so there is
2035        // nothing to query. Plumbing the name into SourceId is quite challenging.
2036        self.doc_extern_map
2037            .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2038    }
2039
2040    /// Returns true if the `[target]` table should be applied to host targets.
2041    pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2042        target::get_target_applies_to_host(self)
2043    }
2044
2045    /// Returns the `[host]` table definition for the given target tuple.
2046    pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2047        target::load_host_triple(self, target)
2048    }
2049
2050    /// Returns the `[target]` table definition for the given target tuple.
2051    pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2052        target::load_target_triple(self, target)
2053    }
2054
2055    /// Returns the cached [`SourceId`] corresponding to the main repository.
2056    ///
2057    /// This is the main cargo registry by default, but it can be overridden in
2058    /// a `.cargo/config.toml`.
2059    pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2060        let source_id = self.crates_io_source_id.try_borrow_with(|| {
2061            self.check_registry_index_not_set()?;
2062            let url = CRATES_IO_INDEX.into_url().unwrap();
2063            SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2064        })?;
2065        Ok(*source_id)
2066    }
2067
2068    pub fn invocation_instant(&self) -> Instant {
2069        self.invocation_instant
2070    }
2071
2072    /// Returns the wall-clock time of this cargo invocation.
2073    ///
2074    /// See the [`invocation_time`] field doc for details.
2075    ///
2076    /// [`invocation_time`]: GlobalContext::invocation_time
2077    pub fn invocation_time(&self) -> jiff::Timestamp {
2078        self.invocation_time
2079    }
2080
2081    /// Retrieves a config variable.
2082    ///
2083    /// This supports most serde `Deserialize` types. Examples:
2084    ///
2085    /// ```rust,ignore
2086    /// let v: Option<u32> = config.get("some.nested.key")?;
2087    /// let v: Option<MyStruct> = config.get("some.key")?;
2088    /// let v: Option<HashMap<String, MyStruct>> = config.get("foo")?;
2089    /// ```
2090    ///
2091    /// The key may be a dotted key, but this does NOT support TOML key
2092    /// quoting. Avoid key components that may have dots. For example,
2093    /// `foo.'a.b'.bar" does not work if you try to fetch `foo.'a.b'". You can
2094    /// fetch `foo` if it is a map, though.
2095    pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2096        let d = Deserializer {
2097            gctx: self,
2098            key: ConfigKey::from_str(key),
2099            env_prefix_ok: true,
2100        };
2101        T::deserialize(d).map_err(|e| e.into())
2102    }
2103
2104    /// Obtain a [`Path`] from a [`Filesystem`], verifying that the
2105    /// appropriate lock is already currently held.
2106    ///
2107    /// Locks are usually acquired via [`GlobalContext::acquire_package_cache_lock`]
2108    /// or [`GlobalContext::try_acquire_package_cache_lock`].
2109    #[track_caller]
2110    #[tracing::instrument(skip_all)]
2111    pub fn assert_package_cache_locked<'a>(
2112        &self,
2113        mode: CacheLockMode,
2114        f: &'a Filesystem,
2115    ) -> &'a Path {
2116        let ret = f.as_path_unlocked();
2117        assert!(
2118            self.package_cache_lock.is_locked(mode),
2119            "package cache lock is not currently held, Cargo forgot to call \
2120             `acquire_package_cache_lock` before we got to this stack frame",
2121        );
2122        assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2123        ret
2124    }
2125
2126    /// Acquires a lock on the global "package cache", blocking if another
2127    /// cargo holds the lock.
2128    ///
2129    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
2130    /// and lock modes.
2131    #[tracing::instrument(skip_all)]
2132    pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2133        self.package_cache_lock.lock(self, mode)
2134    }
2135
2136    /// Acquires a lock on the global "package cache", returning `None` if
2137    /// another cargo holds the lock.
2138    ///
2139    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
2140    /// and lock modes.
2141    #[tracing::instrument(skip_all)]
2142    pub fn try_acquire_package_cache_lock(
2143        &self,
2144        mode: CacheLockMode,
2145    ) -> CargoResult<Option<CacheLock<'_>>> {
2146        self.package_cache_lock.try_lock(self, mode)
2147    }
2148
2149    /// Returns a reference to the shared [`GlobalCacheTracker`].
2150    ///
2151    /// The package cache lock must be held to call this function (and to use
2152    /// it in general).
2153    pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2154        let tracker = self.global_cache_tracker.try_borrow_with(|| {
2155            Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2156        })?;
2157        Ok(tracker.lock().unwrap())
2158    }
2159
2160    /// Returns a reference to the shared [`DeferredGlobalLastUse`].
2161    pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2162        let deferred = self
2163            .deferred_global_last_use
2164            .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2165        Ok(deferred.lock().unwrap())
2166    }
2167
2168    /// Get the global [`WarningHandling`] configuration.
2169    pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2170        Ok(self.build_config()?.warnings.unwrap_or_default())
2171    }
2172
2173    pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2174        self.ws_roots.lock().unwrap()
2175    }
2176}
2177
2178pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2179    ::home::cargo_home_with_cwd(cwd)
2180        .ok()
2181        // https://github.com/rust-lang/cargo/issues/15981
2182        // This is so everything shares one spelling and
2183        // isn't incorrectly seen as distinct.
2184        .map(|home| paths::normalize_path(&home))
2185}
2186
2187pub fn save_credentials(
2188    gctx: &GlobalContext,
2189    token: Option<RegistryCredentialConfig>,
2190    registry: &SourceId,
2191) -> CargoResult<()> {
2192    let registry = if registry.is_crates_io() {
2193        None
2194    } else {
2195        let name = registry
2196            .alt_registry_key()
2197            .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2198        Some(name)
2199    };
2200
2201    // If 'credentials' exists, write to that for backward compatibility reasons.
2202    // Otherwise write to 'credentials.toml'. There's no need to print the
2203    // warning here, because it would already be printed at load time.
2204    let home_path = gctx.home_path.clone().into_path_unlocked();
2205    let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2206        Some(path) => match path.file_name() {
2207            Some(filename) => Path::new(filename).to_owned(),
2208            None => Path::new("credentials.toml").to_owned(),
2209        },
2210        None => Path::new("credentials.toml").to_owned(),
2211    };
2212
2213    let mut file = {
2214        gctx.home_path.create_dir()?;
2215        gctx.home_path
2216            .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2217    };
2218
2219    let mut contents = String::new();
2220    file.read_to_string(&mut contents).with_context(|| {
2221        format!(
2222            "failed to read configuration file `{}`",
2223            file.path().display()
2224        )
2225    })?;
2226
2227    let mut toml = parse_document(&contents, file.path(), gctx)?;
2228
2229    // Move the old token location to the new one.
2230    if let Some(token) = toml.remove("token") {
2231        #[expect(
2232            clippy::disallowed_types,
2233            reason = "need stdlib's HashMap because of TOML compatibility"
2234        )]
2235        let map = std::collections::HashMap::from([("token".to_string(), token)]);
2236        toml.insert("registry".into(), map.into());
2237    }
2238
2239    if let Some(token) = token {
2240        // login
2241
2242        let path_def = Definition::Path(file.path().to_path_buf());
2243        let (key, mut value) = match token {
2244            RegistryCredentialConfig::Token(token) => {
2245                // login with token
2246
2247                let key = "token".to_string();
2248                let value = ConfigValue::String(token.expose(), path_def.clone());
2249                let map = HashMap::from_iter([(key, value)]);
2250                let table = CV::Table(map, path_def.clone());
2251
2252                if let Some(registry) = registry {
2253                    let map = HashMap::from_iter([(registry.to_string(), table)]);
2254                    ("registries".into(), CV::Table(map, path_def.clone()))
2255                } else {
2256                    ("registry".into(), table)
2257                }
2258            }
2259            RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2260                // login with key
2261
2262                let key = "secret-key".to_string();
2263                let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2264                let mut map = HashMap::from_iter([(key, value)]);
2265                if let Some(key_subject) = key_subject {
2266                    let key = "secret-key-subject".to_string();
2267                    let value = ConfigValue::String(key_subject, path_def.clone());
2268                    map.insert(key, value);
2269                }
2270                let table = CV::Table(map, path_def.clone());
2271
2272                if let Some(registry) = registry {
2273                    let map = HashMap::from_iter([(registry.to_string(), table)]);
2274                    ("registries".into(), CV::Table(map, path_def.clone()))
2275                } else {
2276                    ("registry".into(), table)
2277                }
2278            }
2279            _ => unreachable!(),
2280        };
2281
2282        if registry.is_some() {
2283            if let Some(table) = toml.remove("registries") {
2284                let v = CV::from_toml(path_def, table)?;
2285                value.merge(v, false)?;
2286            }
2287        }
2288        toml.insert(key, value.into_toml());
2289    } else {
2290        // logout
2291        if let Some(registry) = registry {
2292            if let Some(registries) = toml.get_mut("registries") {
2293                if let Some(reg) = registries.get_mut(registry) {
2294                    let rtable = reg.as_table_mut().ok_or_else(|| {
2295                        format_err!("expected `[registries.{}]` to be a table", registry)
2296                    })?;
2297                    rtable.remove("token");
2298                    rtable.remove("secret-key");
2299                    rtable.remove("secret-key-subject");
2300                }
2301            }
2302        } else if let Some(registry) = toml.get_mut("registry") {
2303            let reg_table = registry
2304                .as_table_mut()
2305                .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2306            reg_table.remove("token");
2307            reg_table.remove("secret-key");
2308            reg_table.remove("secret-key-subject");
2309        }
2310    }
2311
2312    let contents = toml.to_string();
2313    file.seek(SeekFrom::Start(0))?;
2314    file.write_all(contents.as_bytes())
2315        .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2316    file.file().set_len(contents.len() as u64)?;
2317    set_permissions(file.file(), 0o600)
2318        .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2319
2320    return Ok(());
2321
2322    #[cfg(unix)]
2323    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2324        use std::os::unix::fs::PermissionsExt;
2325
2326        let mut perms = file.metadata()?.permissions();
2327        perms.set_mode(mode);
2328        file.set_permissions(perms)?;
2329        Ok(())
2330    }
2331
2332    #[cfg(not(unix))]
2333    fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2334        Ok(())
2335    }
2336}
2337
2338/// Represents a config-include value in the configuration.
2339///
2340/// This intentionally doesn't derive serde deserialization
2341/// to avoid any misuse of `GlobalContext::get::<ConfigInclude>()`,
2342/// which might lead to wrong config loading order.
2343struct ConfigInclude {
2344    /// Path to a config-include configuration file.
2345    /// Could be either relative or absolute.
2346    path: PathBuf,
2347    def: Definition,
2348    /// Whether this include is optional (missing files are silently ignored)
2349    optional: bool,
2350}
2351
2352impl ConfigInclude {
2353    fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2354        Self {
2355            path: p.into(),
2356            def,
2357            optional: false,
2358        }
2359    }
2360
2361    /// Resolves the absolute path for this include.
2362    ///
2363    /// For file based include,
2364    /// it is relative to parent directory of the config file includes it.
2365    /// For example, if `.cargo/config.toml has a `include = "foo.toml"`,
2366    /// Cargo will load `.cargo/foo.toml`.
2367    ///
2368    /// For CLI based include (e.g., `--config 'include = "foo.toml"'`),
2369    /// it is relative to the current working directory.
2370    ///
2371    /// Returns `None` if this is an optional include and the file doesn't exist.
2372    /// Otherwise returns `Some(PathBuf)` with the absolute path.
2373    fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2374        let abs_path = match &self.def {
2375            Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2376            Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2377        }
2378        .join(&self.path);
2379        let abs_path = paths::normalize_path(&abs_path);
2380
2381        if self.optional && !abs_path.exists() {
2382            tracing::info!(
2383                "skipping optional include `{}` in `{}`:  file not found at `{}`",
2384                self.path.display(),
2385                self.def,
2386                abs_path.display(),
2387            );
2388            None
2389        } else {
2390            Some(abs_path)
2391        }
2392    }
2393}
2394
2395fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2396    // At the moment, no compatibility checks are needed.
2397    toml.parse().map_err(Into::into)
2398}
2399
2400fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2401    // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
2402    // expressions followed by a value that's not an "inline table"
2403    // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
2404    // parse the value as a toml_edit::DocumentMut, and check that the (single)
2405    // inner-most table is set via dotted keys.
2406    let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2407        format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2408    })?;
2409    fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2410        d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2411    }
2412    fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2413        non_empty(d.prefix()) || non_empty(d.suffix())
2414    }
2415    fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2416        non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2417    }
2418    let ok = {
2419        let mut got_to_value = false;
2420        let mut table = doc.as_table();
2421        let mut is_root = true;
2422        while table.is_dotted() || is_root {
2423            is_root = false;
2424            if table.len() != 1 {
2425                break;
2426            }
2427            let (k, n) = table.iter().next().expect("len() == 1 above");
2428            match n {
2429                Item::Table(nt) => {
2430                    if table.key(k).map_or(false, non_empty_key_decor)
2431                        || non_empty_decor(nt.decor())
2432                    {
2433                        bail!(
2434                            "--config argument `{arg}` \
2435                                includes non-whitespace decoration"
2436                        )
2437                    }
2438                    table = nt;
2439                }
2440                Item::Value(v) if v.is_inline_table() => {
2441                    bail!(
2442                        "--config argument `{arg}` \
2443                        sets a value to an inline table, which is not accepted"
2444                    );
2445                }
2446                Item::Value(v) => {
2447                    if table
2448                        .key(k)
2449                        .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2450                        || non_empty_decor(v.decor())
2451                    {
2452                        bail!(
2453                            "--config argument `{arg}` \
2454                                includes non-whitespace decoration"
2455                        )
2456                    }
2457                    got_to_value = true;
2458                    break;
2459                }
2460                Item::ArrayOfTables(_) => {
2461                    bail!(
2462                        "--config argument `{arg}` \
2463                        sets a value to an array of tables, which is not accepted"
2464                    );
2465                }
2466
2467                Item::None => {
2468                    bail!("--config argument `{arg}` doesn't provide a value")
2469                }
2470            }
2471        }
2472        got_to_value
2473    };
2474    if !ok {
2475        bail!(
2476            "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2477        );
2478    }
2479    Ok(doc)
2480}
2481
2482/// A type to deserialize a list of strings from a toml file.
2483///
2484/// Supports deserializing either a whitespace-separated list of arguments in a
2485/// single string or a string list itself. For example these deserialize to
2486/// equivalent values:
2487///
2488/// ```toml
2489/// a = 'a b c'
2490/// b = ['a', 'b', 'c']
2491/// ```
2492#[derive(Debug, Deserialize, Clone)]
2493pub struct StringList(Vec<String>);
2494
2495impl StringList {
2496    pub fn as_slice(&self) -> &[String] {
2497        &self.0
2498    }
2499}
2500
2501#[macro_export]
2502macro_rules! __shell_print {
2503    ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2504        let mut shell = $config.shell();
2505        let out = shell.$which();
2506        drop(out.write_fmt(format_args!($($arg)*)));
2507        if $newline {
2508            drop(out.write_all(b"\n"));
2509        }
2510    });
2511}
2512
2513#[macro_export]
2514macro_rules! drop_println {
2515    ($config:expr) => ( $crate::drop_print!($config, "\n") );
2516    ($config:expr, $($arg:tt)*) => (
2517        $crate::__shell_print!($config, out, true, $($arg)*)
2518    );
2519}
2520
2521#[macro_export]
2522macro_rules! drop_eprintln {
2523    ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2524    ($config:expr, $($arg:tt)*) => (
2525        $crate::__shell_print!($config, err, true, $($arg)*)
2526    );
2527}
2528
2529#[macro_export]
2530macro_rules! drop_print {
2531    ($config:expr, $($arg:tt)*) => (
2532        $crate::__shell_print!($config, out, false, $($arg)*)
2533    );
2534}
2535
2536#[macro_export]
2537macro_rules! drop_eprint {
2538    ($config:expr, $($arg:tt)*) => (
2539        $crate::__shell_print!($config, err, false, $($arg)*)
2540    );
2541}
2542
2543enum Tool {
2544    Rustc,
2545    Rustdoc,
2546}
2547
2548impl Tool {
2549    fn as_str(&self) -> &str {
2550        match self {
2551            Tool::Rustc => "rustc",
2552            Tool::Rustdoc => "rustdoc",
2553        }
2554    }
2555}
2556
2557/// Disable HTTP/2 multiplexing for some broken versions of libcurl.
2558///
2559/// In certain versions of libcurl when proxy is in use with HTTP/2
2560/// multiplexing, connections will continue stacking up. This was
2561/// fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc
2562///
2563/// However, Cargo can still link against old system libcurl if it is from a
2564/// custom built one or on macOS. For those cases, multiplexing needs to be
2565/// disabled when those versions are detected.
2566fn disables_multiplexing_for_bad_curl(
2567    curl_version: &str,
2568    http: &mut CargoHttpConfig,
2569    gctx: &GlobalContext,
2570) {
2571    use crate::util::network;
2572
2573    if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2574        let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2575        if bad_curl_versions
2576            .iter()
2577            .any(|v| curl_version.starts_with(v))
2578        {
2579            tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2580            http.multiplexing = Some(false);
2581        }
2582    }
2583}
2584
2585#[cfg(test)]
2586mod tests {
2587    use super::CargoHttpConfig;
2588    use super::GlobalContext;
2589    use super::Shell;
2590    use super::disables_multiplexing_for_bad_curl;
2591
2592    #[test]
2593    fn disables_multiplexing() {
2594        let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2595        gctx.set_search_stop_path(std::path::PathBuf::new());
2596        gctx.set_env(Default::default());
2597
2598        let mut http = CargoHttpConfig::default();
2599        http.proxy = Some("127.0.0.1:3128".into());
2600        disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2601        assert_eq!(http.multiplexing, Some(false));
2602
2603        let cases = [
2604            (None, None, "7.87.0", None),
2605            (None, None, "7.88.0", None),
2606            (None, None, "7.88.1", None),
2607            (None, None, "8.0.0", None),
2608            (Some("".into()), None, "7.87.0", Some(false)),
2609            (Some("".into()), None, "7.88.0", Some(false)),
2610            (Some("".into()), None, "7.88.1", Some(false)),
2611            (Some("".into()), None, "8.0.0", None),
2612            (Some("".into()), Some(false), "7.87.0", Some(false)),
2613            (Some("".into()), Some(false), "7.88.0", Some(false)),
2614            (Some("".into()), Some(false), "7.88.1", Some(false)),
2615            (Some("".into()), Some(false), "8.0.0", Some(false)),
2616        ];
2617
2618        for (proxy, multiplexing, curl_v, result) in cases {
2619            let mut http = CargoHttpConfig {
2620                multiplexing,
2621                proxy,
2622                ..Default::default()
2623            };
2624            disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2625            assert_eq!(http.multiplexing, result);
2626        }
2627    }
2628
2629    #[test]
2630    fn sync_context() {
2631        fn assert_sync<S: Sync>() {}
2632        assert_sync::<GlobalContext>();
2633    }
2634}