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