Skip to main content

cargo/context/
mod.rs

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