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