Skip to main content

cargo/util/context/
mod.rs

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