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