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