cargo/util/context/
mod.rs

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