1use crate::util::data_structures::{HashMap, HashSet};
65use std::borrow::Cow;
66use std::env;
67use std::ffi::{OsStr, OsString};
68use std::fmt;
69use std::fs::{self, File};
70use std::io::SeekFrom;
71use std::io::prelude::*;
72use std::mem;
73use std::path::{Path, PathBuf};
74use std::str::FromStr;
75use std::sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock};
76use std::time::Instant;
77
78use self::ConfigValue as CV;
79use crate::core::compiler::rustdoc::RustdocExternMap;
80use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
81use crate::core::{CliUnstable, SourceId, Workspace, WorkspaceRootConfig, features};
82use crate::ops::RegistryCredentialConfig;
83use crate::sources::CRATES_IO_INDEX;
84use crate::sources::CRATES_IO_REGISTRY;
85use crate::util::OnceExt as _;
86use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
87use crate::util::errors::CargoResult;
88use crate::util::network::http::{HandleConfiguration, configure_http_handle, http_handle};
89use crate::util::network::http_async;
90use crate::util::restricted_names::is_glob_pattern;
91use crate::util::{CanonicalUrl, closest_msg, internal};
92use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
93
94use anyhow::{Context as _, anyhow, bail, format_err};
95use cargo_credential::Secret;
96use cargo_util::paths;
97use cargo_util_schemas::manifest::RegistryName;
98use cargo_util_terminal::report::Level;
99use cargo_util_terminal::{Shell, Verbosity};
100use curl::easy::Easy;
101use itertools::Itertools;
102use serde::Deserialize;
103use serde::de::IntoDeserializer as _;
104use time::OffsetDateTime;
105use toml_edit::Item;
106use url::Url;
107
108mod de;
109use de::Deserializer;
110
111mod error;
112pub use error::ConfigError;
113
114mod value;
115pub use value::{Definition, OptValue, Value};
116
117mod key;
118pub use key::ConfigKey;
119
120mod config_value;
121pub use config_value::ConfigValue;
122use config_value::is_nonmergeable_list;
123
124mod path;
125pub use path::BracketType;
126pub use path::ConfigRelativePath;
127pub use path::PathAndArgs;
128pub use path::ResolveTemplateError;
129
130mod target;
131pub use target::{TargetCfgConfig, TargetConfig};
132
133mod environment;
134use environment::Env;
135
136mod schema;
137pub use schema::*;
138
139use super::auth::RegistryConfig;
140
141macro_rules! get_value_typed {
143 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
144 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
146 let cv = self.get_cv(key)?;
147 let env = self.get_config_env::<$ty>(key)?;
148 match (cv, env) {
149 (Some(CV::$variant(val, definition)), Some(env)) => {
150 if definition.is_higher_priority(&env.definition) {
151 Ok(Some(Value { val, definition }))
152 } else {
153 Ok(Some(env))
154 }
155 }
156 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
157 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
158 (None, Some(env)) => Ok(Some(env)),
159 (None, None) => Ok(None),
160 }
161 }
162 };
163}
164
165pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
166 "paths",
167 "alias",
168 "build",
169 "credential-alias",
170 "doc",
171 "env",
172 "future-incompat-report",
173 "cache",
174 "cargo-new",
175 "http",
176 "install",
177 "net",
178 "patch",
179 "profile",
180 "resolver",
181 "registries",
182 "registry",
183 "source",
184 "target",
185 "term",
186];
187
188#[derive(Clone, Copy, Debug)]
190enum WhyLoad {
191 Cli,
196 FileDiscovery,
198}
199
200#[derive(Debug)]
202pub struct CredentialCacheValue {
203 pub token_value: Secret<String>,
204 pub expiration: Option<OffsetDateTime>,
205 pub operation_independent: bool,
206}
207
208#[derive(Debug)]
211pub struct GlobalContext {
212 home_path: Filesystem,
214 shell: Mutex<Shell>,
216 values: OnceLock<HashMap<String, ConfigValue>>,
218 credential_values: OnceLock<HashMap<String, ConfigValue>>,
220 cli_config: Option<Vec<String>>,
222 cwd: PathBuf,
224 search_stop_path: Option<PathBuf>,
226 cargo_exe: OnceLock<PathBuf>,
228 rustdoc: OnceLock<PathBuf>,
230 extra_verbose: bool,
232 frozen: bool,
235 locked: bool,
238 offline: bool,
241 jobserver: Option<&'static jobserver::Client>,
243 unstable_flags: CliUnstable,
245 unstable_flags_cli: Option<Vec<String>>,
247 easy: OnceLock<Mutex<Easy>>,
249 crates_io_source_id: OnceLock<SourceId>,
251 cache_rustc_info: bool,
253 invocation_instant: Instant,
255 invocation_time: jiff::Timestamp,
259 target_dir: Option<Filesystem>,
261 env: Env,
263 updated_sources: Mutex<HashSet<SourceId>>,
265 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
268 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
270 package_cache_lock: CacheLocker,
272 http_config: OnceLock<CargoHttpConfig>,
274 http_async: OnceLock<http_async::Client>,
275 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
276 net_config: OnceLock<CargoNetConfig>,
277 build_config: OnceLock<CargoBuildConfig>,
278 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
279 doc_extern_map: OnceLock<RustdocExternMap>,
280 progress_config: ProgressConfig,
281 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
282 pub nightly_features_allowed: bool,
298 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
300 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
302 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
305}
306
307impl GlobalContext {
308 pub fn new(mut shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
316 static GLOBAL_JOBSERVER: LazyLock<CargoResult<Option<jobserver::Client>>> = LazyLock::new(
317 || {
318 use jobserver::FromEnvErrorKind;
319 let jobserver::FromEnv { client, var } =
325 unsafe { jobserver::Client::from_env_ext(true) };
326
327 match client {
328 Ok(client) => return Ok(Some(client)),
329 Err(e)
330 if matches!(
331 e.kind(),
332 FromEnvErrorKind::NoEnvVar
333 | FromEnvErrorKind::NoJobserver
334 | FromEnvErrorKind::NegativeFd
335 | FromEnvErrorKind::Unsupported
336 ) =>
337 {
338 Ok(None)
339 }
340 Err(e) => {
341 let (name, value) = var.unwrap();
342 Err(anyhow::anyhow!(
343 "failed to connect to jobserver from environment variable `{name}={value:?}`: {e}"
344 ))
345 }
346 }
347 },
348 );
349 let jobserver = match &*GLOBAL_JOBSERVER {
350 Ok(jobserver) => jobserver.as_ref(),
351 Err(e) => {
352 let _ = shell.warn(e);
353 None
354 }
355 };
356
357 let env = Env::new();
358
359 let cache_key = "CARGO_CACHE_RUSTC_INFO";
360 let cache_rustc_info = match env.get_env_os(cache_key) {
361 Some(cache) => cache != "0",
362 _ => true,
363 };
364
365 #[expect(
366 clippy::disallowed_methods,
367 reason = "testing only, no reason for config support"
368 )]
369 let invocation_time = match env::var("__CARGO_TEST_INVOCATION_TIME") {
370 Ok(now) => now.parse().unwrap(),
371 Err(_) => jiff::Timestamp::now(),
372 };
373
374 GlobalContext {
375 home_path: Filesystem::new(homedir),
376 shell: Mutex::new(shell),
377 cwd,
378 search_stop_path: None,
379 values: Default::default(),
380 credential_values: Default::default(),
381 cli_config: None,
382 cargo_exe: Default::default(),
383 rustdoc: Default::default(),
384 extra_verbose: false,
385 frozen: false,
386 locked: false,
387 offline: false,
388 jobserver,
389 unstable_flags: CliUnstable::default(),
390 unstable_flags_cli: None,
391 easy: Default::default(),
392 crates_io_source_id: Default::default(),
393 cache_rustc_info,
394 invocation_instant: Instant::now(),
395 invocation_time,
396 target_dir: None,
397 env,
398 updated_sources: Default::default(),
399 credential_cache: Default::default(),
400 registry_config: Default::default(),
401 package_cache_lock: CacheLocker::new(),
402 http_config: Default::default(),
403 http_async: Default::default(),
404 future_incompat_config: Default::default(),
405 net_config: Default::default(),
406 build_config: Default::default(),
407 target_cfgs: Default::default(),
408 doc_extern_map: Default::default(),
409 progress_config: ProgressConfig::default(),
410 env_config: Default::default(),
411 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
412 ws_roots: Default::default(),
413 global_cache_tracker: Default::default(),
414 deferred_global_last_use: Default::default(),
415 }
416 }
417
418 pub fn default() -> CargoResult<GlobalContext> {
423 let shell = Shell::new();
424 let cwd =
425 env::current_dir().context("couldn't get the current directory of the process")?;
426 let homedir = homedir(&cwd).ok_or_else(|| {
427 anyhow!(
428 "Cargo couldn't find your home directory. \
429 This probably means that $HOME was not set."
430 )
431 })?;
432 Ok(GlobalContext::new(shell, cwd, homedir))
433 }
434
435 pub fn home(&self) -> &Filesystem {
437 &self.home_path
438 }
439
440 pub fn diagnostic_home_config(&self) -> String {
444 let home = self.home_path.as_path_unlocked();
445 let path = match self.get_file_path(home, "config", false) {
446 Ok(Some(existing_path)) => existing_path,
447 _ => home.join("config.toml"),
448 };
449 path.to_string_lossy().to_string()
450 }
451
452 pub fn git_path(&self) -> Filesystem {
454 self.home_path.join("git")
455 }
456
457 pub fn git_checkouts_path(&self) -> Filesystem {
460 self.git_path().join("checkouts")
461 }
462
463 pub fn git_db_path(&self) -> Filesystem {
466 self.git_path().join("db")
467 }
468
469 pub fn registry_base_path(&self) -> Filesystem {
471 self.home_path.join("registry")
472 }
473
474 pub fn registry_index_path(&self) -> Filesystem {
476 self.registry_base_path().join("index")
477 }
478
479 pub fn registry_cache_path(&self) -> Filesystem {
481 self.registry_base_path().join("cache")
482 }
483
484 pub fn registry_source_path(&self) -> Filesystem {
486 self.registry_base_path().join("src")
487 }
488
489 pub fn default_registry(&self) -> CargoResult<Option<String>> {
491 Ok(self
492 .get_string("registry.default")?
493 .map(|registry| registry.val))
494 }
495
496 pub fn shell(&self) -> MutexGuard<'_, Shell> {
498 self.shell.lock().unwrap()
499 }
500
501 pub fn debug_assert_shell_not_borrowed(&self) {
507 if cfg!(debug_assertions) {
508 match self.shell.try_lock() {
509 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
510 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
511 }
512 }
513 }
514
515 pub fn rustdoc(&self) -> CargoResult<&Path> {
517 self.rustdoc
518 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
519 .map(AsRef::as_ref)
520 }
521
522 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
524 let cache_location =
525 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
526 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
527 let rustc_workspace_wrapper = self.maybe_get_tool(
528 "rustc_workspace_wrapper",
529 &self.build_config()?.rustc_workspace_wrapper,
530 );
531
532 Rustc::new(
533 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
534 wrapper,
535 rustc_workspace_wrapper,
536 &self
537 .home()
538 .join("bin")
539 .join("rustc")
540 .into_path_unlocked()
541 .with_extension(env::consts::EXE_EXTENSION),
542 if self.cache_rustc_info {
543 cache_location
544 } else {
545 None
546 },
547 self,
548 )
549 }
550
551 pub fn cargo_exe(&self) -> CargoResult<&Path> {
553 self.cargo_exe
554 .try_borrow_with(|| {
555 let from_env = || -> CargoResult<PathBuf> {
556 let exe = self
561 .get_env_os(crate::CARGO_ENV)
562 .map(PathBuf::from)
563 .ok_or_else(|| anyhow!("$CARGO not set"))?;
564 Ok(exe)
565 };
566
567 fn from_current_exe() -> CargoResult<PathBuf> {
568 let exe = env::current_exe()?;
573 Ok(exe)
574 }
575
576 fn from_argv() -> CargoResult<PathBuf> {
577 let argv0 = env::args_os()
584 .map(PathBuf::from)
585 .next()
586 .ok_or_else(|| anyhow!("no argv[0]"))?;
587 paths::resolve_executable(&argv0)
588 }
589
590 fn is_cargo(path: &Path) -> bool {
593 path.file_stem() == Some(OsStr::new("cargo"))
594 }
595
596 let from_current_exe = from_current_exe();
597 if from_current_exe.as_deref().is_ok_and(is_cargo) {
598 return from_current_exe;
599 }
600
601 let from_argv = from_argv();
602 if from_argv.as_deref().is_ok_and(is_cargo) {
603 return from_argv;
604 }
605
606 let exe = from_env()
607 .or(from_current_exe)
608 .or(from_argv)
609 .context("couldn't get the path to cargo executable")?;
610 Ok(exe)
611 })
612 .map(AsRef::as_ref)
613 }
614
615 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
617 self.updated_sources.lock().unwrap()
618 }
619
620 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
622 self.credential_cache.lock().unwrap()
623 }
624
625 pub(crate) fn registry_config(
627 &self,
628 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
629 self.registry_config.lock().unwrap()
630 }
631
632 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
638 self.values.try_borrow_with(|| self.load_values())
639 }
640
641 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
648 let _ = self.values()?;
649 Ok(self.values.get_mut().expect("already loaded config values"))
650 }
651
652 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
654 if self.values.get().is_some() {
655 bail!("config values already found")
656 }
657 match self.values.set(values.into()) {
658 Ok(()) => Ok(()),
659 Err(_) => bail!("could not fill values"),
660 }
661 }
662
663 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
666 let path = path.into();
667 debug_assert!(self.cwd.starts_with(&path));
668 self.search_stop_path = Some(path);
669 }
670
671 pub fn reload_cwd(&mut self) -> CargoResult<()> {
675 let cwd =
676 env::current_dir().context("couldn't get the current directory of the process")?;
677 let homedir = homedir(&cwd).ok_or_else(|| {
678 anyhow!(
679 "Cargo couldn't find your home directory. \
680 This probably means that $HOME was not set."
681 )
682 })?;
683
684 self.cwd = cwd;
685 self.home_path = Filesystem::new(homedir);
686 self.reload_rooted_at(self.cwd.clone())?;
687 Ok(())
688 }
689
690 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
693 let values = self.load_values_from(path.as_ref())?;
694 self.values.replace(values);
695 self.merge_cli_args()?;
696 self.load_unstable_flags_from_config()?;
697 Ok(())
698 }
699
700 pub fn cwd(&self) -> &Path {
702 &self.cwd
703 }
704
705 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
711 if let Some(dir) = &self.target_dir {
712 Ok(Some(dir.clone()))
713 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
714 if dir.is_empty() {
716 bail!(
717 "the target directory is set to an empty string in the \
718 `CARGO_TARGET_DIR` environment variable"
719 )
720 }
721
722 Ok(Some(Filesystem::new(self.cwd.join(dir))))
723 } else if let Some(val) = &self.build_config()?.target_dir {
724 let path = val.resolve_path(self);
725
726 if val.raw_value().is_empty() {
728 bail!(
729 "the target directory is set to an empty string in {}",
730 val.value().definition
731 )
732 }
733
734 Ok(Some(Filesystem::new(path)))
735 } else {
736 Ok(None)
737 }
738 }
739
740 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
744 let Some(val) = &self.build_config()?.build_dir else {
745 return Ok(None);
746 };
747 self.custom_build_dir(val, workspace_manifest_path)
748 .map(Some)
749 }
750
751 pub fn custom_build_dir(
755 &self,
756 val: &ConfigRelativePath,
757 workspace_manifest_path: &Path,
758 ) -> CargoResult<Filesystem> {
759 let replacements = [
760 (
761 "{workspace-root}",
762 workspace_manifest_path
763 .parent()
764 .unwrap()
765 .to_str()
766 .context("workspace root was not valid utf-8")?
767 .to_string(),
768 ),
769 (
770 "{cargo-cache-home}",
771 self.home()
772 .as_path_unlocked()
773 .to_str()
774 .context("cargo home was not valid utf-8")?
775 .to_string(),
776 ),
777 ("{workspace-path-hash}", {
778 let real_path = std::fs::canonicalize(workspace_manifest_path)
779 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
780 let hash = crate::util::hex::short_hash(&real_path);
781 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
782 }),
783 ];
784
785 let template_variables = replacements
786 .iter()
787 .map(|(key, _)| key[1..key.len() - 1].to_string())
788 .collect_vec();
789
790 let path = val
791 .resolve_templated_path(self, replacements)
792 .map_err(|e| match e {
793 path::ResolveTemplateError::UnexpectedVariable {
794 variable,
795 raw_template,
796 } => {
797 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
798 if suggestion == "" {
799 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
800 suggestion = format!("\n\nhelp: available template variables are {variables}");
801 }
802 anyhow!(
803 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
804 )
805 }
806 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
807 let (btype, literal) = match bracket_type {
808 path::BracketType::Opening => ("opening", "{"),
809 path::BracketType::Closing => ("closing", "}"),
810 };
811
812 anyhow!(
813 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
814 )
815 }
816 })?;
817
818 if val.raw_value().is_empty() {
820 bail!(
821 "the build directory is set to an empty string in {}",
822 val.value().definition
823 )
824 }
825
826 Ok(Filesystem::new(path))
827 }
828
829 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
834 if let Some(vals) = self.credential_values.get() {
835 let val = self.get_cv_helper(key, vals)?;
836 if val.is_some() {
837 return Ok(val);
838 }
839 }
840 self.get_cv_helper(key, &*self.values()?)
841 }
842
843 fn get_cv_helper(
844 &self,
845 key: &ConfigKey,
846 vals: &HashMap<String, ConfigValue>,
847 ) -> CargoResult<Option<ConfigValue>> {
848 tracing::trace!("get cv {:?}", key);
849 if key.is_root() {
850 return Ok(Some(CV::Table(
853 vals.clone(),
854 Definition::Path(PathBuf::new()),
855 )));
856 }
857 let mut parts = key.parts().enumerate();
858 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
859 return Ok(None);
860 };
861 for (i, part) in parts {
862 match val {
863 CV::Table(map, _) => {
864 val = match map.get(part) {
865 Some(val) => val,
866 None => return Ok(None),
867 }
868 }
869 CV::Integer(_, def)
870 | CV::String(_, def)
871 | CV::List(_, def)
872 | CV::Boolean(_, def) => {
873 let mut key_so_far = ConfigKey::new();
874 for part in key.parts().take(i) {
875 key_so_far.push(part);
876 }
877 bail!(
878 "expected table for configuration key `{}`, \
879 but found {} in {}",
880 key_so_far,
881 val.desc(),
882 def
883 )
884 }
885 }
886 }
887 Ok(Some(val.clone()))
888 }
889
890 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
892 let cv = self.get_cv(key)?;
895 if key.is_root() {
896 return Ok(cv);
898 }
899 let env = self.env.get_str(key.as_env_key());
900 let env_def = Definition::Environment(key.as_env_key().to_string());
901 let use_env = match (&cv, env) {
902 (Some(CV::List(..)), Some(_)) => true,
904 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
905 (None, Some(_)) => true,
906 _ => false,
907 };
908
909 if !use_env {
910 return Ok(cv);
911 }
912
913 let env = env.unwrap();
917 if env == "true" {
918 Ok(Some(CV::Boolean(true, env_def)))
919 } else if env == "false" {
920 Ok(Some(CV::Boolean(false, env_def)))
921 } else if let Ok(i) = env.parse::<i64>() {
922 Ok(Some(CV::Integer(i, env_def)))
923 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
924 match cv {
925 Some(CV::List(mut cv_list, cv_def)) => {
926 self.get_env_list(key, &mut cv_list)?;
928 Ok(Some(CV::List(cv_list, cv_def)))
929 }
930 Some(cv) => {
931 bail!(
935 "unable to merge array env for config `{}`\n\
936 file: {:?}\n\
937 env: {}",
938 key,
939 cv,
940 env
941 );
942 }
943 None => {
944 let mut cv_list = Vec::new();
945 self.get_env_list(key, &mut cv_list)?;
946 Ok(Some(CV::List(cv_list, env_def)))
947 }
948 }
949 } else {
950 match cv {
952 Some(CV::List(mut cv_list, cv_def)) => {
953 self.get_env_list(key, &mut cv_list)?;
955 Ok(Some(CV::List(cv_list, cv_def)))
956 }
957 _ => {
958 Ok(Some(CV::String(env.to_string(), env_def)))
963 }
964 }
965 }
966 }
967
968 pub fn set_env(&mut self, env: HashMap<String, String>) {
970 self.env = Env::from_map(env);
971 }
972
973 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
976 self.env.iter_str()
977 }
978
979 fn env_keys(&self) -> impl Iterator<Item = &str> {
981 self.env.keys_str()
982 }
983
984 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
985 where
986 T: FromStr,
987 <T as FromStr>::Err: fmt::Display,
988 {
989 match self.env.get_str(key.as_env_key()) {
990 Some(value) => {
991 let definition = Definition::Environment(key.as_env_key().to_string());
992 Ok(Some(Value {
993 val: value
994 .parse()
995 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
996 definition,
997 }))
998 }
999 None => {
1000 self.check_environment_key_case_mismatch(key);
1001 Ok(None)
1002 }
1003 }
1004 }
1005
1006 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
1011 self.env.get_env(key)
1012 }
1013
1014 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
1019 self.env.get_env_os(key)
1020 }
1021
1022 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
1026 if self.env.contains_key(key.as_env_key()) {
1027 return Ok(true);
1028 }
1029 if env_prefix_ok {
1030 let env_prefix = format!("{}_", key.as_env_key());
1031 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
1032 return Ok(true);
1033 }
1034 }
1035 if self.get_cv(key)?.is_some() {
1036 return Ok(true);
1037 }
1038 self.check_environment_key_case_mismatch(key);
1039
1040 Ok(false)
1041 }
1042
1043 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1044 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1045 let _ = self.shell().warn(format!(
1046 "environment variables are expected to use uppercase letters and underscores, \
1047 the variable `{}` will be ignored and have no effect",
1048 env_key
1049 ));
1050 }
1051 }
1052
1053 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
1057 self.get::<OptValue<String>>(key)
1058 }
1059
1060 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1061 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1062 if is_path {
1063 definition.root(self.cwd()).join(value)
1064 } else {
1065 PathBuf::from(value)
1067 }
1068 }
1069
1070 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1073 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1074 self.check_environment_key_case_mismatch(key);
1075 return Ok(());
1076 };
1077
1078 let env_def = Definition::Environment(key.as_env_key().to_string());
1079
1080 if is_nonmergeable_list(&key) {
1081 assert!(
1082 output
1083 .windows(2)
1084 .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1085 "non-mergeable list must have only one definition: {output:?}",
1086 );
1087
1088 if output
1091 .first()
1092 .map(|o| o.definition() > &env_def)
1093 .unwrap_or_default()
1094 {
1095 return Ok(());
1096 } else {
1097 output.clear();
1098 }
1099 }
1100
1101 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1102 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1104 ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1105 })?;
1106 let values = toml_v.as_array().expect("env var was not array");
1107 for value in values {
1108 let s = value.as_str().ok_or_else(|| {
1111 ConfigError::new(
1112 format!("expected string, found {}", value.type_str()),
1113 env_def.clone(),
1114 )
1115 })?;
1116 output.push(CV::String(s.to_string(), env_def.clone()))
1117 }
1118 } else {
1119 output.extend(
1120 env_val
1121 .split_whitespace()
1122 .map(|s| CV::String(s.to_string(), env_def.clone())),
1123 );
1124 }
1125 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1126 Ok(())
1127 }
1128
1129 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1133 match self.get_cv(key)? {
1134 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1135 Some(val) => self.expected("table", key, &val),
1136 None => Ok(None),
1137 }
1138 }
1139
1140 get_value_typed! {get_integer, i64, Integer, "an integer"}
1141 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1142 get_value_typed! {get_string_priv, String, String, "a string"}
1143
1144 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1146 val.expected(ty, &key.to_string())
1147 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1148 }
1149
1150 pub fn configure(
1156 &mut self,
1157 verbose: u32,
1158 quiet: bool,
1159 color: Option<&str>,
1160 frozen: bool,
1161 locked: bool,
1162 offline: bool,
1163 target_dir: &Option<PathBuf>,
1164 unstable_flags: &[String],
1165 cli_config: &[String],
1166 ) -> CargoResult<()> {
1167 for warning in self
1168 .unstable_flags
1169 .parse(unstable_flags, self.nightly_features_allowed)?
1170 {
1171 self.shell().warn(warning)?;
1172 }
1173 if !unstable_flags.is_empty() {
1174 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1177 }
1178 if !cli_config.is_empty() {
1179 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1180 self.merge_cli_args()?;
1181 }
1182
1183 self.load_unstable_flags_from_config()?;
1184
1185 let term = self.get::<TermConfig>("term").unwrap_or_default();
1189
1190 let extra_verbose = verbose >= 2;
1192 let verbose = verbose != 0;
1193 let verbosity = match (verbose, quiet) {
1194 (true, true) => bail!("cannot set both --verbose and --quiet"),
1195 (true, false) => Verbosity::Verbose,
1196 (false, true) => Verbosity::Quiet,
1197 (false, false) => match (term.verbose, term.quiet) {
1198 (Some(true), Some(true)) => {
1199 bail!("cannot set both `term.verbose` and `term.quiet`")
1200 }
1201 (Some(true), _) => Verbosity::Verbose,
1202 (_, Some(true)) => Verbosity::Quiet,
1203 _ => Verbosity::Normal,
1204 },
1205 };
1206 self.shell().set_verbosity(verbosity);
1207 self.extra_verbose = extra_verbose;
1208
1209 let color = color.or_else(|| term.color.as_deref());
1210 self.shell().set_color_choice(color)?;
1211 if let Some(hyperlinks) = term.hyperlinks {
1212 self.shell().set_hyperlinks(hyperlinks)?;
1213 }
1214 if let Some(unicode) = term.unicode {
1215 self.shell().set_unicode(unicode)?;
1216 }
1217
1218 self.progress_config = term.progress.unwrap_or_default();
1219
1220 self.frozen = frozen;
1221 self.locked = locked;
1222 self.offline = offline
1223 || self
1224 .net_config()
1225 .ok()
1226 .and_then(|n| n.offline)
1227 .unwrap_or(false);
1228 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1229 self.target_dir = cli_target_dir;
1230
1231 self.shell()
1232 .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1233
1234 Ok(())
1235 }
1236
1237 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1238 if self.nightly_features_allowed {
1241 self.unstable_flags = self
1242 .get::<Option<CliUnstable>>("unstable")?
1243 .unwrap_or_default();
1244 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1245 self.unstable_flags.parse(unstable_flags_cli, true)?;
1250 }
1251 }
1252
1253 Ok(())
1254 }
1255
1256 pub fn cli_unstable(&self) -> &CliUnstable {
1257 &self.unstable_flags
1258 }
1259
1260 pub fn extra_verbose(&self) -> bool {
1261 self.extra_verbose
1262 }
1263
1264 pub fn network_allowed(&self) -> bool {
1265 !self.offline_flag().is_some()
1266 }
1267
1268 pub fn offline_flag(&self) -> Option<&'static str> {
1269 if self.frozen {
1270 Some("--frozen")
1271 } else if self.offline {
1272 Some("--offline")
1273 } else {
1274 None
1275 }
1276 }
1277
1278 pub fn set_locked(&mut self, locked: bool) {
1279 self.locked = locked;
1280 }
1281
1282 pub fn lock_update_allowed(&self) -> bool {
1283 !self.locked_flag().is_some()
1284 }
1285
1286 pub fn locked_flag(&self) -> Option<&'static str> {
1287 if self.frozen {
1288 Some("--frozen")
1289 } else if self.locked {
1290 Some("--locked")
1291 } else {
1292 None
1293 }
1294 }
1295
1296 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1298 self.load_values_from(&self.cwd)
1299 }
1300
1301 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1305 let mut result = Vec::new();
1306 let mut seen = HashSet::default();
1307 let home = self.home_path.clone().into_path_unlocked();
1308 self.walk_tree(&self.cwd, &home, |path| {
1309 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1310 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1311 result.push(cv);
1312 Ok(())
1313 })
1314 .context("could not load Cargo configuration")?;
1315 Ok(result)
1316 }
1317
1318 fn load_unmerged_include(
1322 &self,
1323 cv: &mut CV,
1324 seen: &mut HashSet<PathBuf>,
1325 output: &mut Vec<CV>,
1326 ) -> CargoResult<()> {
1327 let includes = self.include_paths(cv, false)?;
1328 for include in includes {
1329 let Some(abs_path) = include.resolve_path(self) else {
1330 continue;
1331 };
1332
1333 let mut cv = self
1334 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1335 .with_context(|| {
1336 format!(
1337 "failed to load config include `{}` from `{}`",
1338 include.path.display(),
1339 include.def
1340 )
1341 })?;
1342 self.load_unmerged_include(&mut cv, seen, output)?;
1343 output.push(cv);
1344 }
1345 Ok(())
1346 }
1347
1348 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1350 let mut cfg = CV::Table(HashMap::default(), Definition::BuiltIn);
1353 let home = self.home_path.clone().into_path_unlocked();
1354
1355 self.walk_tree(path, &home, |path| {
1356 let value = self.load_file(path)?;
1357 cfg.merge(value, false).with_context(|| {
1358 format!("failed to merge configuration at `{}`", path.display())
1359 })?;
1360 Ok(())
1361 })
1362 .context("could not load Cargo configuration")?;
1363
1364 match cfg {
1365 CV::Table(map, _) => Ok(map),
1366 _ => unreachable!(),
1367 }
1368 }
1369
1370 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1374 self._load_file(path, &mut HashSet::default(), true, WhyLoad::FileDiscovery)
1375 }
1376
1377 fn _load_file(
1385 &self,
1386 path: &Path,
1387 seen: &mut HashSet<PathBuf>,
1388 includes: bool,
1389 why_load: WhyLoad,
1390 ) -> CargoResult<ConfigValue> {
1391 if !seen.insert(path.to_path_buf()) {
1392 bail!(
1393 "config `include` cycle detected with path `{}`",
1394 path.display()
1395 );
1396 }
1397 tracing::debug!(?path, ?why_load, includes, "load config from file");
1398
1399 let contents = fs::read_to_string(path)
1400 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1401 let toml = parse_document(&contents, path, self).with_context(|| {
1402 format!("could not parse TOML configuration in `{}`", path.display())
1403 })?;
1404 let def = match why_load {
1405 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1406 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1407 };
1408 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1409 format!(
1410 "failed to load TOML configuration from `{}`",
1411 path.display()
1412 )
1413 })?;
1414 if includes {
1415 self.load_includes(value, seen, why_load)
1416 } else {
1417 Ok(value)
1418 }
1419 }
1420
1421 fn load_includes(
1428 &self,
1429 mut value: CV,
1430 seen: &mut HashSet<PathBuf>,
1431 why_load: WhyLoad,
1432 ) -> CargoResult<CV> {
1433 let includes = self.include_paths(&mut value, true)?;
1435
1436 let mut root = CV::Table(HashMap::default(), value.definition().clone());
1438 for include in includes {
1439 let Some(abs_path) = include.resolve_path(self) else {
1440 continue;
1441 };
1442
1443 self._load_file(&abs_path, seen, true, why_load)
1444 .and_then(|include| root.merge(include, true))
1445 .with_context(|| {
1446 format!(
1447 "failed to load config include `{}` from `{}`",
1448 include.path.display(),
1449 include.def
1450 )
1451 })?;
1452 }
1453 root.merge(value, true)?;
1454 Ok(root)
1455 }
1456
1457 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1459 let CV::Table(table, _def) = cv else {
1460 unreachable!()
1461 };
1462 let include = if remove {
1463 table.remove("include").map(Cow::Owned)
1464 } else {
1465 table.get("include").map(Cow::Borrowed)
1466 };
1467 let includes = match include.map(|c| c.into_owned()) {
1468 Some(CV::List(list, _def)) => list
1469 .into_iter()
1470 .enumerate()
1471 .map(|(idx, cv)| match cv {
1472 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1473 CV::Table(mut table, def) => {
1474 let s = match table.remove("path") {
1476 Some(CV::String(s, _)) => s,
1477 Some(other) => bail!(
1478 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1479 other.desc()
1480 ),
1481 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1482 };
1483
1484 let optional = match table.remove("optional") {
1486 Some(CV::Boolean(b, _)) => b,
1487 Some(other) => bail!(
1488 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1489 other.desc()
1490 ),
1491 None => false,
1492 };
1493
1494 let mut include = ConfigInclude::new(s, def);
1495 include.optional = optional;
1496 Ok(include)
1497 }
1498 other => bail!(
1499 "expected a string or table, but found {} at `include[{idx}]` in {}",
1500 other.desc(),
1501 other.definition(),
1502 ),
1503 })
1504 .collect::<CargoResult<Vec<_>>>()?,
1505 Some(other) => bail!(
1506 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1507 other.desc(),
1508 other.definition()
1509 ),
1510 None => {
1511 return Ok(Vec::new());
1512 }
1513 };
1514
1515 for include in &includes {
1516 if include.path.extension() != Some(OsStr::new("toml")) {
1517 bail!(
1518 "expected a config include path ending with `.toml`, \
1519 but found `{}` from `{}`",
1520 include.path.display(),
1521 include.def,
1522 )
1523 }
1524
1525 if let Some(path) = include.path.to_str() {
1526 if is_glob_pattern(path) {
1528 bail!(
1529 "expected a config include path without glob patterns, \
1530 but found `{}` from `{}`",
1531 include.path.display(),
1532 include.def,
1533 )
1534 }
1535 if path.contains(&['{', '}']) {
1536 bail!(
1537 "expected a config include path without template braces, \
1538 but found `{}` from `{}`",
1539 include.path.display(),
1540 include.def,
1541 )
1542 }
1543 }
1544 }
1545
1546 Ok(includes)
1547 }
1548
1549 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1551 let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1552 let Some(cli_args) = &self.cli_config else {
1553 return Ok(loaded_args);
1554 };
1555 let mut seen = HashSet::default();
1556 for arg in cli_args {
1557 let arg_as_path = self.cwd.join(arg);
1558 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1559 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1561 .with_context(|| {
1562 format!("failed to load config from `{}`", arg_as_path.display())
1563 })?
1564 } else {
1565 let doc = toml_dotted_keys(arg)?;
1566 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1567 .with_context(|| {
1568 format!("failed to parse value from --config argument `{arg}`")
1569 })?;
1570
1571 if doc
1572 .get("registry")
1573 .and_then(|v| v.as_table())
1574 .and_then(|t| t.get("token"))
1575 .is_some()
1576 {
1577 bail!("registry.token cannot be set through --config for security reasons");
1578 } else if let Some((k, _)) = doc
1579 .get("registries")
1580 .and_then(|v| v.as_table())
1581 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1582 {
1583 bail!(
1584 "registries.{}.token cannot be set through --config for security reasons",
1585 k
1586 );
1587 }
1588
1589 if doc
1590 .get("registry")
1591 .and_then(|v| v.as_table())
1592 .and_then(|t| t.get("secret-key"))
1593 .is_some()
1594 {
1595 bail!(
1596 "registry.secret-key cannot be set through --config for security reasons"
1597 );
1598 } else if let Some((k, _)) = doc
1599 .get("registries")
1600 .and_then(|v| v.as_table())
1601 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1602 {
1603 bail!(
1604 "registries.{}.secret-key cannot be set through --config for security reasons",
1605 k
1606 );
1607 }
1608
1609 CV::from_toml(Definition::Cli(None), doc)
1610 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1611 };
1612 let tmp_table = self
1613 .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1614 .context("failed to load --config include".to_string())?;
1615 loaded_args
1616 .merge(tmp_table, true)
1617 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1618 }
1619 Ok(loaded_args)
1620 }
1621
1622 fn merge_cli_args(&mut self) -> CargoResult<()> {
1624 let cv_from_cli = self.cli_args_as_table()?;
1625 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1626
1627 let root_cv = mem::take(self.values_mut()?);
1628 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1631 root_cv.merge(cv_from_cli, true)?;
1632
1633 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1635
1636 Ok(())
1637 }
1638
1639 fn get_file_path(
1645 &self,
1646 dir: &Path,
1647 filename_without_extension: &str,
1648 warn: bool,
1649 ) -> CargoResult<Option<PathBuf>> {
1650 let possible = dir.join(filename_without_extension);
1651 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1652
1653 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1654 if warn {
1655 if let Ok(possible_with_extension_handle) =
1656 same_file::Handle::from_path(&possible_with_extension)
1657 {
1658 if possible_handle != possible_with_extension_handle {
1664 self.shell().warn(format!(
1665 "both `{}` and `{}` exist. Using `{}`",
1666 possible.display(),
1667 possible_with_extension.display(),
1668 possible.display()
1669 ))?;
1670 }
1671 } else {
1672 self.shell().print_report(&[
1673 Level::WARNING.secondary_title(
1674 format!(
1675 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1676 possible.display(),
1677 )).element(Level::HELP.message(
1678 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1679 ], false)?;
1680 }
1681 }
1682
1683 Ok(Some(possible))
1684 } else if possible_with_extension.exists() {
1685 Ok(Some(possible_with_extension))
1686 } else {
1687 Ok(None)
1688 }
1689 }
1690
1691 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1692 where
1693 F: FnMut(&Path) -> CargoResult<()>,
1694 {
1695 let mut seen_dir = HashSet::default();
1696
1697 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1698 let config_root = current.join(".cargo");
1699 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1700 walk(&path)?;
1701 }
1702
1703 let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1704 seen_dir.insert(canonical_root);
1705 }
1706
1707 let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1708
1709 if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1713 if let Some(path) = self.get_file_path(home, "config", true)? {
1714 walk(&path)?;
1715 }
1716 }
1717
1718 Ok(())
1719 }
1720
1721 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1723 RegistryName::new(registry)?;
1724 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1725 self.resolve_registry_index(&index).with_context(|| {
1726 format!(
1727 "invalid index URL for registry `{}` defined in {}",
1728 registry, index.definition
1729 )
1730 })
1731 } else {
1732 bail!(
1733 "registry index was not found in any configuration: `{}`",
1734 registry
1735 );
1736 }
1737 }
1738
1739 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1741 if self.get_string("registry.index")?.is_some() {
1742 bail!(
1743 "the `registry.index` config value is no longer supported\n\
1744 Use `[source]` replacement to alter the default index for crates.io."
1745 );
1746 }
1747 Ok(())
1748 }
1749
1750 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1751 let base = index
1753 .definition
1754 .root(self.cwd())
1755 .join("truncated-by-url_with_base");
1756 let _parsed = index.val.into_url()?;
1758 let url = index.val.into_url_with_base(Some(&*base))?;
1759 if url.password().is_some() {
1760 bail!("registry URLs may not contain passwords");
1761 }
1762 Ok(url)
1763 }
1764
1765 pub fn load_credentials(&self) -> CargoResult<()> {
1773 if self.credential_values.filled() {
1774 return Ok(());
1775 }
1776
1777 let home_path = self.home_path.clone().into_path_unlocked();
1778 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1779 return Ok(());
1780 };
1781
1782 let mut value = self.load_file(&credentials)?;
1783 {
1785 let (value_map, def) = value.table_mut("<root>")?;
1786
1787 if let Some(token) = value_map.remove("token") {
1788 value_map.entry("registry".into()).or_insert_with(|| {
1789 let map = HashMap::from_iter([("token".into(), token)]);
1790 CV::Table(map, def.clone())
1791 });
1792 }
1793 }
1794
1795 let mut credential_values = HashMap::default();
1796 if let CV::Table(map, _) = value {
1797 let base_map = self.values()?;
1798 for (k, v) in map {
1799 let entry = match base_map.get(&k) {
1800 Some(base_entry) => {
1801 let mut entry = base_entry.clone();
1802 entry.merge(v, true)?;
1803 entry
1804 }
1805 None => v,
1806 };
1807 credential_values.insert(k, entry);
1808 }
1809 }
1810 self.credential_values
1811 .set(credential_values)
1812 .expect("was not filled at beginning of the function");
1813 Ok(())
1814 }
1815
1816 fn maybe_get_tool(
1819 &self,
1820 tool: &str,
1821 from_config: &Option<ConfigRelativePath>,
1822 ) -> Option<PathBuf> {
1823 let var = tool.to_uppercase();
1824
1825 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1826 Some(tool_path) => {
1827 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1828 let path = if maybe_relative {
1829 self.cwd.join(tool_path)
1830 } else {
1831 PathBuf::from(tool_path)
1832 };
1833 Some(path)
1834 }
1835
1836 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1837 }
1838 }
1839
1840 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1851 let tool_str = tool.as_str();
1852 self.maybe_get_tool(tool_str, from_config)
1853 .or_else(|| {
1854 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1868 if toolchain.to_str()?.contains(&['/', '\\']) {
1871 return None;
1872 }
1873 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1876 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1877 let tool_meta = tool_resolved.metadata().ok()?;
1878 let rustup_meta = rustup_resolved.metadata().ok()?;
1879 if tool_meta.len() != rustup_meta.len() {
1884 return None;
1885 }
1886 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1888 let toolchain_exe = home::rustup_home()
1889 .ok()?
1890 .join("toolchains")
1891 .join(&toolchain)
1892 .join("bin")
1893 .join(&tool_exe);
1894 toolchain_exe.exists().then_some(toolchain_exe)
1895 })
1896 .unwrap_or_else(|| PathBuf::from(tool_str))
1897 }
1898
1899 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1901 let key = ConfigKey::from_str("paths");
1902 match self.get_cv(&key)? {
1904 Some(CV::List(val, definition)) => {
1905 let val = val
1906 .into_iter()
1907 .map(|cv| match cv {
1908 CV::String(s, def) => Ok((s, def)),
1909 other => self.expected("string", &key, &other),
1910 })
1911 .collect::<CargoResult<Vec<_>>>()?;
1912 Ok(Some(Value { val, definition }))
1913 }
1914 Some(val) => self.expected("list", &key, &val),
1915 None => Ok(None),
1916 }
1917 }
1918
1919 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1920 self.jobserver
1921 }
1922
1923 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1924 let http = self
1925 .easy
1926 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1927 {
1928 let mut http = http.lock().unwrap();
1929 http.reset();
1930 let timeout = configure_http_handle(self, &mut http)?;
1931 timeout.configure(&mut http)?;
1932 }
1933 Ok(http)
1934 }
1935
1936 pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1937 self.http_async.try_borrow_with(|| {
1938 let handle_config = HandleConfiguration::new(&self)?;
1939 Ok(http_async::Client::new(handle_config))
1940 })
1941 }
1942
1943 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1944 self.http_config.try_borrow_with(|| {
1945 let mut http = self.get::<CargoHttpConfig>("http")?;
1946 let curl_v = curl::Version::get();
1947 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1948 Ok(http)
1949 })
1950 }
1951
1952 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1953 self.future_incompat_config
1954 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1955 }
1956
1957 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1958 self.net_config
1959 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1960 }
1961
1962 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1963 self.build_config
1964 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1965 }
1966
1967 pub fn progress_config(&self) -> &ProgressConfig {
1968 &self.progress_config
1969 }
1970
1971 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1974 let env_config = self.env_config.try_borrow_with(|| {
1975 CargoResult::Ok(Arc::new({
1976 let env_config = self.get::<EnvConfig>("env")?;
1977 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1993 if env_config.contains_key(*disallowed) {
1994 bail!(
1995 "setting the `{disallowed}` environment variable is not supported \
1996 in the `[env]` configuration table"
1997 );
1998 }
1999 }
2000 env_config
2001 .into_iter()
2002 .filter_map(|(k, v)| {
2003 if v.is_force() || self.get_env_os(&k).is_none() {
2004 Some((k, v.resolve(self.cwd()).to_os_string()))
2005 } else {
2006 None
2007 }
2008 })
2009 .collect()
2010 }))
2011 })?;
2012
2013 Ok(env_config)
2014 }
2015
2016 pub fn validate_term_config(&self) -> CargoResult<()> {
2022 drop(self.get::<TermConfig>("term")?);
2023 Ok(())
2024 }
2025
2026 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2030 self.target_cfgs
2031 .try_borrow_with(|| target::load_target_cfgs(self))
2032 }
2033
2034 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2035 self.doc_extern_map
2039 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2040 }
2041
2042 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2044 target::get_target_applies_to_host(self)
2045 }
2046
2047 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2049 target::load_host_triple(self, target)
2050 }
2051
2052 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2054 target::load_target_triple(self, target)
2055 }
2056
2057 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2062 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2063 self.check_registry_index_not_set()?;
2064 let url = CRATES_IO_INDEX.into_url().unwrap();
2065 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2066 })?;
2067 Ok(*source_id)
2068 }
2069
2070 pub fn invocation_instant(&self) -> Instant {
2071 self.invocation_instant
2072 }
2073
2074 pub fn invocation_time(&self) -> jiff::Timestamp {
2080 self.invocation_time
2081 }
2082
2083 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2098 let d = Deserializer {
2099 gctx: self,
2100 key: ConfigKey::from_str(key),
2101 env_prefix_ok: true,
2102 };
2103 T::deserialize(d).map_err(|e| e.into())
2104 }
2105
2106 #[track_caller]
2112 #[tracing::instrument(skip_all)]
2113 pub fn assert_package_cache_locked<'a>(
2114 &self,
2115 mode: CacheLockMode,
2116 f: &'a Filesystem,
2117 ) -> &'a Path {
2118 let ret = f.as_path_unlocked();
2119 assert!(
2120 self.package_cache_lock.is_locked(mode),
2121 "package cache lock is not currently held, Cargo forgot to call \
2122 `acquire_package_cache_lock` before we got to this stack frame",
2123 );
2124 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2125 ret
2126 }
2127
2128 #[tracing::instrument(skip_all)]
2134 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2135 self.package_cache_lock.lock(self, mode)
2136 }
2137
2138 #[tracing::instrument(skip_all)]
2144 pub fn try_acquire_package_cache_lock(
2145 &self,
2146 mode: CacheLockMode,
2147 ) -> CargoResult<Option<CacheLock<'_>>> {
2148 self.package_cache_lock.try_lock(self, mode)
2149 }
2150
2151 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2156 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2157 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2158 })?;
2159 Ok(tracker.lock().unwrap())
2160 }
2161
2162 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2164 let deferred = self
2165 .deferred_global_last_use
2166 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2167 Ok(deferred.lock().unwrap())
2168 }
2169
2170 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2172 Ok(self.build_config()?.warnings.unwrap_or_default())
2173 }
2174
2175 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2176 self.ws_roots.lock().unwrap()
2177 }
2178}
2179
2180pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2181 ::home::cargo_home_with_cwd(cwd).ok()
2182}
2183
2184pub fn save_credentials(
2185 gctx: &GlobalContext,
2186 token: Option<RegistryCredentialConfig>,
2187 registry: &SourceId,
2188) -> CargoResult<()> {
2189 let registry = if registry.is_crates_io() {
2190 None
2191 } else {
2192 let name = registry
2193 .alt_registry_key()
2194 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2195 Some(name)
2196 };
2197
2198 let home_path = gctx.home_path.clone().into_path_unlocked();
2202 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2203 Some(path) => match path.file_name() {
2204 Some(filename) => Path::new(filename).to_owned(),
2205 None => Path::new("credentials.toml").to_owned(),
2206 },
2207 None => Path::new("credentials.toml").to_owned(),
2208 };
2209
2210 let mut file = {
2211 gctx.home_path.create_dir()?;
2212 gctx.home_path
2213 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2214 };
2215
2216 let mut contents = String::new();
2217 file.read_to_string(&mut contents).with_context(|| {
2218 format!(
2219 "failed to read configuration file `{}`",
2220 file.path().display()
2221 )
2222 })?;
2223
2224 let mut toml = parse_document(&contents, file.path(), gctx)?;
2225
2226 if let Some(token) = toml.remove("token") {
2228 #[expect(
2229 clippy::disallowed_types,
2230 reason = "need stdlib's HashMap because of TOML compatibility"
2231 )]
2232 let map = std::collections::HashMap::from([("token".to_string(), token)]);
2233 toml.insert("registry".into(), map.into());
2234 }
2235
2236 if let Some(token) = token {
2237 let path_def = Definition::Path(file.path().to_path_buf());
2240 let (key, mut value) = match token {
2241 RegistryCredentialConfig::Token(token) => {
2242 let key = "token".to_string();
2245 let value = ConfigValue::String(token.expose(), path_def.clone());
2246 let map = HashMap::from_iter([(key, value)]);
2247 let table = CV::Table(map, path_def.clone());
2248
2249 if let Some(registry) = registry {
2250 let map = HashMap::from_iter([(registry.to_string(), table)]);
2251 ("registries".into(), CV::Table(map, path_def.clone()))
2252 } else {
2253 ("registry".into(), table)
2254 }
2255 }
2256 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2257 let key = "secret-key".to_string();
2260 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2261 let mut map = HashMap::from_iter([(key, value)]);
2262 if let Some(key_subject) = key_subject {
2263 let key = "secret-key-subject".to_string();
2264 let value = ConfigValue::String(key_subject, path_def.clone());
2265 map.insert(key, value);
2266 }
2267 let table = CV::Table(map, path_def.clone());
2268
2269 if let Some(registry) = registry {
2270 let map = HashMap::from_iter([(registry.to_string(), table)]);
2271 ("registries".into(), CV::Table(map, path_def.clone()))
2272 } else {
2273 ("registry".into(), table)
2274 }
2275 }
2276 _ => unreachable!(),
2277 };
2278
2279 if registry.is_some() {
2280 if let Some(table) = toml.remove("registries") {
2281 let v = CV::from_toml(path_def, table)?;
2282 value.merge(v, false)?;
2283 }
2284 }
2285 toml.insert(key, value.into_toml());
2286 } else {
2287 if let Some(registry) = registry {
2289 if let Some(registries) = toml.get_mut("registries") {
2290 if let Some(reg) = registries.get_mut(registry) {
2291 let rtable = reg.as_table_mut().ok_or_else(|| {
2292 format_err!("expected `[registries.{}]` to be a table", registry)
2293 })?;
2294 rtable.remove("token");
2295 rtable.remove("secret-key");
2296 rtable.remove("secret-key-subject");
2297 }
2298 }
2299 } else if let Some(registry) = toml.get_mut("registry") {
2300 let reg_table = registry
2301 .as_table_mut()
2302 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2303 reg_table.remove("token");
2304 reg_table.remove("secret-key");
2305 reg_table.remove("secret-key-subject");
2306 }
2307 }
2308
2309 let contents = toml.to_string();
2310 file.seek(SeekFrom::Start(0))?;
2311 file.write_all(contents.as_bytes())
2312 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2313 file.file().set_len(contents.len() as u64)?;
2314 set_permissions(file.file(), 0o600)
2315 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2316
2317 return Ok(());
2318
2319 #[cfg(unix)]
2320 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2321 use std::os::unix::fs::PermissionsExt;
2322
2323 let mut perms = file.metadata()?.permissions();
2324 perms.set_mode(mode);
2325 file.set_permissions(perms)?;
2326 Ok(())
2327 }
2328
2329 #[cfg(not(unix))]
2330 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2331 Ok(())
2332 }
2333}
2334
2335struct ConfigInclude {
2341 path: PathBuf,
2344 def: Definition,
2345 optional: bool,
2347}
2348
2349impl ConfigInclude {
2350 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2351 Self {
2352 path: p.into(),
2353 def,
2354 optional: false,
2355 }
2356 }
2357
2358 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2371 let abs_path = match &self.def {
2372 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2373 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2374 }
2375 .join(&self.path);
2376 let abs_path = paths::normalize_path(&abs_path);
2377
2378 if self.optional && !abs_path.exists() {
2379 tracing::info!(
2380 "skipping optional include `{}` in `{}`: file not found at `{}`",
2381 self.path.display(),
2382 self.def,
2383 abs_path.display(),
2384 );
2385 None
2386 } else {
2387 Some(abs_path)
2388 }
2389 }
2390}
2391
2392fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2393 toml.parse().map_err(Into::into)
2395}
2396
2397fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2398 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2404 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2405 })?;
2406 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2407 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2408 }
2409 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2410 non_empty(d.prefix()) || non_empty(d.suffix())
2411 }
2412 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2413 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2414 }
2415 let ok = {
2416 let mut got_to_value = false;
2417 let mut table = doc.as_table();
2418 let mut is_root = true;
2419 while table.is_dotted() || is_root {
2420 is_root = false;
2421 if table.len() != 1 {
2422 break;
2423 }
2424 let (k, n) = table.iter().next().expect("len() == 1 above");
2425 match n {
2426 Item::Table(nt) => {
2427 if table.key(k).map_or(false, non_empty_key_decor)
2428 || non_empty_decor(nt.decor())
2429 {
2430 bail!(
2431 "--config argument `{arg}` \
2432 includes non-whitespace decoration"
2433 )
2434 }
2435 table = nt;
2436 }
2437 Item::Value(v) if v.is_inline_table() => {
2438 bail!(
2439 "--config argument `{arg}` \
2440 sets a value to an inline table, which is not accepted"
2441 );
2442 }
2443 Item::Value(v) => {
2444 if table
2445 .key(k)
2446 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2447 || non_empty_decor(v.decor())
2448 {
2449 bail!(
2450 "--config argument `{arg}` \
2451 includes non-whitespace decoration"
2452 )
2453 }
2454 got_to_value = true;
2455 break;
2456 }
2457 Item::ArrayOfTables(_) => {
2458 bail!(
2459 "--config argument `{arg}` \
2460 sets a value to an array of tables, which is not accepted"
2461 );
2462 }
2463
2464 Item::None => {
2465 bail!("--config argument `{arg}` doesn't provide a value")
2466 }
2467 }
2468 }
2469 got_to_value
2470 };
2471 if !ok {
2472 bail!(
2473 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2474 );
2475 }
2476 Ok(doc)
2477}
2478
2479#[derive(Debug, Deserialize, Clone)]
2490pub struct StringList(Vec<String>);
2491
2492impl StringList {
2493 pub fn as_slice(&self) -> &[String] {
2494 &self.0
2495 }
2496}
2497
2498#[macro_export]
2499macro_rules! __shell_print {
2500 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2501 let mut shell = $config.shell();
2502 let out = shell.$which();
2503 drop(out.write_fmt(format_args!($($arg)*)));
2504 if $newline {
2505 drop(out.write_all(b"\n"));
2506 }
2507 });
2508}
2509
2510#[macro_export]
2511macro_rules! drop_println {
2512 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2513 ($config:expr, $($arg:tt)*) => (
2514 $crate::__shell_print!($config, out, true, $($arg)*)
2515 );
2516}
2517
2518#[macro_export]
2519macro_rules! drop_eprintln {
2520 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2521 ($config:expr, $($arg:tt)*) => (
2522 $crate::__shell_print!($config, err, true, $($arg)*)
2523 );
2524}
2525
2526#[macro_export]
2527macro_rules! drop_print {
2528 ($config:expr, $($arg:tt)*) => (
2529 $crate::__shell_print!($config, out, false, $($arg)*)
2530 );
2531}
2532
2533#[macro_export]
2534macro_rules! drop_eprint {
2535 ($config:expr, $($arg:tt)*) => (
2536 $crate::__shell_print!($config, err, false, $($arg)*)
2537 );
2538}
2539
2540enum Tool {
2541 Rustc,
2542 Rustdoc,
2543}
2544
2545impl Tool {
2546 fn as_str(&self) -> &str {
2547 match self {
2548 Tool::Rustc => "rustc",
2549 Tool::Rustdoc => "rustdoc",
2550 }
2551 }
2552}
2553
2554fn disables_multiplexing_for_bad_curl(
2564 curl_version: &str,
2565 http: &mut CargoHttpConfig,
2566 gctx: &GlobalContext,
2567) {
2568 use crate::util::network;
2569
2570 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2571 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2572 if bad_curl_versions
2573 .iter()
2574 .any(|v| curl_version.starts_with(v))
2575 {
2576 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2577 http.multiplexing = Some(false);
2578 }
2579 }
2580}
2581
2582#[cfg(test)]
2583mod tests {
2584 use super::CargoHttpConfig;
2585 use super::GlobalContext;
2586 use super::Shell;
2587 use super::disables_multiplexing_for_bad_curl;
2588
2589 #[test]
2590 fn disables_multiplexing() {
2591 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2592 gctx.set_search_stop_path(std::path::PathBuf::new());
2593 gctx.set_env(Default::default());
2594
2595 let mut http = CargoHttpConfig::default();
2596 http.proxy = Some("127.0.0.1:3128".into());
2597 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2598 assert_eq!(http.multiplexing, Some(false));
2599
2600 let cases = [
2601 (None, None, "7.87.0", None),
2602 (None, None, "7.88.0", None),
2603 (None, None, "7.88.1", None),
2604 (None, None, "8.0.0", None),
2605 (Some("".into()), None, "7.87.0", Some(false)),
2606 (Some("".into()), None, "7.88.0", Some(false)),
2607 (Some("".into()), None, "7.88.1", Some(false)),
2608 (Some("".into()), None, "8.0.0", None),
2609 (Some("".into()), Some(false), "7.87.0", Some(false)),
2610 (Some("".into()), Some(false), "7.88.0", Some(false)),
2611 (Some("".into()), Some(false), "7.88.1", Some(false)),
2612 (Some("".into()), Some(false), "8.0.0", Some(false)),
2613 ];
2614
2615 for (proxy, multiplexing, curl_v, result) in cases {
2616 let mut http = CargoHttpConfig {
2617 multiplexing,
2618 proxy,
2619 ..Default::default()
2620 };
2621 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2622 assert_eq!(http.multiplexing, result);
2623 }
2624 }
2625
2626 #[test]
2627 fn sync_context() {
2628 fn assert_sync<S: Sync>() {}
2629 assert_sync::<GlobalContext>();
2630 }
2631}