Skip to main content

cargo/util/context/
mod.rs

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