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