cargo/util/context/
mod.rs

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