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