Skip to main content

cargo/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`]: 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`],
19//!   but also supports a list 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//! # Config schemas
24//!
25//! Configuration schemas are defined in the [`crate::config::schema`] module.
26//!
27//! ## Config deserialization
28//!
29//! Cargo uses a two-layer deserialization approach:
30//!
31//! 1. **External sources → `ConfigValue`** ---
32//!    Configuration files, environment variables, and CLI `--config` arguments
33//!    are parsed into [`ConfigValue`] instances via [`ConfigValue::from_toml`].
34//!    These parsed results are stored in [`GlobalContext`].
35//!
36//! 2. **`ConfigValue` → Target types** ---
37//!    The [`GlobalContext::get`] method uses a custom Serde deserializer
38//!    to convert [`ConfigValue`] instances to the caller's desired type.
39//!    Precedence between [`ConfigValue`] sources is resolved during retrieval
40//!    based on [`Definition`] priority.
41//!    See [`ConfigReader::get`] for the typed lookup API.
42//!
43//! ## Map key recommendations
44//!
45//! Handling tables that have arbitrary keys can be tricky, particularly if it
46//! should support environment variables. In general, if possible, the caller
47//! should pass the full key path into the `get()` method so that the config
48//! deserializer can properly handle environment variables (which need to be
49//! uppercased, and dashes converted to underscores).
50//!
51//! A good example is the `[target]` table. The code will request
52//! `target.$TUPLE` and the config system can then appropriately fetch
53//! environment variables like `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER`.
54//! Conversely, it is not possible do the same thing for the `cfg()` target
55//! tables (because Cargo must fetch all of them), so those do not support
56//! environment variables.
57//!
58//! Try to avoid keys that are a prefix of another with a dash/underscore. For
59//! example `build.target` and `build.target-dir`. This is OK if these are not
60//! structs/maps, but if it is a struct or map, then it will not be able to
61//! read the environment variable due to ambiguity. (See `ConfigMapAccess` for
62//! more details.)
63
64use cargo_util::collections::{HashMap, HashSet};
65use std::borrow::Cow;
66use std::env;
67use std::ffi::{OsStr, OsString};
68use std::fs::File;
69use std::io::SeekFrom;
70use std::io::prelude::*;
71use std::path::{Path, PathBuf};
72use std::sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock};
73use std::time::Instant;
74
75use self::ConfigValue as CV;
76use crate::ops::RegistryCredentialConfig;
77use crate::sources::CRATES_IO_INDEX;
78use crate::sources::CRATES_IO_REGISTRY;
79use crate::util::OnceExt as _;
80use crate::util::VersionReqMatchMode;
81use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
82use crate::util::errors::CargoResult;
83use crate::util::network::http::{HandleConfiguration, configure_http_handle, http_handle};
84use crate::util::network::http_async;
85use crate::util::restricted_names::is_glob_pattern;
86use crate::util::{CanonicalUrl, closest_msg, internal};
87use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
88use crate::workspace::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
89use crate::workspace::{CliUnstable, SourceId, Workspace, WorkspaceRootConfig, features};
90
91use anyhow::{Context as _, anyhow, bail, format_err};
92use cargo_credential::Secret;
93use cargo_util::paths;
94use cargo_util_schemas::manifest::RegistryName;
95use cargo_util_terminal::report::Level;
96use cargo_util_terminal::{Shell, Verbosity};
97use curl::easy::Easy;
98use itertools::Itertools;
99use serde::Deserialize;
100use serde::de::IntoDeserializer as _;
101use time::OffsetDateTime;
102use toml_edit::Item;
103use url::Url;
104
105pub use crate::config::{
106    ConfigError, ConfigKey, ConfigStore, ConfigValue, Definition, DefinitionKind, OptValue, Value,
107};
108use crate::config::{ConfigReader, Env};
109
110mod path;
111pub use path::BracketType;
112pub use path::ConfigRelativePathResolveExt;
113pub use path::ResolveTemplateError;
114
115mod target;
116pub use target::{TargetCfgConfig, TargetConfig};
117
118pub use crate::config::schema::*;
119
120pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
121    "paths",
122    "alias",
123    "build",
124    "credential-alias",
125    "doc",
126    "env",
127    "future-incompat-report",
128    "cache",
129    "cargo-new",
130    "http",
131    "install",
132    "net",
133    "patch",
134    "profile",
135    "resolver",
136    "registries",
137    "registry",
138    "source",
139    "target",
140    "term",
141];
142
143/// A previously generated authentication token and the data needed to determine if it can be reused.
144#[derive(Debug)]
145pub struct CredentialCacheValue {
146    pub token_value: Secret<String>,
147    pub expiration: Option<OffsetDateTime>,
148    pub operation_independent: bool,
149}
150
151/// Configuration information for cargo. This is not specific to a build, it is information
152/// relating to cargo itself.
153#[derive(Debug)]
154pub struct GlobalContext {
155    /// The location of the user's Cargo home directory. OS-dependent.
156    home_path: Filesystem,
157    /// Information about how to write messages to the shell
158    shell: Mutex<Shell>,
159    /// A collection of configuration options
160    config: OnceLock<ConfigStore>,
161    /// A collection of configuration options from the credentials file
162    credentials_config: OnceLock<ConfigStore>,
163    /// CLI config values, passed in via `configure`.
164    cli_config: Option<Vec<String>>,
165    /// The current working directory of cargo
166    cwd: PathBuf,
167    /// Directory where config file searching should stop (inclusive).
168    search_stop_path: Option<PathBuf>,
169    /// The location of the cargo executable (path to current process)
170    cargo_exe: OnceLock<PathBuf>,
171    /// The location of the rustdoc executable
172    rustdoc: OnceLock<PathBuf>,
173    /// Whether we are printing extra verbose messages
174    extra_verbose: bool,
175    /// `frozen` is the same as `locked`, but additionally will not access the
176    /// network to determine if the lock file is out-of-date.
177    frozen: bool,
178    /// `locked` is set if we should not update lock files. If the lock file
179    /// is missing, or needs to be updated, an error is produced.
180    locked: bool,
181    /// `offline` is set if we should never access the network, but otherwise
182    /// continue operating if possible.
183    offline: bool,
184    /// A global static IPC control mechanism (used for managing parallel builds)
185    jobserver: Option<&'static jobserver::Client>,
186    /// Cli flags of the form "-Z something" merged with config file values
187    unstable_flags: CliUnstable,
188    /// Cli flags of the form "-Z something"
189    unstable_flags_cli: Option<Vec<String>>,
190    /// A handle on curl easy mode for http calls
191    easy: OnceLock<Mutex<Easy>>,
192    /// Cache of the `SourceId` for crates.io
193    crates_io_source_id: OnceLock<SourceId>,
194    /// If false, don't cache `rustc --version --verbose` invocations
195    cache_rustc_info: bool,
196    /// Monotonic start of this cargo invocation for reporting time elapsed.
197    invocation_instant: Instant,
198    /// Wall-clock time of this cargo invocation.
199    ///
200    /// Currently used as the reference time for `min-publish-age` and `-Zbuild-analysis`.
201    invocation_time: jiff::Timestamp,
202    /// Target Directory via resolved Cli parameter
203    target_dir: Option<Filesystem>,
204    /// Environment variable snapshot.
205    env: Env,
206    /// Tracks which sources have been updated to avoid multiple updates.
207    updated_sources: Mutex<HashSet<SourceId>>,
208    /// Cache of credentials from configuration or credential providers.
209    /// Maps from url to credential value.
210    credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
211    /// Cache of registry config from the `[registries]` table.
212    registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
213    /// Locks on the package and index caches.
214    package_cache_lock: CacheLocker,
215    /// Cached configuration parsed by Cargo
216    http_config: OnceLock<CargoHttpConfig>,
217    http_async: OnceLock<http_async::Client>,
218    future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
219    net_config: OnceLock<CargoNetConfig>,
220    build_config: OnceLock<CargoBuildConfig>,
221    target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
222    doc_extern_map: OnceLock<RustdocExternMap>,
223    progress_config: ProgressConfig,
224    env_config: OnceLock<Arc<HashMap<String, OsString>>>,
225    /// This should be false if:
226    /// - this is an artifact of the rustc distribution process for "stable" or for "beta"
227    /// - this is an `#[test]` that does not opt in with `enable_nightly_features`
228    /// - this is an integration test that uses `ProcessBuilder`
229    ///      that does not opt in with `masquerade_as_nightly_cargo`
230    /// This should be true if:
231    /// - this is an artifact of the rustc distribution process for "nightly"
232    /// - this is being used in the rustc distribution process internally
233    /// - this is a cargo executable that was built from source
234    /// - this is an `#[test]` that called `enable_nightly_features`
235    /// - this is an integration test that uses `ProcessBuilder`
236    ///       that called `masquerade_as_nightly_cargo`
237    /// It's public to allow tests use nightly features.
238    /// NOTE: this should be set before `configure()`. If calling this from an integration test,
239    /// consider using `ConfigBuilder::enable_nightly_features` instead.
240    pub nightly_features_allowed: bool,
241    /// `WorkspaceRootConfigs` that have been found
242    ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
243    /// The global cache tracker is a database used to track disk cache usage.
244    global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
245    /// A cache of modifications to make to [`GlobalContext::global_cache_tracker`],
246    /// saved to disk in a batch to improve performance.
247    deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
248}
249
250impl GlobalContext {
251    /// Creates a new config instance.
252    ///
253    /// This is typically used for tests or other special cases. `default` is
254    /// preferred otherwise.
255    ///
256    /// This does only minimal initialization. In particular, it does not load
257    /// any config files from disk. Those will be loaded lazily as-needed.
258    pub fn new(mut shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
259        static GLOBAL_JOBSERVER: LazyLock<CargoResult<Option<jobserver::Client>>> = LazyLock::new(
260            || {
261                use jobserver::FromEnvErrorKind;
262                // Note that this is unsafe because it may misinterpret file descriptors
263                // on Unix as jobserver file descriptors. We hopefully execute this near
264                // the beginning of the process though to ensure we don't get false
265                // positives, or in other words we try to execute this before we open
266                // any file descriptors ourselves.
267                let jobserver::FromEnv { client, var } =
268                    unsafe { jobserver::Client::from_env_ext(true) };
269
270                match client {
271                    Ok(client) => return Ok(Some(client)),
272                    Err(e)
273                        if matches!(
274                            e.kind(),
275                            FromEnvErrorKind::NoEnvVar
276                                | FromEnvErrorKind::NoJobserver
277                                | FromEnvErrorKind::NegativeFd
278                                | FromEnvErrorKind::Unsupported
279                        ) =>
280                    {
281                        Ok(None)
282                    }
283                    Err(e) => {
284                        let (name, value) = var.unwrap();
285                        Err(anyhow::anyhow!(
286                            "failed to connect to jobserver from environment variable `{name}={value:?}`: {e}"
287                        ))
288                    }
289                }
290            },
291        );
292        let jobserver = match &*GLOBAL_JOBSERVER {
293            Ok(jobserver) => jobserver.as_ref(),
294            Err(e) => {
295                let _ = shell.warn(e);
296                None
297            }
298        };
299
300        let env = Env::new();
301
302        let cache_key = "CARGO_CACHE_RUSTC_INFO";
303        let cache_rustc_info = match env.get_env_os(cache_key) {
304            Some(cache) => cache != "0",
305            _ => true,
306        };
307
308        #[expect(
309            clippy::disallowed_methods,
310            reason = "testing only, no reason for config support"
311        )]
312        let invocation_time = match env::var("__CARGO_TEST_INVOCATION_TIME") {
313            Ok(now) => now.parse().unwrap(),
314            Err(_) => jiff::Timestamp::now(),
315        };
316
317        GlobalContext {
318            home_path: Filesystem::new(homedir),
319            shell: Mutex::new(shell),
320            cwd,
321            search_stop_path: None,
322            config: Default::default(),
323            credentials_config: Default::default(),
324            cli_config: None,
325            cargo_exe: Default::default(),
326            rustdoc: Default::default(),
327            extra_verbose: false,
328            frozen: false,
329            locked: false,
330            offline: false,
331            jobserver,
332            unstable_flags: CliUnstable::default(),
333            unstable_flags_cli: None,
334            easy: Default::default(),
335            crates_io_source_id: Default::default(),
336            cache_rustc_info,
337            invocation_instant: Instant::now(),
338            invocation_time,
339            target_dir: None,
340            env,
341            updated_sources: Default::default(),
342            credential_cache: Default::default(),
343            registry_config: Default::default(),
344            package_cache_lock: CacheLocker::new(),
345            http_config: Default::default(),
346            http_async: Default::default(),
347            future_incompat_config: Default::default(),
348            net_config: Default::default(),
349            build_config: Default::default(),
350            target_cfgs: Default::default(),
351            doc_extern_map: Default::default(),
352            progress_config: ProgressConfig::default(),
353            env_config: Default::default(),
354            nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
355            ws_roots: Default::default(),
356            global_cache_tracker: Default::default(),
357            deferred_global_last_use: Default::default(),
358        }
359    }
360
361    /// Creates a new instance, with all default settings.
362    ///
363    /// This does only minimal initialization. In particular, it does not load
364    /// any config files from disk. Those will be loaded lazily as-needed.
365    pub fn default() -> CargoResult<GlobalContext> {
366        let shell = Shell::new();
367        let cwd =
368            env::current_dir().context("couldn't get the current directory of the process")?;
369        let homedir = homedir(&cwd).ok_or_else(|| {
370            anyhow!(
371                "Cargo couldn't find your home directory. \
372                 This probably means that $HOME was not set."
373            )
374        })?;
375        Ok(GlobalContext::new(shell, cwd, homedir))
376    }
377
378    /// Gets the user's Cargo home directory (OS-dependent).
379    pub fn home(&self) -> &Filesystem {
380        &self.home_path
381    }
382
383    /// Returns a path to display to the user with the location of their home
384    /// config file (to only be used for displaying a diagnostics suggestion,
385    /// such as recommending where to add a config value).
386    pub fn diagnostic_home_config(&self) -> String {
387        let home = self.home_path.as_path_unlocked();
388        let path = match self.get_file_path(home, "config", false) {
389            Ok(Some(existing_path)) => existing_path,
390            _ => home.join("config.toml"),
391        };
392        path.to_string_lossy().to_string()
393    }
394
395    /// Gets the Cargo Git directory (`<cargo_home>/git`).
396    pub fn git_path(&self) -> Filesystem {
397        self.home_path.join("git")
398    }
399
400    /// Gets the directory of code sources Cargo checkouts from Git bare repos
401    /// (`<cargo_home>/git/checkouts`).
402    pub fn git_checkouts_path(&self) -> Filesystem {
403        self.git_path().join("checkouts")
404    }
405
406    /// Gets the directory for all Git bare repos Cargo clones
407    /// (`<cargo_home>/git/db`).
408    pub fn git_db_path(&self) -> Filesystem {
409        self.git_path().join("db")
410    }
411
412    /// Gets the Cargo base directory for all registry information (`<cargo_home>/registry`).
413    pub fn registry_base_path(&self) -> Filesystem {
414        self.home_path.join("registry")
415    }
416
417    /// Gets the Cargo registry index directory (`<cargo_home>/registry/index`).
418    pub fn registry_index_path(&self) -> Filesystem {
419        self.registry_base_path().join("index")
420    }
421
422    /// Gets the Cargo registry cache directory (`<cargo_home>/registry/cache`).
423    pub fn registry_cache_path(&self) -> Filesystem {
424        self.registry_base_path().join("cache")
425    }
426
427    /// Gets the Cargo registry source directory (`<cargo_home>/registry/src`).
428    pub fn registry_source_path(&self) -> Filesystem {
429        self.registry_base_path().join("src")
430    }
431
432    /// Gets the default Cargo registry.
433    pub fn default_registry(&self) -> CargoResult<Option<String>> {
434        Ok(self
435            .get_string(["registry", "default"])?
436            .map(|registry| registry.val))
437    }
438
439    /// Gets a reference to the shell, e.g., for writing error messages.
440    pub fn shell(&self) -> MutexGuard<'_, Shell> {
441        self.shell.lock().unwrap()
442    }
443
444    /// Assert [`Self::shell`] is not in use
445    ///
446    /// Testing might not identify bugs with two accesses to `shell` at once
447    /// due to conditional logic,
448    /// so place this outside of the conditions to catch these bugs in more situations.
449    pub fn debug_assert_shell_not_borrowed(&self) {
450        if cfg!(debug_assertions) {
451            match self.shell.try_lock() {
452                Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
453                Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
454            }
455        }
456    }
457
458    /// Gets the path to the `rustdoc` executable.
459    pub fn rustdoc(&self) -> CargoResult<&Path> {
460        self.rustdoc
461            .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
462            .map(AsRef::as_ref)
463    }
464
465    /// Gets the path to the `rustc` executable.
466    pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
467        let cache_location =
468            ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
469        let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
470        let rustc_workspace_wrapper = self.maybe_get_tool(
471            "rustc_workspace_wrapper",
472            &self.build_config()?.rustc_workspace_wrapper,
473        );
474
475        Rustc::new(
476            self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
477            wrapper,
478            rustc_workspace_wrapper,
479            &self
480                .home()
481                .join("bin")
482                .join("rustc")
483                .into_path_unlocked()
484                .with_extension(env::consts::EXE_EXTENSION),
485            if self.cache_rustc_info {
486                cache_location
487            } else {
488                None
489            },
490            self,
491        )
492    }
493
494    /// Gets the path to the `cargo` executable.
495    pub fn cargo_exe(&self) -> CargoResult<&Path> {
496        self.cargo_exe
497            .try_borrow_with(|| {
498                let from_env = || -> CargoResult<PathBuf> {
499                    // Try re-using the `cargo` set in the environment already. This allows
500                    // commands that use Cargo as a library to inherit (via `cargo <subcommand>`)
501                    // or set (by setting `$CARGO`) a correct path to `cargo` when the current exe
502                    // is not actually cargo (e.g., `cargo-*` binaries, Valgrind, `ld.so`, etc.).
503                    let exe = self
504                        .get_env_os(crate::CARGO_ENV)
505                        .map(PathBuf::from)
506                        .ok_or_else(|| anyhow!("$CARGO not set"))?;
507                    Ok(exe)
508                };
509
510                fn from_current_exe() -> CargoResult<PathBuf> {
511                    // Try fetching the path to `cargo` using `env::current_exe()`.
512                    // The method varies per operating system and might fail; in particular,
513                    // it depends on `/proc` being mounted on Linux, and some environments
514                    // (like containers or chroots) may not have that available.
515                    let exe = env::current_exe()?;
516                    Ok(exe)
517                }
518
519                fn from_argv() -> CargoResult<PathBuf> {
520                    // Grab `argv[0]` and attempt to resolve it to an absolute path.
521                    // If `argv[0]` has one component, it must have come from a `PATH` lookup,
522                    // so probe `PATH` in that case.
523                    // Otherwise, it has multiple components and is either:
524                    // - a relative path (e.g., `./cargo`, `target/debug/cargo`), or
525                    // - an absolute path (e.g., `/usr/local/bin/cargo`).
526                    let argv0 = env::args_os()
527                        .map(PathBuf::from)
528                        .next()
529                        .ok_or_else(|| anyhow!("no argv[0]"))?;
530                    paths::resolve_executable(&argv0)
531                }
532
533                // Determines whether `path` is a cargo binary.
534                // See: https://github.com/rust-lang/cargo/issues/15099#issuecomment-2666737150
535                fn is_cargo(path: &Path) -> bool {
536                    path.file_stem() == Some(OsStr::new("cargo"))
537                }
538
539                let from_current_exe = from_current_exe();
540                if from_current_exe.as_deref().is_ok_and(is_cargo) {
541                    return from_current_exe;
542                }
543
544                let from_argv = from_argv();
545                if from_argv.as_deref().is_ok_and(is_cargo) {
546                    return from_argv;
547                }
548
549                let exe = from_env()
550                    .or(from_current_exe)
551                    .or(from_argv)
552                    .context("couldn't get the path to cargo executable")?;
553                Ok(exe)
554            })
555            .map(AsRef::as_ref)
556    }
557
558    /// Which package sources have been updated, used to ensure it is only done once.
559    pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
560        self.updated_sources.lock().unwrap()
561    }
562
563    /// Cached credentials from credential providers or configuration.
564    pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
565        self.credential_cache.lock().unwrap()
566    }
567
568    /// Gets the configuration for a remote registry.
569    ///
570    /// The registry name is resolved from its index URL. This may emit a note if
571    /// the resolved name differs from the source's configured name. Returns
572    /// `None` if no configuration is found.
573    ///
574    /// **Note:** this does not load tokens from `credentials.toml`, see instead
575    /// [`Self::registry_credentials`].
576    pub fn registry_config(&self, sid: &SourceId) -> CargoResult<Option<RegistryConfig>> {
577        let mut cache = self.registry_config.lock().unwrap();
578        if let Some(cfg) = cache.get(sid) {
579            return Ok(cfg.clone());
580        }
581        let cfg = registry_config_uncached(self, sid)?;
582        cache.insert(*sid, cfg.clone());
583        Ok(cfg)
584    }
585
586    /// Gets the credentials configured for a remote registry.
587    ///
588    /// This loads credentials if needed and resolves the registry name in the
589    /// same way as [`GlobalContext::registry_config`].
590    pub fn registry_credentials(&self, sid: &SourceId) -> CargoResult<Option<RegistryCredential>> {
591        let credentials = self.credentials_config()?;
592        let mut key = if sid.is_crates_io() {
593            ConfigKey::from_parts(["registry"])
594        } else if let Some(name) = registry_name(self, sid)? {
595            ConfigKey::from_parts(["registries", &name])
596        } else {
597            return Ok(None);
598        };
599        let has_credentials = credentials.get_cv(&key)?.is_some();
600        key.push("token");
601        let credentials_token = match credentials.get_cv(&key)? {
602            Some(CV::String(val, definition)) => Some(Value {
603                val: Secret::from(val),
604                definition,
605            }),
606            Some(cv) => return Err(ConfigError::expected(&key, "a string", &cv).into()),
607            None => None,
608        };
609
610        let config = self.registry_config(sid)?;
611        let has_config = config.is_some();
612        let config_token = config.and_then(|config| {
613            #[expect(deprecated)]
614            config.token
615        });
616
617        let token = match (config_token, credentials_token) {
618            (Some(config_token), Some(credentials_token))
619                if config_token
620                    .definition
621                    .is_higher_priority(&credentials_token.definition) =>
622            {
623                Some(config_token)
624            }
625            (_, Some(credentials_token)) => Some(credentials_token),
626            (config_token, None) => config_token,
627        };
628
629        Ok((has_config || has_credentials).then_some(RegistryCredential { token }))
630    }
631
632    /// Gets all config values from disk.
633    ///
634    /// This will lazy-load the values as necessary. Callers are responsible
635    /// for checking environment variables. Callers outside of the `config`
636    /// module should avoid using this.
637    pub fn config(&self) -> CargoResult<&ConfigStore> {
638        self.config.try_borrow_with(|| self.load_config())
639    }
640
641    /// Gets a mutable copy of the on-disk config values.
642    ///
643    /// This requires the config values to already have been loaded. This
644    /// currently only exists for `cargo vendor` to remove the `source`
645    /// entries. This doesn't respect environment variables. You should avoid
646    /// using this if possible.
647    pub fn config_mut(&mut self) -> CargoResult<&mut ConfigStore> {
648        let _ = self.config()?;
649        Ok(self.config.get_mut().expect("already loaded config values"))
650    }
651
652    /// Sets the path where ancestor config file searching will stop. The
653    /// given path is included, but its ancestors are not.
654    pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
655        let path = path.into();
656        debug_assert!(self.cwd.starts_with(&path));
657        self.search_stop_path = Some(path);
658    }
659
660    /// Switches the working directory to [`std::env::current_dir`]
661    ///
662    /// There is not a need to also call [`Self::reload_rooted_at`].
663    pub fn reload_cwd(&mut self) -> CargoResult<()> {
664        let cwd =
665            env::current_dir().context("couldn't get the current directory of the process")?;
666        let homedir = homedir(&cwd).ok_or_else(|| {
667            anyhow!(
668                "Cargo couldn't find your home directory. \
669                 This probably means that $HOME was not set."
670            )
671        })?;
672
673        self.cwd = cwd;
674        self.home_path = Filesystem::new(homedir);
675        self.reload_rooted_at(self.cwd.clone())?;
676        Ok(())
677    }
678
679    /// Reloads on-disk configuration values, starting at the given path and
680    /// walking up its ancestors.
681    pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
682        let config = self.load_config_from(path.as_ref())?;
683        self.config.replace(config);
684        self.merge_cli_args()?;
685        self.load_unstable_flags_from_config()?;
686        Ok(())
687    }
688
689    /// The current working directory.
690    pub fn cwd(&self) -> &Path {
691        &self.cwd
692    }
693
694    /// The `target` output directory to use.
695    ///
696    /// Returns `None` if the user has not chosen an explicit directory.
697    ///
698    /// Callers should prefer [`Workspace::target_dir`] instead.
699    pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
700        if let Some(dir) = &self.target_dir {
701            Ok(Some(dir.clone()))
702        } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
703            // Check if the CARGO_TARGET_DIR environment variable is set to an empty string.
704            if dir.is_empty() {
705                bail!(
706                    "the target directory is set to an empty string in the \
707                     `CARGO_TARGET_DIR` environment variable"
708                )
709            }
710
711            Ok(Some(Filesystem::new(self.cwd.join(dir))))
712        } else if let Some(val) = &self.build_config()?.target_dir {
713            let path = val.resolve_path(self);
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 target directory is set to an empty string in {}",
719                    val.value().definition
720                )
721            }
722
723            Ok(Some(Filesystem::new(path)))
724        } else {
725            Ok(None)
726        }
727    }
728
729    /// The directory to use for intermediate build artifacts.
730    ///
731    /// Callers should prefer [`Workspace::build_dir`] instead.
732    pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
733        let Some(val) = &self.build_config()?.build_dir else {
734            return Ok(None);
735        };
736        self.custom_build_dir(val, workspace_manifest_path)
737            .map(Some)
738    }
739
740    /// The directory to use for intermediate build artifacts.
741    ///
742    /// Callers should prefer [`Workspace::build_dir`] instead.
743    pub fn custom_build_dir(
744        &self,
745        val: &ConfigRelativePath,
746        workspace_manifest_path: &Path,
747    ) -> CargoResult<Filesystem> {
748        let replacements = [
749            (
750                "{workspace-root}",
751                workspace_manifest_path
752                    .parent()
753                    .unwrap()
754                    .to_str()
755                    .context("workspace root was not valid utf-8")?
756                    .to_string(),
757            ),
758            (
759                "{cargo-cache-home}",
760                self.home()
761                    .as_path_unlocked()
762                    .to_str()
763                    .context("cargo home was not valid utf-8")?
764                    .to_string(),
765            ),
766            ("{workspace-path-hash}", {
767                let real_path = std::fs::canonicalize(workspace_manifest_path)
768                    .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
769                let hash = crate::util::hex::short_hash(&real_path);
770                format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
771            }),
772        ];
773
774        let template_variables = replacements
775            .iter()
776            .map(|(key, _)| key[1..key.len() - 1].to_string())
777            .collect_vec();
778
779        let path = val
780            .resolve_templated_path(self, replacements)
781            .map_err(|e| match e {
782                path::ResolveTemplateError::UnexpectedVariable {
783                    variable,
784                    raw_template,
785                } => {
786                    let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
787                    if suggestion == "" {
788                        let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
789                        suggestion = format!("\n\nhelp: available template variables are {variables}");
790                    }
791                    anyhow!(
792                            "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
793                        )
794                }
795                path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
796                    let (btype, literal) = match bracket_type {
797                        path::BracketType::Opening => ("opening", "{"),
798                        path::BracketType::Closing => ("closing", "}"),
799                    };
800
801                    anyhow!(
802                            "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
803                        )
804                }
805            })?;
806
807        // Check if the target directory is set to an empty string in the config.toml file.
808        if val.raw_value().is_empty() {
809            bail!(
810                "the build directory is set to an empty string in {}",
811                val.value().definition
812            )
813        }
814
815        Ok(Filesystem::new(path))
816    }
817
818    fn config_reader(&self) -> CargoResult<ConfigReader<'_>> {
819        Ok(ConfigReader {
820            store: self.config()?,
821            shell: &self.shell,
822            env: &self.env,
823            advanced_env: self.cli_unstable().advanced_env,
824        })
825    }
826
827    pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
828        self.config_reader()?.get_cv_with_env(key)
829    }
830
831    /// Helper primarily for testing.
832    pub fn set_env(&mut self, env: HashMap<String, String>) {
833        self.env = Env::from_map(env);
834    }
835
836    /// Returns all environment variables as an iterator,
837    /// keeping only entries where both the key and value are valid UTF-8.
838    pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
839        self.env.iter_str()
840    }
841
842    /// Get the value of environment variable `key` through the snapshot in
843    /// [`GlobalContext`].
844    ///
845    /// This can be used similarly to [`std::env::var`].
846    pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
847        self.env.get_env(key)
848    }
849
850    /// Get the value of environment variable `key` through the snapshot in
851    /// [`GlobalContext`].
852    ///
853    /// This can be used similarly to [`std::env::var_os`].
854    pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
855        self.env.get_env_os(key)
856    }
857
858    /// Get a string config value.
859    ///
860    /// See `get` for more details.
861    pub fn get_string(
862        &self,
863        key: impl IntoIterator<Item = impl AsRef<str>>,
864    ) -> CargoResult<OptValue<String>> {
865        self.get::<OptValue<String>>(key)
866    }
867
868    fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
869        let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
870        if is_path {
871            definition.root(self.cwd()).join(value)
872        } else {
873            // A pathless name.
874            PathBuf::from(value)
875        }
876    }
877
878    /// Update the instance based on settings typically passed in on
879    /// the command-line.
880    ///
881    /// This may also load the config from disk if it hasn't already been
882    /// loaded.
883    pub fn configure(
884        &mut self,
885        verbose: u32,
886        quiet: bool,
887        color: Option<&str>,
888        frozen: bool,
889        locked: bool,
890        offline: bool,
891        target_dir: &Option<PathBuf>,
892        unstable_flags: &[String],
893        cli_config: &[String],
894    ) -> CargoResult<()> {
895        for warning in self
896            .unstable_flags
897            .parse(unstable_flags, self.nightly_features_allowed)?
898        {
899            self.shell().warn(warning)?;
900        }
901        if !unstable_flags.is_empty() {
902            // store a copy of the cli flags separately for `load_unstable_flags_from_config`
903            // (we might also need it again for `reload_rooted_at`)
904            self.unstable_flags_cli = Some(unstable_flags.to_vec());
905        }
906        if !cli_config.is_empty() {
907            self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
908            self.merge_cli_args()?;
909        }
910
911        self.load_unstable_flags_from_config()?;
912
913        // Ignore errors in the configuration files. We don't want basic
914        // commands like `cargo version` to error out due to config file
915        // problems.
916        let term = self.get::<TermConfig>(["term"]).unwrap_or_default();
917
918        // The command line takes precedence over configuration.
919        let extra_verbose = verbose >= 2;
920        let verbose = verbose != 0;
921        let verbosity = match (verbose, quiet) {
922            (true, true) => bail!("cannot set both --verbose and --quiet"),
923            (true, false) => Verbosity::Verbose,
924            (false, true) => Verbosity::Quiet,
925            (false, false) => match (term.verbose, term.quiet) {
926                (Some(true), Some(true)) => {
927                    bail!("cannot set both `term.verbose` and `term.quiet`")
928                }
929                (Some(true), _) => Verbosity::Verbose,
930                (_, Some(true)) => Verbosity::Quiet,
931                _ => Verbosity::Normal,
932            },
933        };
934        self.shell().set_verbosity(verbosity);
935        self.extra_verbose = extra_verbose;
936
937        let color = color.or_else(|| term.color.as_deref());
938        self.shell().set_color_choice(color)?;
939        if let Some(hyperlinks) = term.hyperlinks {
940            self.shell().set_hyperlinks(hyperlinks)?;
941        }
942        if let Some(unicode) = term.unicode {
943            self.shell().set_unicode(unicode)?;
944        }
945
946        self.progress_config = term.progress.unwrap_or_default();
947
948        self.frozen = frozen;
949        self.locked = locked;
950        self.offline = offline
951            || self
952                .net_config()
953                .ok()
954                .and_then(|n| n.offline)
955                .unwrap_or(false);
956        let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
957        self.target_dir = cli_target_dir;
958
959        self.shell()
960            .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
961
962        Ok(())
963    }
964
965    fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
966        // If nightly features are enabled, allow setting Z-flags from config
967        // using the `unstable` table. Ignore that block otherwise.
968        if self.nightly_features_allowed {
969            self.unstable_flags = self
970                .get::<Option<CliUnstable>>(["unstable"])?
971                .unwrap_or_default();
972            if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
973                // NB. It's not ideal to parse these twice, but doing it again here
974                //     allows the CLI to override config files for both enabling
975                //     and disabling, and doing it up top allows CLI Zflags to
976                //     control config parsing behavior.
977                self.unstable_flags.parse(unstable_flags_cli, true)?;
978            }
979        }
980
981        Ok(())
982    }
983
984    pub fn cli_unstable(&self) -> &CliUnstable {
985        &self.unstable_flags
986    }
987
988    pub fn extra_verbose(&self) -> bool {
989        self.extra_verbose
990    }
991
992    pub fn network_allowed(&self) -> bool {
993        !self.offline_flag().is_some()
994    }
995
996    pub fn offline_flag(&self) -> Option<&'static str> {
997        if self.frozen {
998            Some("--frozen")
999        } else if self.offline {
1000            Some("--offline")
1001        } else {
1002            None
1003        }
1004    }
1005
1006    pub fn set_locked(&mut self, locked: bool) {
1007        self.locked = locked;
1008    }
1009
1010    pub fn lock_update_allowed(&self) -> bool {
1011        !self.locked_flag().is_some()
1012    }
1013
1014    pub fn locked_flag(&self) -> Option<&'static str> {
1015        if self.frozen {
1016            Some("--frozen")
1017        } else if self.locked {
1018            Some("--locked")
1019        } else {
1020            None
1021        }
1022    }
1023
1024    /// Loads configuration from the filesystem.
1025    pub fn load_config(&self) -> CargoResult<ConfigStore> {
1026        self.load_config_from(&self.cwd)
1027    }
1028
1029    /// Like [`load_config`](GlobalContext::load_config) but without merging config values.
1030    ///
1031    /// This is primarily crafted for `cargo config` command.
1032    pub(crate) fn load_config_unmerged(&self) -> CargoResult<Vec<ConfigStore>> {
1033        let mut result = Vec::new();
1034        let mut seen = HashSet::default();
1035        let home = self.home_path.clone().into_path_unlocked();
1036        self.walk_tree(&self.cwd, &home, |path| {
1037            let mut config = self._load_file(path, &mut seen, false, DefinitionKind::Path)?;
1038            self.load_unmerged_include(&mut config, &mut seen, &mut result)?;
1039            result.push(config);
1040            Ok(())
1041        })
1042        .context("could not load Cargo configuration")?;
1043        Ok(result)
1044    }
1045
1046    /// Like [`load_includes`](GlobalContext::load_includes) but without merging config values.
1047    ///
1048    /// This is primarily crafted for `cargo config` command.
1049    fn load_unmerged_include(
1050        &self,
1051        config: &mut ConfigStore,
1052        seen: &mut HashSet<PathBuf>,
1053        output: &mut Vec<ConfigStore>,
1054    ) -> CargoResult<()> {
1055        let includes = self.include_paths(config, false)?;
1056        for include in includes {
1057            let Some(abs_path) = include.resolve_path(self) else {
1058                continue;
1059            };
1060
1061            let mut config = self
1062                ._load_file(&abs_path, seen, false, DefinitionKind::Path)
1063                .with_context(|| {
1064                    format!(
1065                        "failed to load config include `{}` from `{}`",
1066                        include.path.display(),
1067                        include.def
1068                    )
1069                })?;
1070            self.load_unmerged_include(&mut config, seen, output)?;
1071            output.push(config);
1072        }
1073        Ok(())
1074    }
1075
1076    /// Start a config file discovery from a path and merges all config values found.
1077    fn load_config_from(&self, path: &Path) -> CargoResult<ConfigStore> {
1078        let mut cfg = ConfigStore::default();
1079        let home = self.home_path.clone().into_path_unlocked();
1080
1081        self.walk_tree(path, &home, |path| {
1082            let config = self.load_file(path)?;
1083            cfg.merge(config, false).with_context(|| {
1084                format!("failed to merge configuration at `{}`", path.display())
1085            })?;
1086            Ok(())
1087        })
1088        .context("could not load Cargo configuration")?;
1089
1090        Ok(cfg)
1091    }
1092
1093    /// Loads configuration from a path.
1094    ///
1095    /// This is used during config file discovery.
1096    fn load_file(&self, path: &Path) -> CargoResult<ConfigStore> {
1097        self._load_file(path, &mut HashSet::default(), true, DefinitionKind::Path)
1098    }
1099
1100    /// Loads configuration from a path with options.
1101    ///
1102    /// This is the implementation of loading configuration from a path.
1103    ///
1104    /// * `includes` determines whether to load configs from [`ConfigInclude`].
1105    /// * `seen` is used to check for cyclic includes.
1106    /// * `definition_kind` tells why a config is being loaded.
1107    fn _load_file(
1108        &self,
1109        path: &Path,
1110        seen: &mut HashSet<PathBuf>,
1111        includes: bool,
1112        definition_kind: DefinitionKind,
1113    ) -> CargoResult<ConfigStore> {
1114        if !seen.insert(path.to_path_buf()) {
1115            bail!(
1116                "config `include` cycle detected with path `{}`",
1117                path.display()
1118            );
1119        }
1120        tracing::debug!(?path, ?definition_kind, includes, "load config from file");
1121
1122        let config = ConfigStore::read_layer(path, definition_kind)?;
1123
1124        if includes {
1125            self.load_includes(config, seen, definition_kind)
1126        } else {
1127            Ok(config)
1128        }
1129    }
1130
1131    /// Load any `include` files listed in the given `config`.
1132    ///
1133    /// Returns `config` with the given include files merged into it.
1134    ///
1135    /// * `seen` is used to check for cyclic includes.
1136    /// * `definition_kind` tells why a config is being loaded.
1137    fn load_includes(
1138        &self,
1139        mut config: ConfigStore,
1140        seen: &mut HashSet<PathBuf>,
1141        definition_kind: DefinitionKind,
1142    ) -> CargoResult<ConfigStore> {
1143        // Get the list of files to load.
1144        let includes = self.include_paths(&mut config, true)?;
1145
1146        // Accumulate all values here.
1147        let root = CV::Table(HashMap::default(), config.definition().clone());
1148        let mut root = ConfigStore::with_table_value(root).unwrap();
1149        for include in includes {
1150            let Some(abs_path) = include.resolve_path(self) else {
1151                continue;
1152            };
1153
1154            self._load_file(&abs_path, seen, true, definition_kind)
1155                .and_then(|include| root.merge(include, true))
1156                .with_context(|| {
1157                    format!(
1158                        "failed to load config include `{}` from `{}`",
1159                        include.path.display(),
1160                        include.def
1161                    )
1162                })?;
1163        }
1164        root.merge(config, true)?;
1165        Ok(root)
1166    }
1167
1168    /// Converts the `include` config value to a list of absolute paths.
1169    fn include_paths(
1170        &self,
1171        config: &mut ConfigStore,
1172        remove: bool,
1173    ) -> CargoResult<Vec<ConfigInclude>> {
1174        let table = config.values_mut();
1175        let include = if remove {
1176            table.remove("include").map(Cow::Owned)
1177        } else {
1178            table.get("include").map(Cow::Borrowed)
1179        };
1180        let includes = match include.map(|c| c.into_owned()) {
1181            Some(CV::List(list, _def)) => list
1182                .into_iter()
1183                .enumerate()
1184                .map(|(idx, cv)| match cv {
1185                    CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1186                    CV::Table(mut table, def) => {
1187                        // Extract `include.path`
1188                        let s = match table.remove("path") {
1189                            Some(CV::String(s, _)) => s,
1190                            Some(other) => bail!(
1191                                "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1192                                other.desc()
1193                            ),
1194                            None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1195                        };
1196
1197                        // Extract optional `include.optional` field
1198                        let optional = match table.remove("optional") {
1199                            Some(CV::Boolean(b, _)) => b,
1200                            Some(other) => bail!(
1201                                "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1202                                other.desc()
1203                            ),
1204                            None => false,
1205                        };
1206
1207                        let mut include = ConfigInclude::new(s, def);
1208                        include.optional = optional;
1209                        Ok(include)
1210                    }
1211                    other => bail!(
1212                        "expected a string or table, but found {} at `include[{idx}]` in {}",
1213                        other.desc(),
1214                        other.definition(),
1215                    ),
1216                })
1217                .collect::<CargoResult<Vec<_>>>()?,
1218            Some(other) => bail!(
1219                "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1220                other.desc(),
1221                other.definition()
1222            ),
1223            None => {
1224                return Ok(Vec::new());
1225            }
1226        };
1227
1228        for include in &includes {
1229            if include.path.extension() != Some(OsStr::new("toml")) {
1230                bail!(
1231                    "expected a config include path ending with `.toml`, \
1232                     but found `{}` from `{}`",
1233                    include.path.display(),
1234                    include.def,
1235                )
1236            }
1237
1238            if let Some(path) = include.path.to_str() {
1239                // Ignore non UTF-8 bytes as glob and template syntax are for textual config.
1240                if is_glob_pattern(path) {
1241                    bail!(
1242                        "expected a config include path without glob patterns, \
1243                         but found `{}` from `{}`",
1244                        include.path.display(),
1245                        include.def,
1246                    )
1247                }
1248                if path.contains(&['{', '}']) {
1249                    bail!(
1250                        "expected a config include path without template braces, \
1251                         but found `{}` from `{}`",
1252                        include.path.display(),
1253                        include.def,
1254                    )
1255                }
1256            }
1257        }
1258
1259        Ok(includes)
1260    }
1261
1262    /// Parses the CLI config args and returns them as a table.
1263    pub(crate) fn cli_args_to_config(&self) -> CargoResult<ConfigStore> {
1264        let mut loaded_args =
1265            ConfigStore::with_table_value(CV::Table(HashMap::default(), Definition::Cli(None)))
1266                .unwrap();
1267        let Some(cli_args) = &self.cli_config else {
1268            return Ok(loaded_args);
1269        };
1270        let mut seen = HashSet::default();
1271        for arg in cli_args {
1272            let arg_as_path = self.cwd.join(arg);
1273            let arg_config = if !arg.is_empty() && arg_as_path.exists() {
1274                // --config path_to_file
1275                self._load_file(&arg_as_path, &mut seen, true, DefinitionKind::Cli)
1276                    .with_context(|| {
1277                        format!("failed to load config from `{}`", arg_as_path.display())
1278                    })?
1279            } else {
1280                let doc = toml_dotted_keys(arg)?;
1281                let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1282                    .with_context(|| {
1283                        format!("failed to parse value from --config argument `{arg}`")
1284                    })?;
1285
1286                if doc
1287                    .get("registry")
1288                    .and_then(|v| v.as_table())
1289                    .and_then(|t| t.get("token"))
1290                    .is_some()
1291                {
1292                    bail!("registry.token cannot be set through --config for security reasons");
1293                } else if let Some((k, _)) = doc
1294                    .get("registries")
1295                    .and_then(|v| v.as_table())
1296                    .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1297                {
1298                    bail!(
1299                        "registries.{}.token cannot be set through --config for security reasons",
1300                        k
1301                    );
1302                }
1303
1304                let table = CV::from_toml(Definition::Cli(None), doc)
1305                    .with_context(|| format!("failed to convert --config argument `{arg}`"))?;
1306                ConfigStore::with_table_value(table).unwrap()
1307            };
1308            let arg_config = self
1309                .load_includes(arg_config, &mut HashSet::default(), DefinitionKind::Cli)
1310                .context("failed to load --config include".to_string())?;
1311            loaded_args
1312                .merge(arg_config, true)
1313                .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1314        }
1315        Ok(loaded_args)
1316    }
1317
1318    /// Add config arguments passed on the command line.
1319    fn merge_cli_args(&mut self) -> CargoResult<()> {
1320        let cli_config = self.cli_args_to_config()?;
1321        self.config_mut()?.merge(cli_config, true)
1322    }
1323
1324    /// The purpose of this function is to aid in the transition to using
1325    /// .toml extensions on Cargo's config files, which were historically not used.
1326    /// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.
1327    /// When both exist, we want to prefer the one without an extension for
1328    /// backwards compatibility, but warn the user appropriately.
1329    fn get_file_path(
1330        &self,
1331        dir: &Path,
1332        filename_without_extension: &str,
1333        warn: bool,
1334    ) -> CargoResult<Option<PathBuf>> {
1335        let possible = dir.join(filename_without_extension);
1336        let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1337
1338        if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1339            if warn {
1340                if let Ok(possible_with_extension_handle) =
1341                    same_file::Handle::from_path(&possible_with_extension)
1342                {
1343                    // We don't want to print a warning if the version
1344                    // without the extension is just a symlink to the version
1345                    // WITH an extension, which people may want to do to
1346                    // support multiple Cargo versions at once and not
1347                    // get a warning.
1348                    if possible_handle != possible_with_extension_handle {
1349                        self.shell().warn(format!(
1350                            "both `{}` and `{}` exist. Using `{}`",
1351                            possible.display(),
1352                            possible_with_extension.display(),
1353                            possible.display()
1354                        ))?;
1355                    }
1356                } else {
1357                    self.shell().print_report(&[
1358                        Level::WARNING.secondary_title(
1359                            format!(
1360                                "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1361                                possible.display(),
1362                            )).element(Level::HELP.message(
1363                            format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1364                    ], false)?;
1365                }
1366            }
1367
1368            Ok(Some(possible))
1369        } else if possible_with_extension.exists() {
1370            Ok(Some(possible_with_extension))
1371        } else {
1372            Ok(None)
1373        }
1374    }
1375
1376    fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1377    where
1378        F: FnMut(&Path) -> CargoResult<()>,
1379    {
1380        let mut seen_dir = HashSet::default();
1381
1382        for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1383            let config_root = current.join(".cargo");
1384            if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1385                walk(&path)?;
1386            }
1387
1388            let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1389            seen_dir.insert(canonical_root);
1390        }
1391
1392        let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1393
1394        // Once we're done, also be sure to walk the home directory even if it's not
1395        // in our history to be sure we pick up that standard location for
1396        // information.
1397        if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1398            if let Some(path) = self.get_file_path(home, "config", true)? {
1399                walk(&path)?;
1400            }
1401        }
1402
1403        Ok(())
1404    }
1405
1406    /// Gets the index for a registry.
1407    pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1408        RegistryName::new(registry)?;
1409        if let Some(index) = self.get_string(["registries", registry, "index"])? {
1410            self.resolve_registry_index(&index).with_context(|| {
1411                format!(
1412                    "invalid index URL for registry `{}` defined in {}",
1413                    registry, index.definition
1414                )
1415            })
1416        } else {
1417            bail!(
1418                "registry index was not found in any configuration: `{}`",
1419                registry
1420            );
1421        }
1422    }
1423
1424    /// Returns an error if `registry.index` is set.
1425    pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1426        if self.get_string(["registry", "index"])?.is_some() {
1427            bail!(
1428                "the `registry.index` config value is no longer supported\n\
1429                Use `[source]` replacement to alter the default index for crates.io."
1430            );
1431        }
1432        Ok(())
1433    }
1434
1435    fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1436        // This handles relative file: URLs, relative to the config definition.
1437        let base = index
1438            .definition
1439            .root(self.cwd())
1440            .join("truncated-by-url_with_base");
1441        // Parse val to check it is a URL, not a relative path without a protocol.
1442        let _parsed = index.val.into_url()?;
1443        let url = index.val.into_url_with_base(Some(&*base))?;
1444        if url.password().is_some() {
1445            bail!("registry URLs may not contain passwords");
1446        }
1447        Ok(url)
1448    }
1449
1450    /// Gets configuration values from the credentials file, loading them on first use.
1451    fn credentials_config(&self) -> CargoResult<&ConfigStore> {
1452        self.credentials_config
1453            .try_borrow_with(|| self.load_credentials())
1454    }
1455
1456    /// Loads credentials config from the credentials file, if present.
1457    fn load_credentials(&self) -> CargoResult<ConfigStore> {
1458        let home_path = self.home_path.clone().into_path_unlocked();
1459        let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1460            return Ok(ConfigStore::default());
1461        };
1462
1463        let mut config = ConfigStore::read_layer(&credentials, DefinitionKind::Path)?;
1464        // Backwards compatibility for old `.cargo/credentials` layout.
1465        {
1466            let def = config.definition().clone();
1467            let value_map = config.values_mut();
1468
1469            if let Some(token) = value_map.remove("token") {
1470                value_map.entry("registry".into()).or_insert_with(|| {
1471                    let map = HashMap::from_iter([("token".into(), token)]);
1472                    CV::Table(map, def.clone())
1473                });
1474            }
1475        }
1476
1477        Ok(config)
1478    }
1479
1480    /// Looks for a path for `tool` in an environment variable or the given config, and returns
1481    /// `None` if it's not present.
1482    fn maybe_get_tool(
1483        &self,
1484        tool: &str,
1485        from_config: &Option<ConfigRelativePath>,
1486    ) -> Option<PathBuf> {
1487        let var = tool.to_uppercase();
1488
1489        match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1490            Some(tool_path) => {
1491                let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1492                let path = if maybe_relative {
1493                    self.cwd.join(tool_path)
1494                } else {
1495                    PathBuf::from(tool_path)
1496                };
1497                Some(path)
1498            }
1499
1500            None => from_config.as_ref().map(|p| p.resolve_program(self)),
1501        }
1502    }
1503
1504    /// Returns the path for the given tool.
1505    ///
1506    /// This will look for the tool in the following order:
1507    ///
1508    /// 1. From an environment variable matching the tool name (such as `RUSTC`).
1509    /// 2. From the given config value (which is usually something like `build.rustc`).
1510    /// 3. Finds the tool in the PATH environment variable.
1511    ///
1512    /// This is intended for tools that are rustup proxies. If you need to get
1513    /// a tool that is not a rustup proxy, use `maybe_get_tool` instead.
1514    fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1515        let tool_str = tool.as_str();
1516        self.maybe_get_tool(tool_str, from_config)
1517            .or_else(|| {
1518                // This is an optimization to circumvent the rustup proxies
1519                // which can have a significant performance hit. The goal here
1520                // is to determine if calling `rustc` from PATH would end up
1521                // calling the proxies.
1522                //
1523                // This is somewhat cautious trying to determine if it is safe
1524                // to circumvent rustup, because there are some situations
1525                // where users may do things like modify PATH, call cargo
1526                // directly, use a custom rustup toolchain link without a
1527                // cargo executable, etc. However, there is still some risk
1528                // this may make the wrong decision in unusual circumstances.
1529                //
1530                // First, we must be running under rustup in the first place.
1531                let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1532                // Rustup supports named toolchains and absolute path toolchains,
1533                // but not relative path toolchains. This also enforces UTF-8.
1534                let toolchain = toolchain.to_str()?;
1535                let toolchain_path = Path::new(toolchain);
1536                let toolchain_dir = if toolchain_path.is_absolute() {
1537                    toolchain_path.to_path_buf()
1538                } else if !toolchain.contains(&['/', '\\']) {
1539                    home::rustup_home().ok()?.join("toolchains").join(toolchain)
1540                } else {
1541                    return None;
1542                };
1543                // If the tool on PATH is the same as `rustup` on path, then
1544                // there is pretty good evidence that it will be a proxy.
1545                let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1546                let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1547                let tool_meta = tool_resolved.metadata().ok()?;
1548                let rustup_meta = rustup_resolved.metadata().ok()?;
1549                // This works on the assumption that rustup and its proxies
1550                // use hard links to a single binary. If rustup ever changes
1551                // that setup, then I think the worst consequence is that this
1552                // optimization will not work, and it will take the slow path.
1553                if tool_meta.len() != rustup_meta.len() {
1554                    return None;
1555                }
1556                // Try to find the tool in rustup's toolchain directory.
1557                let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1558                let toolchain_exe = toolchain_dir.join("bin").join(&tool_exe);
1559                toolchain_exe.exists().then_some(toolchain_exe)
1560            })
1561            .unwrap_or_else(|| PathBuf::from(tool_str))
1562    }
1563
1564    /// Get the `paths` overrides config value.
1565    pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1566        let key = ConfigKey::from_parts(["paths"]);
1567        // paths overrides cannot be set via env config, so use get_cv here.
1568        match self.config()?.get_cv(&key)? {
1569            Some(CV::List(val, definition)) => {
1570                let val = val
1571                    .into_iter()
1572                    .map(|cv| match cv {
1573                        CV::String(s, def) => Ok((s, def)),
1574                        other => crate::config::expected("string", &key, &other),
1575                    })
1576                    .collect::<CargoResult<Vec<_>>>()?;
1577                Ok(Some(Value { val, definition }))
1578            }
1579            Some(val) => crate::config::expected("list", &key, &val),
1580            None => Ok(None),
1581        }
1582    }
1583
1584    pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1585        self.jobserver
1586    }
1587
1588    pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1589        let http = self
1590            .easy
1591            .try_borrow_with(|| http_handle(self).map(Into::into))?;
1592        {
1593            let mut http = http.lock().unwrap();
1594            http.reset();
1595            let timeout = configure_http_handle(self, &mut http)?;
1596            timeout.configure(&mut http)?;
1597        }
1598        Ok(http)
1599    }
1600
1601    pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1602        self.http_async.try_borrow_with(|| {
1603            let handle_config = HandleConfiguration::new(&self)?;
1604            Ok(http_async::Client::new(handle_config))
1605        })
1606    }
1607
1608    pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1609        self.http_config.try_borrow_with(|| {
1610            let mut http = self.get::<CargoHttpConfig>(["http"])?;
1611            let curl_v = curl::Version::get();
1612            disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1613            Ok(http)
1614        })
1615    }
1616
1617    pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1618        self.future_incompat_config
1619            .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>(["future-incompat-report"]))
1620    }
1621
1622    pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1623        self.net_config
1624            .try_borrow_with(|| self.get::<CargoNetConfig>(["net"]))
1625    }
1626
1627    pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1628        self.build_config
1629            .try_borrow_with(|| self.get::<CargoBuildConfig>(["build"]))
1630    }
1631
1632    pub fn progress_config(&self) -> &ProgressConfig {
1633        &self.progress_config
1634    }
1635
1636    /// Get the env vars from the config `[env]` table which
1637    /// are `force = true` or don't exist in the env snapshot [`GlobalContext::get_env`].
1638    pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1639        let env_config = self.env_config.try_borrow_with(|| {
1640            CargoResult::Ok(Arc::new({
1641                let env_config = self.get::<EnvConfig>(["env"])?;
1642                // Reasons for disallowing these values:
1643                //
1644                // - CARGO_HOME: The initial call to cargo does not honor this value
1645                //   from the [env] table. Recursive calls to cargo would use the new
1646                //   value, possibly behaving differently from the outer cargo.
1647                //
1648                // - RUSTUP_HOME and RUSTUP_TOOLCHAIN: Under normal usage with rustup,
1649                //   this will have no effect because the rustup proxy sets
1650                //   RUSTUP_HOME and RUSTUP_TOOLCHAIN, and that would override the
1651                //   [env] table. If the outer cargo is executed directly
1652                //   circumventing the rustup proxy, then this would affect calls to
1653                //   rustc (assuming that is a proxy), which could potentially cause
1654                //   problems with cargo and rustc being from different toolchains. We
1655                //   consider this to be not a use case we would like to support,
1656                //   since it will likely cause problems or lead to confusion.
1657                for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1658                    if env_config.contains_key(*disallowed) {
1659                        bail!(
1660                            "setting the `{disallowed}` environment variable is not supported \
1661                            in the `[env]` configuration table"
1662                        );
1663                    }
1664                }
1665                env_config
1666                    .into_iter()
1667                    .filter_map(|(k, v)| {
1668                        if v.is_force() || self.get_env_os(&k).is_none() {
1669                            Some((k, v.resolve(self.cwd()).to_os_string()))
1670                        } else {
1671                            None
1672                        }
1673                    })
1674                    .collect()
1675            }))
1676        })?;
1677
1678        Ok(env_config)
1679    }
1680
1681    /// This is used to validate the `term` table has valid syntax.
1682    ///
1683    /// This is necessary because loading the term settings happens very
1684    /// early, and in some situations (like `cargo version`) we don't want to
1685    /// fail if there are problems with the config file.
1686    pub fn validate_term_config(&self) -> CargoResult<()> {
1687        drop(self.get::<TermConfig>(["term"])?);
1688        Ok(())
1689    }
1690
1691    /// Returns a list of `target.'cfg()'` tables.
1692    ///
1693    /// The list is sorted by the table name.
1694    pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1695        self.target_cfgs
1696            .try_borrow_with(|| target::load_target_cfgs(self))
1697    }
1698
1699    pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1700        // Note: This does not support environment variables. The `Unit`
1701        // fundamentally does not have access to the registry name, so there is
1702        // nothing to query. Plumbing the name into SourceId is quite challenging.
1703        self.doc_extern_map
1704            .try_borrow_with(|| self.get::<RustdocExternMap>(["doc", "extern-map"]))
1705    }
1706
1707    /// Returns true if the `[target]` table should be applied to host targets.
1708    pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1709        target::get_target_applies_to_host(self)
1710    }
1711
1712    /// Returns the `[host]` table definition for the given target tuple.
1713    pub fn host_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
1714        target::load_host_tuple(self, target)
1715    }
1716
1717    /// Returns the `[target]` table definition for the given target tuple.
1718    pub fn target_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
1719        target::load_target_tuple(self, target)
1720    }
1721
1722    /// Returns the cached [`SourceId`] corresponding to the main repository.
1723    ///
1724    /// This is the main cargo registry by default, but it can be overridden in
1725    /// a `.cargo/config.toml`.
1726    pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1727        let source_id = self.crates_io_source_id.try_borrow_with(|| {
1728            self.check_registry_index_not_set()?;
1729            let url = CRATES_IO_INDEX.into_url().unwrap();
1730            SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1731        })?;
1732        Ok(*source_id)
1733    }
1734
1735    pub fn invocation_instant(&self) -> Instant {
1736        self.invocation_instant
1737    }
1738
1739    /// Returns the wall-clock time of this cargo invocation.
1740    ///
1741    /// See the [`invocation_time`] field doc for details.
1742    ///
1743    /// [`invocation_time`]: GlobalContext::invocation_time
1744    pub fn invocation_time(&self) -> jiff::Timestamp {
1745        self.invocation_time
1746    }
1747
1748    /// Retrieves a config variable.
1749    ///
1750    /// This supports most serde `Deserialize` types. Examples:
1751    ///
1752    /// ```rust,ignore
1753    /// let v: Option<u32> = config.get(["some", "nested", "key"])?;
1754    /// let v: Option<MyStruct> = config.get(["some", "key"])?;
1755    /// let v: Option<HashMap<String, MyStruct>> = config.get(["foo"])?;
1756    /// ```
1757    ///
1758    /// The key may be a dotted key, but this does NOT support TOML key
1759    /// quoting. Avoid key components that may have dots. For example,
1760    /// `foo.'a.b'.bar" does not work if you try to fetch `foo.'a.b'". You can
1761    /// fetch `foo` if it is a map, though.
1762    pub fn get<'de, T: serde::de::Deserialize<'de>>(
1763        &self,
1764
1765        key: impl IntoIterator<Item = impl AsRef<str>>,
1766    ) -> CargoResult<T> {
1767        self.config_reader()?.get(ConfigKey::from_parts(key))
1768    }
1769
1770    /// Obtain a [`Path`] from a [`Filesystem`], verifying that the
1771    /// appropriate lock is already currently held.
1772    ///
1773    /// Locks are usually acquired via [`GlobalContext::acquire_package_cache_lock`]
1774    /// or [`GlobalContext::try_acquire_package_cache_lock`].
1775    #[track_caller]
1776    #[tracing::instrument(skip_all)]
1777    pub fn assert_package_cache_locked<'a>(
1778        &self,
1779        mode: CacheLockMode,
1780        f: &'a Filesystem,
1781    ) -> &'a Path {
1782        let ret = f.as_path_unlocked();
1783        assert!(
1784            self.package_cache_lock.is_locked(mode),
1785            "package cache lock is not currently held, Cargo forgot to call \
1786             `acquire_package_cache_lock` before we got to this stack frame",
1787        );
1788        assert!(ret.starts_with(self.home_path.as_path_unlocked()));
1789        ret
1790    }
1791
1792    /// Acquires a lock on the global "package cache", blocking if another
1793    /// cargo holds the lock.
1794    ///
1795    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
1796    /// and lock modes.
1797    #[tracing::instrument(skip_all)]
1798    pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
1799        self.package_cache_lock.lock(self, mode)
1800    }
1801
1802    /// Acquires a lock on the global "package cache", returning `None` if
1803    /// another cargo holds the lock.
1804    ///
1805    /// See [`crate::util::cache_lock`] for an in-depth discussion of locking
1806    /// and lock modes.
1807    #[tracing::instrument(skip_all)]
1808    pub fn try_acquire_package_cache_lock(
1809        &self,
1810        mode: CacheLockMode,
1811    ) -> CargoResult<Option<CacheLock<'_>>> {
1812        self.package_cache_lock.try_lock(self, mode)
1813    }
1814
1815    /// Returns a reference to the shared [`GlobalCacheTracker`].
1816    ///
1817    /// The package cache lock must be held to call this function (and to use
1818    /// it in general).
1819    pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
1820        let tracker = self.global_cache_tracker.try_borrow_with(|| {
1821            Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
1822        })?;
1823        Ok(tracker.lock().unwrap())
1824    }
1825
1826    /// Returns a reference to the shared [`DeferredGlobalLastUse`].
1827    pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
1828        let deferred = self
1829            .deferred_global_last_use
1830            .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
1831        Ok(deferred.lock().unwrap())
1832    }
1833
1834    /// Get the global [`WarningHandling`] configuration.
1835    pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
1836        Ok(self.build_config()?.warnings.unwrap_or_default())
1837    }
1838
1839    pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
1840        self.ws_roots.lock().unwrap()
1841    }
1842
1843    /// Mode for matching version requirements
1844    /// against versions already selected / locked.
1845    ///
1846    /// Once stabilized,
1847    /// this always returns [`VersionReqMatchMode::Prerelease`] (or remove and inline).
1848    pub fn version_req_match_mode(&self) -> VersionReqMatchMode {
1849        if self.cli_unstable().prerelease {
1850            VersionReqMatchMode::Prerelease
1851        } else {
1852            VersionReqMatchMode::Default
1853        }
1854    }
1855}
1856
1857fn registry_config_uncached(
1858    gctx: &GlobalContext,
1859    sid: &SourceId,
1860) -> CargoResult<Option<RegistryConfig>> {
1861    tracing::trace!("loading registry config for {}", sid);
1862    if !sid.is_remote_registry() {
1863        bail!(
1864            "{} does not support API commands.\n\
1865             Check for a source-replacement in .cargo/config.",
1866            sid
1867        );
1868    }
1869
1870    // Handle crates.io specially, since it uses different configuration keys.
1871    if sid.is_crates_io() {
1872        gctx.check_registry_index_not_set()?;
1873        return Ok(gctx
1874            .get::<Option<GlobalRegistryConfig>>(["registry"])?
1875            .map(|c| c.to_registry_config()));
1876    }
1877
1878    let name = registry_name(gctx, sid)?;
1879
1880    // It's possible to have a registry configured in a Cargo config file,
1881    // then override it with configuration from environment variables.
1882    // If the name doesn't match, leave a note to help the user understand
1883    // the potentially confusing situation.
1884    if let Some(name) = name.as_deref() {
1885        if Some(name) != sid.alt_registry_key() {
1886            gctx.shell().note(format!(
1887                "name of alternative registry `{}` set to `{name}`",
1888                sid.url()
1889            ))?
1890        }
1891    }
1892
1893    if let Some(name) = &name {
1894        tracing::debug!("found alternative registry name `{name}` for {sid}");
1895        gctx.get::<Option<RegistryConfig>>(["registries", name.as_str()])
1896    } else {
1897        tracing::debug!("no registry name found for {sid}");
1898        Ok(None)
1899    }
1900}
1901
1902fn registry_name(gctx: &GlobalContext, sid: &SourceId) -> CargoResult<Option<String>> {
1903    // Find the SourceId's name by its index URL. If environment variables
1904    // are available they will be preferred over configuration values.
1905    //
1906    // The fundamental problem is that we only know the index url of the registry
1907    // for certain. For example, an unnamed registry source can come from the `--index`
1908    // command line argument, or from a Cargo.lock file. For this reason, we always
1909    // attempt to discover the name by looking it up by the index URL.
1910    //
1911    // This also allows the authorization token for a registry to be set
1912    // without knowing the registry name by using the _INDEX and _TOKEN
1913    // environment variables.
1914
1915    let name = {
1916        // Discover names from environment variables.
1917        let index = sid.canonical_url();
1918        let mut names: Vec<_> = gctx
1919            .env()
1920            .filter_map(|(k, v)| {
1921                Some((
1922                    k.strip_prefix("CARGO_REGISTRIES_")?
1923                        .strip_suffix("_INDEX")?,
1924                    v,
1925                ))
1926            })
1927            .filter_map(|(k, v)| Some((k, CanonicalUrl::new(&v.into_url().ok()?).ok()?)))
1928            .filter(|(_, v)| v == index)
1929            .map(|(k, _)| k.to_lowercase())
1930            .collect();
1931
1932        // Discover names from the configuration only if none were found in the environment.
1933        if names.len() == 0 {
1934            if let Some(registries) = gctx.config()?.values().get("registries") {
1935                let (registries, _) = registries.table("registries")?;
1936                for (name, value) in registries {
1937                    if let Some(v) = value.table(&format!("registries.{name}"))?.0.get("index") {
1938                        let (v, _) = v.string(&format!("registries.{name}.index"))?;
1939                        if index == &CanonicalUrl::new(&v.into_url()?)? {
1940                            names.push(name.clone());
1941                        }
1942                    }
1943                }
1944            }
1945        }
1946        names.sort();
1947        match names.len() {
1948            0 => None,
1949            1 => Some(std::mem::take(&mut names[0])),
1950            _ => anyhow::bail!(
1951                "multiple registries are configured with the same index url '{}': {}",
1952                &sid.as_url(),
1953                names.join(", ")
1954            ),
1955        }
1956    };
1957
1958    Ok(name)
1959}
1960
1961pub fn homedir(cwd: &Path) -> Option<PathBuf> {
1962    ::home::cargo_home_with_cwd(cwd)
1963        .ok()
1964        // https://github.com/rust-lang/cargo/issues/15981
1965        // This is so everything shares one spelling and
1966        // isn't incorrectly seen as distinct.
1967        .map(|home| paths::normalize_path(&home))
1968}
1969
1970pub fn save_credentials(
1971    gctx: &GlobalContext,
1972    token: Option<RegistryCredentialConfig>,
1973    registry: &SourceId,
1974) -> CargoResult<()> {
1975    let registry = if registry.is_crates_io() {
1976        None
1977    } else {
1978        let name = registry
1979            .alt_registry_key()
1980            .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
1981        Some(name)
1982    };
1983
1984    // If 'credentials' exists, write to that for backward compatibility reasons.
1985    // Otherwise write to 'credentials.toml'. There's no need to print the
1986    // warning here, because it would already be printed at load time.
1987    let home_path = gctx.home_path.clone().into_path_unlocked();
1988    let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
1989        Some(path) => match path.file_name() {
1990            Some(filename) => Path::new(filename).to_owned(),
1991            None => Path::new("credentials.toml").to_owned(),
1992        },
1993        None => Path::new("credentials.toml").to_owned(),
1994    };
1995
1996    let mut file = {
1997        gctx.home_path.create_dir()?;
1998        gctx.home_path
1999            .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2000    };
2001
2002    let mut contents = String::new();
2003    file.read_to_string(&mut contents).with_context(|| {
2004        format!(
2005            "failed to read configuration file `{}`",
2006            file.path().display()
2007        )
2008    })?;
2009
2010    let mut toml = contents.parse::<toml::Table>()?;
2011
2012    // Move the old token location to the new one.
2013    if let Some(token) = toml.remove("token") {
2014        #[expect(
2015            clippy::disallowed_types,
2016            reason = "need stdlib's HashMap because of TOML compatibility"
2017        )]
2018        let map = std::collections::HashMap::from([("token".to_string(), token)]);
2019        toml.insert("registry".into(), map.into());
2020    }
2021
2022    if let Some(token) = token {
2023        // login
2024
2025        let path_def = Definition::Path(file.path().to_path_buf());
2026        let (key, mut value) = match token {
2027            RegistryCredentialConfig::Token(token) => {
2028                // login with token
2029
2030                let key = "token".to_string();
2031                let value = ConfigValue::String(token.expose(), path_def.clone());
2032                let map = HashMap::from_iter([(key, value)]);
2033                let table = CV::Table(map, path_def.clone());
2034
2035                if let Some(registry) = registry {
2036                    let map = HashMap::from_iter([(registry.to_string(), table)]);
2037                    ("registries".into(), CV::Table(map, path_def.clone()))
2038                } else {
2039                    ("registry".into(), table)
2040                }
2041            }
2042            _ => unreachable!(),
2043        };
2044
2045        if registry.is_some() {
2046            if let Some(table) = toml.remove("registries") {
2047                let v = CV::from_toml(path_def, table)?;
2048                value.merge(v, false)?;
2049            }
2050        }
2051        toml.insert(key, value.into_toml());
2052    } else {
2053        // logout
2054        if let Some(registry) = registry {
2055            if let Some(registries) = toml.get_mut("registries") {
2056                if let Some(reg) = registries.get_mut(registry) {
2057                    let rtable = reg.as_table_mut().ok_or_else(|| {
2058                        format_err!("expected `[registries.{}]` to be a table", registry)
2059                    })?;
2060                    rtable.remove("token");
2061                }
2062            }
2063        } else if let Some(registry) = toml.get_mut("registry") {
2064            let reg_table = registry
2065                .as_table_mut()
2066                .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2067            reg_table.remove("token");
2068        }
2069    }
2070
2071    let contents = toml.to_string();
2072    file.seek(SeekFrom::Start(0))?;
2073    file.write_all(contents.as_bytes())
2074        .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2075    file.file().set_len(contents.len() as u64)?;
2076    set_permissions(file.file(), 0o600)
2077        .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2078
2079    return Ok(());
2080
2081    #[cfg(unix)]
2082    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2083        use std::os::unix::fs::PermissionsExt;
2084
2085        let mut perms = file.metadata()?.permissions();
2086        perms.set_mode(mode);
2087        file.set_permissions(perms)?;
2088        Ok(())
2089    }
2090
2091    #[cfg(not(unix))]
2092    fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2093        Ok(())
2094    }
2095}
2096
2097/// Represents a config-include value in the configuration.
2098///
2099/// This intentionally doesn't derive serde deserialization
2100/// to avoid any misuse of `GlobalContext::get::<ConfigInclude>()`,
2101/// which might lead to wrong config loading order.
2102struct ConfigInclude {
2103    /// Path to a config-include configuration file.
2104    /// Could be either relative or absolute.
2105    path: PathBuf,
2106    def: Definition,
2107    /// Whether this include is optional (missing files are silently ignored)
2108    optional: bool,
2109}
2110
2111impl ConfigInclude {
2112    fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2113        Self {
2114            path: p.into(),
2115            def,
2116            optional: false,
2117        }
2118    }
2119
2120    /// Resolves the absolute path for this include.
2121    ///
2122    /// For file based include,
2123    /// it is relative to parent directory of the config file includes it.
2124    /// For example, if `.cargo/config.toml has a `include = "foo.toml"`,
2125    /// Cargo will load `.cargo/foo.toml`.
2126    ///
2127    /// For CLI based include (e.g., `--config 'include = "foo.toml"'`),
2128    /// it is relative to the current working directory.
2129    ///
2130    /// Returns `None` if this is an optional include and the file doesn't exist.
2131    /// Otherwise returns `Some(PathBuf)` with the absolute path.
2132    fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2133        let abs_path = match &self.def {
2134            Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2135            Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2136        }
2137        .join(&self.path);
2138        let abs_path = paths::normalize_path(&abs_path);
2139
2140        if self.optional && !abs_path.exists() {
2141            tracing::info!(
2142                "skipping optional include `{}` in `{}`:  file not found at `{}`",
2143                self.path.display(),
2144                self.def,
2145                abs_path.display(),
2146            );
2147            None
2148        } else {
2149            Some(abs_path)
2150        }
2151    }
2152}
2153
2154fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2155    // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
2156    // expressions followed by a value that's not an "inline table"
2157    // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
2158    // parse the value as a toml_edit::DocumentMut, and check that the (single)
2159    // inner-most table is set via dotted keys.
2160    let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2161        format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2162    })?;
2163    fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2164        d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2165    }
2166    fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2167        non_empty(d.prefix()) || non_empty(d.suffix())
2168    }
2169    fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2170        non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2171    }
2172    let ok = {
2173        let mut got_to_value = false;
2174        let mut table = doc.as_table();
2175        let mut is_root = true;
2176        while table.is_dotted() || is_root {
2177            is_root = false;
2178            if table.len() != 1 {
2179                break;
2180            }
2181            let (k, n) = table.iter().next().expect("len() == 1 above");
2182            match n {
2183                Item::Table(nt) => {
2184                    if table.key(k).map_or(false, non_empty_key_decor)
2185                        || non_empty_decor(nt.decor())
2186                    {
2187                        bail!(
2188                            "--config argument `{arg}` \
2189                                includes non-whitespace decoration"
2190                        )
2191                    }
2192                    table = nt;
2193                }
2194                Item::Value(v) if v.is_inline_table() => {
2195                    bail!(
2196                        "--config argument `{arg}` \
2197                        sets a value to an inline table, which is not accepted"
2198                    );
2199                }
2200                Item::Value(v) => {
2201                    if table
2202                        .key(k)
2203                        .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2204                        || non_empty_decor(v.decor())
2205                    {
2206                        bail!(
2207                            "--config argument `{arg}` \
2208                                includes non-whitespace decoration"
2209                        )
2210                    }
2211                    got_to_value = true;
2212                    break;
2213                }
2214                Item::ArrayOfTables(_) => {
2215                    bail!(
2216                        "--config argument `{arg}` \
2217                        sets a value to an array of tables, which is not accepted"
2218                    );
2219                }
2220
2221                Item::None => {
2222                    bail!("--config argument `{arg}` doesn't provide a value")
2223                }
2224            }
2225        }
2226        got_to_value
2227    };
2228    if !ok {
2229        bail!(
2230            "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2231        );
2232    }
2233    Ok(doc)
2234}
2235
2236#[macro_export]
2237macro_rules! __shell_print {
2238    ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2239        let mut shell = $config.shell();
2240        let out = shell.$which();
2241        drop(out.write_fmt(format_args!($($arg)*)));
2242        if $newline {
2243            drop(out.write_all(b"\n"));
2244        }
2245    });
2246}
2247
2248#[macro_export]
2249macro_rules! drop_println {
2250    ($config:expr) => ( $crate::drop_print!($config, "\n") );
2251    ($config:expr, $($arg:tt)*) => (
2252        $crate::__shell_print!($config, out, true, $($arg)*)
2253    );
2254}
2255
2256#[macro_export]
2257macro_rules! drop_eprintln {
2258    ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2259    ($config:expr, $($arg:tt)*) => (
2260        $crate::__shell_print!($config, err, true, $($arg)*)
2261    );
2262}
2263
2264#[macro_export]
2265macro_rules! drop_print {
2266    ($config:expr, $($arg:tt)*) => (
2267        $crate::__shell_print!($config, out, false, $($arg)*)
2268    );
2269}
2270
2271#[macro_export]
2272macro_rules! drop_eprint {
2273    ($config:expr, $($arg:tt)*) => (
2274        $crate::__shell_print!($config, err, false, $($arg)*)
2275    );
2276}
2277
2278enum Tool {
2279    Rustc,
2280    Rustdoc,
2281}
2282
2283impl Tool {
2284    fn as_str(&self) -> &str {
2285        match self {
2286            Tool::Rustc => "rustc",
2287            Tool::Rustdoc => "rustdoc",
2288        }
2289    }
2290}
2291
2292/// Disable HTTP/2 multiplexing for some broken versions of libcurl.
2293///
2294/// In certain versions of libcurl when proxy is in use with HTTP/2
2295/// multiplexing, connections will continue stacking up. This was
2296/// fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc
2297///
2298/// However, Cargo can still link against old system libcurl if it is from a
2299/// custom built one or on macOS. For those cases, multiplexing needs to be
2300/// disabled when those versions are detected.
2301fn disables_multiplexing_for_bad_curl(
2302    curl_version: &str,
2303    http: &mut CargoHttpConfig,
2304    gctx: &GlobalContext,
2305) {
2306    use crate::util::network;
2307
2308    if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2309        let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2310        if bad_curl_versions
2311            .iter()
2312            .any(|v| curl_version.starts_with(v))
2313        {
2314            tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2315            http.multiplexing = Some(false);
2316        }
2317    }
2318}
2319
2320#[cfg(test)]
2321mod tests {
2322    use super::CargoHttpConfig;
2323    use super::GlobalContext;
2324    use super::Shell;
2325    use super::disables_multiplexing_for_bad_curl;
2326
2327    #[test]
2328    fn disables_multiplexing() {
2329        let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2330        gctx.set_search_stop_path(std::path::PathBuf::new());
2331        gctx.set_env(Default::default());
2332
2333        let mut http = CargoHttpConfig::default();
2334        http.proxy = Some("127.0.0.1:3128".into());
2335        disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2336        assert_eq!(http.multiplexing, Some(false));
2337
2338        let cases = [
2339            (None, None, "7.87.0", None),
2340            (None, None, "7.88.0", None),
2341            (None, None, "7.88.1", None),
2342            (None, None, "8.0.0", None),
2343            (Some("".into()), None, "7.87.0", Some(false)),
2344            (Some("".into()), None, "7.88.0", Some(false)),
2345            (Some("".into()), None, "7.88.1", Some(false)),
2346            (Some("".into()), None, "8.0.0", None),
2347            (Some("".into()), Some(false), "7.87.0", Some(false)),
2348            (Some("".into()), Some(false), "7.88.0", Some(false)),
2349            (Some("".into()), Some(false), "7.88.1", Some(false)),
2350            (Some("".into()), Some(false), "8.0.0", Some(false)),
2351        ];
2352
2353        for (proxy, multiplexing, curl_v, result) in cases {
2354            let mut http = CargoHttpConfig {
2355                multiplexing,
2356                proxy,
2357                ..Default::default()
2358            };
2359            disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2360            assert_eq!(http.multiplexing, result);
2361        }
2362    }
2363
2364    #[test]
2365    fn sync_context() {
2366        fn assert_sync<S: Sync>() {}
2367        assert_sync::<GlobalContext>();
2368    }
2369}