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