Skip to main content

cargo/context/
mod.rs

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