1use 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#[derive(Debug)]
145pub struct CredentialCacheValue {
146 pub token_value: Secret<String>,
147 pub expiration: Option<OffsetDateTime>,
148 pub operation_independent: bool,
149}
150
151#[derive(Debug)]
154pub struct GlobalContext {
155 home_path: Filesystem,
157 shell: Mutex<Shell>,
159 config: OnceLock<ConfigStore>,
161 credentials_config: OnceLock<ConfigStore>,
163 cli_config: Option<Vec<String>>,
165 cwd: PathBuf,
167 search_stop_path: Option<PathBuf>,
169 cargo_exe: OnceLock<PathBuf>,
171 rustdoc: OnceLock<PathBuf>,
173 extra_verbose: bool,
175 frozen: bool,
178 locked: bool,
181 offline: bool,
184 jobserver: Option<&'static jobserver::Client>,
186 unstable_flags: CliUnstable,
188 unstable_flags_cli: Option<Vec<String>>,
190 easy: OnceLock<Mutex<Easy>>,
192 crates_io_source_id: OnceLock<SourceId>,
194 cache_rustc_info: bool,
196 invocation_instant: Instant,
198 invocation_time: jiff::Timestamp,
202 target_dir: Option<Filesystem>,
204 env: Env,
206 updated_sources: Mutex<HashSet<SourceId>>,
208 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
211 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
213 package_cache_lock: CacheLocker,
215 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 pub nightly_features_allowed: bool,
241 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
243 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
245 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
248}
249
250impl GlobalContext {
251 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 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 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 pub fn home(&self) -> &Filesystem {
380 &self.home_path
381 }
382
383 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 pub fn git_path(&self) -> Filesystem {
397 self.home_path.join("git")
398 }
399
400 pub fn git_checkouts_path(&self) -> Filesystem {
403 self.git_path().join("checkouts")
404 }
405
406 pub fn git_db_path(&self) -> Filesystem {
409 self.git_path().join("db")
410 }
411
412 pub fn registry_base_path(&self) -> Filesystem {
414 self.home_path.join("registry")
415 }
416
417 pub fn registry_index_path(&self) -> Filesystem {
419 self.registry_base_path().join("index")
420 }
421
422 pub fn registry_cache_path(&self) -> Filesystem {
424 self.registry_base_path().join("cache")
425 }
426
427 pub fn registry_source_path(&self) -> Filesystem {
429 self.registry_base_path().join("src")
430 }
431
432 pub fn default_registry(&self) -> CargoResult<Option<String>> {
434 Ok(self
435 .get_string(["registry", "default"])?
436 .map(|registry| registry.val))
437 }
438
439 pub fn shell(&self) -> MutexGuard<'_, Shell> {
441 self.shell.lock().unwrap()
442 }
443
444 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 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 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 pub fn cargo_exe(&self) -> CargoResult<&Path> {
496 self.cargo_exe
497 .try_borrow_with(|| {
498 let from_env = || -> CargoResult<PathBuf> {
499 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 let exe = env::current_exe()?;
516 Ok(exe)
517 }
518
519 fn from_argv() -> CargoResult<PathBuf> {
520 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 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 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
560 self.updated_sources.lock().unwrap()
561 }
562
563 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
565 self.credential_cache.lock().unwrap()
566 }
567
568 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 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 pub fn config(&self) -> CargoResult<&ConfigStore> {
638 self.config.try_borrow_with(|| self.load_config())
639 }
640
641 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 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 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 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 pub fn cwd(&self) -> &Path {
691 &self.cwd
692 }
693
694 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 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 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 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 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 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 pub fn set_env(&mut self, env: HashMap<String, String>) {
833 self.env = Env::from_map(env);
834 }
835
836 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
839 self.env.iter_str()
840 }
841
842 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
847 self.env.get_env(key)
848 }
849
850 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
855 self.env.get_env_os(key)
856 }
857
858 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 PathBuf::from(value)
875 }
876 }
877
878 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 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 let term = self.get::<TermConfig>(["term"]).unwrap_or_default();
917
918 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 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 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 pub fn load_config(&self) -> CargoResult<ConfigStore> {
1026 self.load_config_from(&self.cwd)
1027 }
1028
1029 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 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 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 fn load_file(&self, path: &Path) -> CargoResult<ConfigStore> {
1097 self._load_file(path, &mut HashSet::default(), true, DefinitionKind::Path)
1098 }
1099
1100 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 fn load_includes(
1138 &self,
1139 mut config: ConfigStore,
1140 seen: &mut HashSet<PathBuf>,
1141 definition_kind: DefinitionKind,
1142 ) -> CargoResult<ConfigStore> {
1143 let includes = self.include_paths(&mut config, true)?;
1145
1146 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 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 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 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 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 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 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 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 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 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 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 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 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 let base = index
1438 .definition
1439 .root(self.cwd())
1440 .join("truncated-by-url_with_base");
1441 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 fn credentials_config(&self) -> CargoResult<&ConfigStore> {
1452 self.credentials_config
1453 .try_borrow_with(|| self.load_credentials())
1454 }
1455
1456 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 {
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 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 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 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1532 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 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 if tool_meta.len() != rustup_meta.len() {
1554 return None;
1555 }
1556 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 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1566 let key = ConfigKey::from_parts(["paths"]);
1567 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 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 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 pub fn validate_term_config(&self) -> CargoResult<()> {
1687 drop(self.get::<TermConfig>(["term"])?);
1688 Ok(())
1689 }
1690
1691 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 self.doc_extern_map
1704 .try_borrow_with(|| self.get::<RustdocExternMap>(["doc", "extern-map"]))
1705 }
1706
1707 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1709 target::get_target_applies_to_host(self)
1710 }
1711
1712 pub fn host_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
1714 target::load_host_tuple(self, target)
1715 }
1716
1717 pub fn target_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
1719 target::load_target_tuple(self, target)
1720 }
1721
1722 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 pub fn invocation_time(&self) -> jiff::Timestamp {
1745 self.invocation_time
1746 }
1747
1748 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 #[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 #[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 #[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 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 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 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 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 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 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 let name = {
1916 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 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 .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 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 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 let path_def = Definition::Path(file.path().to_path_buf());
2026 let (key, mut value) = match token {
2027 RegistryCredentialConfig::Token(token) => {
2028 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 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
2097struct ConfigInclude {
2103 path: PathBuf,
2106 def: Definition,
2107 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 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 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
2292fn 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}