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