cargo/util/context/
mod.rs

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