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