#
cargo/util/context/
mod.rs

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