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