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 should_embed_metadata(&self) -> bool {
1263 self.cli_unstable().embed_metadata.unwrap_or(true)
1264 }
1265
1266 pub fn network_allowed(&self) -> bool {
1267 !self.offline_flag().is_some()
1268 }
1269
1270 pub fn offline_flag(&self) -> Option<&'static str> {
1271 if self.frozen {
1272 Some("--frozen")
1273 } else if self.offline {
1274 Some("--offline")
1275 } else {
1276 None
1277 }
1278 }
1279
1280 pub fn set_locked(&mut self, locked: bool) {
1281 self.locked = locked;
1282 }
1283
1284 pub fn lock_update_allowed(&self) -> bool {
1285 !self.locked_flag().is_some()
1286 }
1287
1288 pub fn locked_flag(&self) -> Option<&'static str> {
1289 if self.frozen {
1290 Some("--frozen")
1291 } else if self.locked {
1292 Some("--locked")
1293 } else {
1294 None
1295 }
1296 }
1297
1298 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1300 self.load_values_from(&self.cwd)
1301 }
1302
1303 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1307 let mut result = Vec::new();
1308 let mut seen = HashSet::default();
1309 let home = self.home_path.clone().into_path_unlocked();
1310 self.walk_tree(&self.cwd, &home, |path| {
1311 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1312 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1313 result.push(cv);
1314 Ok(())
1315 })
1316 .context("could not load Cargo configuration")?;
1317 Ok(result)
1318 }
1319
1320 fn load_unmerged_include(
1324 &self,
1325 cv: &mut CV,
1326 seen: &mut HashSet<PathBuf>,
1327 output: &mut Vec<CV>,
1328 ) -> CargoResult<()> {
1329 let includes = self.include_paths(cv, false)?;
1330 for include in includes {
1331 let Some(abs_path) = include.resolve_path(self) else {
1332 continue;
1333 };
1334
1335 let mut cv = self
1336 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1337 .with_context(|| {
1338 format!(
1339 "failed to load config include `{}` from `{}`",
1340 include.path.display(),
1341 include.def
1342 )
1343 })?;
1344 self.load_unmerged_include(&mut cv, seen, output)?;
1345 output.push(cv);
1346 }
1347 Ok(())
1348 }
1349
1350 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1352 let mut cfg = CV::Table(HashMap::default(), Definition::BuiltIn);
1355 let home = self.home_path.clone().into_path_unlocked();
1356
1357 self.walk_tree(path, &home, |path| {
1358 let value = self.load_file(path)?;
1359 cfg.merge(value, false).with_context(|| {
1360 format!("failed to merge configuration at `{}`", path.display())
1361 })?;
1362 Ok(())
1363 })
1364 .context("could not load Cargo configuration")?;
1365
1366 match cfg {
1367 CV::Table(map, _) => Ok(map),
1368 _ => unreachable!(),
1369 }
1370 }
1371
1372 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1376 self._load_file(path, &mut HashSet::default(), true, WhyLoad::FileDiscovery)
1377 }
1378
1379 fn _load_file(
1387 &self,
1388 path: &Path,
1389 seen: &mut HashSet<PathBuf>,
1390 includes: bool,
1391 why_load: WhyLoad,
1392 ) -> CargoResult<ConfigValue> {
1393 if !seen.insert(path.to_path_buf()) {
1394 bail!(
1395 "config `include` cycle detected with path `{}`",
1396 path.display()
1397 );
1398 }
1399 tracing::debug!(?path, ?why_load, includes, "load config from file");
1400
1401 let contents = fs::read_to_string(path)
1402 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1403 let toml = parse_document(&contents, path, self).with_context(|| {
1404 format!("could not parse TOML configuration in `{}`", path.display())
1405 })?;
1406 let def = match why_load {
1407 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1408 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1409 };
1410 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1411 format!(
1412 "failed to load TOML configuration from `{}`",
1413 path.display()
1414 )
1415 })?;
1416 if includes {
1417 self.load_includes(value, seen, why_load)
1418 } else {
1419 Ok(value)
1420 }
1421 }
1422
1423 fn load_includes(
1430 &self,
1431 mut value: CV,
1432 seen: &mut HashSet<PathBuf>,
1433 why_load: WhyLoad,
1434 ) -> CargoResult<CV> {
1435 let includes = self.include_paths(&mut value, true)?;
1437
1438 let mut root = CV::Table(HashMap::default(), value.definition().clone());
1440 for include in includes {
1441 let Some(abs_path) = include.resolve_path(self) else {
1442 continue;
1443 };
1444
1445 self._load_file(&abs_path, seen, true, why_load)
1446 .and_then(|include| root.merge(include, true))
1447 .with_context(|| {
1448 format!(
1449 "failed to load config include `{}` from `{}`",
1450 include.path.display(),
1451 include.def
1452 )
1453 })?;
1454 }
1455 root.merge(value, true)?;
1456 Ok(root)
1457 }
1458
1459 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1461 let CV::Table(table, _def) = cv else {
1462 unreachable!()
1463 };
1464 let include = if remove {
1465 table.remove("include").map(Cow::Owned)
1466 } else {
1467 table.get("include").map(Cow::Borrowed)
1468 };
1469 let includes = match include.map(|c| c.into_owned()) {
1470 Some(CV::List(list, _def)) => list
1471 .into_iter()
1472 .enumerate()
1473 .map(|(idx, cv)| match cv {
1474 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1475 CV::Table(mut table, def) => {
1476 let s = match table.remove("path") {
1478 Some(CV::String(s, _)) => s,
1479 Some(other) => bail!(
1480 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1481 other.desc()
1482 ),
1483 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1484 };
1485
1486 let optional = match table.remove("optional") {
1488 Some(CV::Boolean(b, _)) => b,
1489 Some(other) => bail!(
1490 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1491 other.desc()
1492 ),
1493 None => false,
1494 };
1495
1496 let mut include = ConfigInclude::new(s, def);
1497 include.optional = optional;
1498 Ok(include)
1499 }
1500 other => bail!(
1501 "expected a string or table, but found {} at `include[{idx}]` in {}",
1502 other.desc(),
1503 other.definition(),
1504 ),
1505 })
1506 .collect::<CargoResult<Vec<_>>>()?,
1507 Some(other) => bail!(
1508 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1509 other.desc(),
1510 other.definition()
1511 ),
1512 None => {
1513 return Ok(Vec::new());
1514 }
1515 };
1516
1517 for include in &includes {
1518 if include.path.extension() != Some(OsStr::new("toml")) {
1519 bail!(
1520 "expected a config include path ending with `.toml`, \
1521 but found `{}` from `{}`",
1522 include.path.display(),
1523 include.def,
1524 )
1525 }
1526
1527 if let Some(path) = include.path.to_str() {
1528 if is_glob_pattern(path) {
1530 bail!(
1531 "expected a config include path without glob patterns, \
1532 but found `{}` from `{}`",
1533 include.path.display(),
1534 include.def,
1535 )
1536 }
1537 if path.contains(&['{', '}']) {
1538 bail!(
1539 "expected a config include path without template braces, \
1540 but found `{}` from `{}`",
1541 include.path.display(),
1542 include.def,
1543 )
1544 }
1545 }
1546 }
1547
1548 Ok(includes)
1549 }
1550
1551 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1553 let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1554 let Some(cli_args) = &self.cli_config else {
1555 return Ok(loaded_args);
1556 };
1557 let mut seen = HashSet::default();
1558 for arg in cli_args {
1559 let arg_as_path = self.cwd.join(arg);
1560 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1561 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1563 .with_context(|| {
1564 format!("failed to load config from `{}`", arg_as_path.display())
1565 })?
1566 } else {
1567 let doc = toml_dotted_keys(arg)?;
1568 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1569 .with_context(|| {
1570 format!("failed to parse value from --config argument `{arg}`")
1571 })?;
1572
1573 if doc
1574 .get("registry")
1575 .and_then(|v| v.as_table())
1576 .and_then(|t| t.get("token"))
1577 .is_some()
1578 {
1579 bail!("registry.token cannot be set through --config for security reasons");
1580 } else if let Some((k, _)) = doc
1581 .get("registries")
1582 .and_then(|v| v.as_table())
1583 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1584 {
1585 bail!(
1586 "registries.{}.token cannot be set through --config for security reasons",
1587 k
1588 );
1589 }
1590
1591 if doc
1592 .get("registry")
1593 .and_then(|v| v.as_table())
1594 .and_then(|t| t.get("secret-key"))
1595 .is_some()
1596 {
1597 bail!(
1598 "registry.secret-key cannot be set through --config for security reasons"
1599 );
1600 } else if let Some((k, _)) = doc
1601 .get("registries")
1602 .and_then(|v| v.as_table())
1603 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1604 {
1605 bail!(
1606 "registries.{}.secret-key cannot be set through --config for security reasons",
1607 k
1608 );
1609 }
1610
1611 CV::from_toml(Definition::Cli(None), doc)
1612 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1613 };
1614 let tmp_table = self
1615 .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1616 .context("failed to load --config include".to_string())?;
1617 loaded_args
1618 .merge(tmp_table, true)
1619 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1620 }
1621 Ok(loaded_args)
1622 }
1623
1624 fn merge_cli_args(&mut self) -> CargoResult<()> {
1626 let cv_from_cli = self.cli_args_as_table()?;
1627 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1628
1629 let root_cv = mem::take(self.values_mut()?);
1630 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1633 root_cv.merge(cv_from_cli, true)?;
1634
1635 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1637
1638 Ok(())
1639 }
1640
1641 fn get_file_path(
1647 &self,
1648 dir: &Path,
1649 filename_without_extension: &str,
1650 warn: bool,
1651 ) -> CargoResult<Option<PathBuf>> {
1652 let possible = dir.join(filename_without_extension);
1653 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1654
1655 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1656 if warn {
1657 if let Ok(possible_with_extension_handle) =
1658 same_file::Handle::from_path(&possible_with_extension)
1659 {
1660 if possible_handle != possible_with_extension_handle {
1666 self.shell().warn(format!(
1667 "both `{}` and `{}` exist. Using `{}`",
1668 possible.display(),
1669 possible_with_extension.display(),
1670 possible.display()
1671 ))?;
1672 }
1673 } else {
1674 self.shell().print_report(&[
1675 Level::WARNING.secondary_title(
1676 format!(
1677 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1678 possible.display(),
1679 )).element(Level::HELP.message(
1680 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1681 ], false)?;
1682 }
1683 }
1684
1685 Ok(Some(possible))
1686 } else if possible_with_extension.exists() {
1687 Ok(Some(possible_with_extension))
1688 } else {
1689 Ok(None)
1690 }
1691 }
1692
1693 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1694 where
1695 F: FnMut(&Path) -> CargoResult<()>,
1696 {
1697 let mut seen_dir = HashSet::default();
1698
1699 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1700 let config_root = current.join(".cargo");
1701 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1702 walk(&path)?;
1703 }
1704
1705 let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1706 seen_dir.insert(canonical_root);
1707 }
1708
1709 let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1710
1711 if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1715 if let Some(path) = self.get_file_path(home, "config", true)? {
1716 walk(&path)?;
1717 }
1718 }
1719
1720 Ok(())
1721 }
1722
1723 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1725 RegistryName::new(registry)?;
1726 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1727 self.resolve_registry_index(&index).with_context(|| {
1728 format!(
1729 "invalid index URL for registry `{}` defined in {}",
1730 registry, index.definition
1731 )
1732 })
1733 } else {
1734 bail!(
1735 "registry index was not found in any configuration: `{}`",
1736 registry
1737 );
1738 }
1739 }
1740
1741 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1743 if self.get_string("registry.index")?.is_some() {
1744 bail!(
1745 "the `registry.index` config value is no longer supported\n\
1746 Use `[source]` replacement to alter the default index for crates.io."
1747 );
1748 }
1749 Ok(())
1750 }
1751
1752 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1753 let base = index
1755 .definition
1756 .root(self.cwd())
1757 .join("truncated-by-url_with_base");
1758 let _parsed = index.val.into_url()?;
1760 let url = index.val.into_url_with_base(Some(&*base))?;
1761 if url.password().is_some() {
1762 bail!("registry URLs may not contain passwords");
1763 }
1764 Ok(url)
1765 }
1766
1767 pub fn load_credentials(&self) -> CargoResult<()> {
1775 if self.credential_values.filled() {
1776 return Ok(());
1777 }
1778
1779 let home_path = self.home_path.clone().into_path_unlocked();
1780 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1781 return Ok(());
1782 };
1783
1784 let mut value = self.load_file(&credentials)?;
1785 {
1787 let (value_map, def) = value.table_mut("<root>")?;
1788
1789 if let Some(token) = value_map.remove("token") {
1790 value_map.entry("registry".into()).or_insert_with(|| {
1791 let map = HashMap::from_iter([("token".into(), token)]);
1792 CV::Table(map, def.clone())
1793 });
1794 }
1795 }
1796
1797 let mut credential_values = HashMap::default();
1798 if let CV::Table(map, _) = value {
1799 let base_map = self.values()?;
1800 for (k, v) in map {
1801 let entry = match base_map.get(&k) {
1802 Some(base_entry) => {
1803 let mut entry = base_entry.clone();
1804 entry.merge(v, true)?;
1805 entry
1806 }
1807 None => v,
1808 };
1809 credential_values.insert(k, entry);
1810 }
1811 }
1812 self.credential_values
1813 .set(credential_values)
1814 .expect("was not filled at beginning of the function");
1815 Ok(())
1816 }
1817
1818 fn maybe_get_tool(
1821 &self,
1822 tool: &str,
1823 from_config: &Option<ConfigRelativePath>,
1824 ) -> Option<PathBuf> {
1825 let var = tool.to_uppercase();
1826
1827 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1828 Some(tool_path) => {
1829 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1830 let path = if maybe_relative {
1831 self.cwd.join(tool_path)
1832 } else {
1833 PathBuf::from(tool_path)
1834 };
1835 Some(path)
1836 }
1837
1838 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1839 }
1840 }
1841
1842 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1853 let tool_str = tool.as_str();
1854 self.maybe_get_tool(tool_str, from_config)
1855 .or_else(|| {
1856 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1870 if toolchain.to_str()?.contains(&['/', '\\']) {
1873 return None;
1874 }
1875 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1878 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1879 let tool_meta = tool_resolved.metadata().ok()?;
1880 let rustup_meta = rustup_resolved.metadata().ok()?;
1881 if tool_meta.len() != rustup_meta.len() {
1886 return None;
1887 }
1888 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1890 let toolchain_exe = home::rustup_home()
1891 .ok()?
1892 .join("toolchains")
1893 .join(&toolchain)
1894 .join("bin")
1895 .join(&tool_exe);
1896 toolchain_exe.exists().then_some(toolchain_exe)
1897 })
1898 .unwrap_or_else(|| PathBuf::from(tool_str))
1899 }
1900
1901 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1903 let key = ConfigKey::from_str("paths");
1904 match self.get_cv(&key)? {
1906 Some(CV::List(val, definition)) => {
1907 let val = val
1908 .into_iter()
1909 .map(|cv| match cv {
1910 CV::String(s, def) => Ok((s, def)),
1911 other => self.expected("string", &key, &other),
1912 })
1913 .collect::<CargoResult<Vec<_>>>()?;
1914 Ok(Some(Value { val, definition }))
1915 }
1916 Some(val) => self.expected("list", &key, &val),
1917 None => Ok(None),
1918 }
1919 }
1920
1921 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1922 self.jobserver
1923 }
1924
1925 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1926 let http = self
1927 .easy
1928 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1929 {
1930 let mut http = http.lock().unwrap();
1931 http.reset();
1932 let timeout = configure_http_handle(self, &mut http)?;
1933 timeout.configure(&mut http)?;
1934 }
1935 Ok(http)
1936 }
1937
1938 pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1939 self.http_async.try_borrow_with(|| {
1940 let handle_config = HandleConfiguration::new(&self)?;
1941 Ok(http_async::Client::new(handle_config))
1942 })
1943 }
1944
1945 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1946 self.http_config.try_borrow_with(|| {
1947 let mut http = self.get::<CargoHttpConfig>("http")?;
1948 let curl_v = curl::Version::get();
1949 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1950 Ok(http)
1951 })
1952 }
1953
1954 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1955 self.future_incompat_config
1956 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1957 }
1958
1959 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1960 self.net_config
1961 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1962 }
1963
1964 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1965 self.build_config
1966 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1967 }
1968
1969 pub fn progress_config(&self) -> &ProgressConfig {
1970 &self.progress_config
1971 }
1972
1973 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1976 let env_config = self.env_config.try_borrow_with(|| {
1977 CargoResult::Ok(Arc::new({
1978 let env_config = self.get::<EnvConfig>("env")?;
1979 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1995 if env_config.contains_key(*disallowed) {
1996 bail!(
1997 "setting the `{disallowed}` environment variable is not supported \
1998 in the `[env]` configuration table"
1999 );
2000 }
2001 }
2002 env_config
2003 .into_iter()
2004 .filter_map(|(k, v)| {
2005 if v.is_force() || self.get_env_os(&k).is_none() {
2006 Some((k, v.resolve(self.cwd()).to_os_string()))
2007 } else {
2008 None
2009 }
2010 })
2011 .collect()
2012 }))
2013 })?;
2014
2015 Ok(env_config)
2016 }
2017
2018 pub fn validate_term_config(&self) -> CargoResult<()> {
2024 drop(self.get::<TermConfig>("term")?);
2025 Ok(())
2026 }
2027
2028 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2032 self.target_cfgs
2033 .try_borrow_with(|| target::load_target_cfgs(self))
2034 }
2035
2036 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2037 self.doc_extern_map
2041 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2042 }
2043
2044 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2046 target::get_target_applies_to_host(self)
2047 }
2048
2049 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2051 target::load_host_triple(self, target)
2052 }
2053
2054 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2056 target::load_target_triple(self, target)
2057 }
2058
2059 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2064 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2065 self.check_registry_index_not_set()?;
2066 let url = CRATES_IO_INDEX.into_url().unwrap();
2067 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2068 })?;
2069 Ok(*source_id)
2070 }
2071
2072 pub fn invocation_instant(&self) -> Instant {
2073 self.invocation_instant
2074 }
2075
2076 pub fn invocation_time(&self) -> jiff::Timestamp {
2082 self.invocation_time
2083 }
2084
2085 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2100 let d = Deserializer {
2101 gctx: self,
2102 key: ConfigKey::from_str(key),
2103 env_prefix_ok: true,
2104 };
2105 T::deserialize(d).map_err(|e| e.into())
2106 }
2107
2108 #[track_caller]
2114 #[tracing::instrument(skip_all)]
2115 pub fn assert_package_cache_locked<'a>(
2116 &self,
2117 mode: CacheLockMode,
2118 f: &'a Filesystem,
2119 ) -> &'a Path {
2120 let ret = f.as_path_unlocked();
2121 assert!(
2122 self.package_cache_lock.is_locked(mode),
2123 "package cache lock is not currently held, Cargo forgot to call \
2124 `acquire_package_cache_lock` before we got to this stack frame",
2125 );
2126 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2127 ret
2128 }
2129
2130 #[tracing::instrument(skip_all)]
2136 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2137 self.package_cache_lock.lock(self, mode)
2138 }
2139
2140 #[tracing::instrument(skip_all)]
2146 pub fn try_acquire_package_cache_lock(
2147 &self,
2148 mode: CacheLockMode,
2149 ) -> CargoResult<Option<CacheLock<'_>>> {
2150 self.package_cache_lock.try_lock(self, mode)
2151 }
2152
2153 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2158 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2159 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2160 })?;
2161 Ok(tracker.lock().unwrap())
2162 }
2163
2164 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2166 let deferred = self
2167 .deferred_global_last_use
2168 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2169 Ok(deferred.lock().unwrap())
2170 }
2171
2172 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2174 Ok(self.build_config()?.warnings.unwrap_or_default())
2175 }
2176
2177 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2178 self.ws_roots.lock().unwrap()
2179 }
2180}
2181
2182pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2183 ::home::cargo_home_with_cwd(cwd)
2184 .ok()
2185 .map(|home| paths::normalize_path(&home))
2189}
2190
2191pub fn save_credentials(
2192 gctx: &GlobalContext,
2193 token: Option<RegistryCredentialConfig>,
2194 registry: &SourceId,
2195) -> CargoResult<()> {
2196 let registry = if registry.is_crates_io() {
2197 None
2198 } else {
2199 let name = registry
2200 .alt_registry_key()
2201 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2202 Some(name)
2203 };
2204
2205 let home_path = gctx.home_path.clone().into_path_unlocked();
2209 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2210 Some(path) => match path.file_name() {
2211 Some(filename) => Path::new(filename).to_owned(),
2212 None => Path::new("credentials.toml").to_owned(),
2213 },
2214 None => Path::new("credentials.toml").to_owned(),
2215 };
2216
2217 let mut file = {
2218 gctx.home_path.create_dir()?;
2219 gctx.home_path
2220 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2221 };
2222
2223 let mut contents = String::new();
2224 file.read_to_string(&mut contents).with_context(|| {
2225 format!(
2226 "failed to read configuration file `{}`",
2227 file.path().display()
2228 )
2229 })?;
2230
2231 let mut toml = parse_document(&contents, file.path(), gctx)?;
2232
2233 if let Some(token) = toml.remove("token") {
2235 #[expect(
2236 clippy::disallowed_types,
2237 reason = "need stdlib's HashMap because of TOML compatibility"
2238 )]
2239 let map = std::collections::HashMap::from([("token".to_string(), token)]);
2240 toml.insert("registry".into(), map.into());
2241 }
2242
2243 if let Some(token) = token {
2244 let path_def = Definition::Path(file.path().to_path_buf());
2247 let (key, mut value) = match token {
2248 RegistryCredentialConfig::Token(token) => {
2249 let key = "token".to_string();
2252 let value = ConfigValue::String(token.expose(), path_def.clone());
2253 let map = HashMap::from_iter([(key, value)]);
2254 let table = CV::Table(map, path_def.clone());
2255
2256 if let Some(registry) = registry {
2257 let map = HashMap::from_iter([(registry.to_string(), table)]);
2258 ("registries".into(), CV::Table(map, path_def.clone()))
2259 } else {
2260 ("registry".into(), table)
2261 }
2262 }
2263 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2264 let key = "secret-key".to_string();
2267 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2268 let mut map = HashMap::from_iter([(key, value)]);
2269 if let Some(key_subject) = key_subject {
2270 let key = "secret-key-subject".to_string();
2271 let value = ConfigValue::String(key_subject, path_def.clone());
2272 map.insert(key, value);
2273 }
2274 let table = CV::Table(map, path_def.clone());
2275
2276 if let Some(registry) = registry {
2277 let map = HashMap::from_iter([(registry.to_string(), table)]);
2278 ("registries".into(), CV::Table(map, path_def.clone()))
2279 } else {
2280 ("registry".into(), table)
2281 }
2282 }
2283 _ => unreachable!(),
2284 };
2285
2286 if registry.is_some() {
2287 if let Some(table) = toml.remove("registries") {
2288 let v = CV::from_toml(path_def, table)?;
2289 value.merge(v, false)?;
2290 }
2291 }
2292 toml.insert(key, value.into_toml());
2293 } else {
2294 if let Some(registry) = registry {
2296 if let Some(registries) = toml.get_mut("registries") {
2297 if let Some(reg) = registries.get_mut(registry) {
2298 let rtable = reg.as_table_mut().ok_or_else(|| {
2299 format_err!("expected `[registries.{}]` to be a table", registry)
2300 })?;
2301 rtable.remove("token");
2302 rtable.remove("secret-key");
2303 rtable.remove("secret-key-subject");
2304 }
2305 }
2306 } else if let Some(registry) = toml.get_mut("registry") {
2307 let reg_table = registry
2308 .as_table_mut()
2309 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2310 reg_table.remove("token");
2311 reg_table.remove("secret-key");
2312 reg_table.remove("secret-key-subject");
2313 }
2314 }
2315
2316 let contents = toml.to_string();
2317 file.seek(SeekFrom::Start(0))?;
2318 file.write_all(contents.as_bytes())
2319 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2320 file.file().set_len(contents.len() as u64)?;
2321 set_permissions(file.file(), 0o600)
2322 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2323
2324 return Ok(());
2325
2326 #[cfg(unix)]
2327 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2328 use std::os::unix::fs::PermissionsExt;
2329
2330 let mut perms = file.metadata()?.permissions();
2331 perms.set_mode(mode);
2332 file.set_permissions(perms)?;
2333 Ok(())
2334 }
2335
2336 #[cfg(not(unix))]
2337 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2338 Ok(())
2339 }
2340}
2341
2342struct ConfigInclude {
2348 path: PathBuf,
2351 def: Definition,
2352 optional: bool,
2354}
2355
2356impl ConfigInclude {
2357 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2358 Self {
2359 path: p.into(),
2360 def,
2361 optional: false,
2362 }
2363 }
2364
2365 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2378 let abs_path = match &self.def {
2379 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2380 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2381 }
2382 .join(&self.path);
2383 let abs_path = paths::normalize_path(&abs_path);
2384
2385 if self.optional && !abs_path.exists() {
2386 tracing::info!(
2387 "skipping optional include `{}` in `{}`: file not found at `{}`",
2388 self.path.display(),
2389 self.def,
2390 abs_path.display(),
2391 );
2392 None
2393 } else {
2394 Some(abs_path)
2395 }
2396 }
2397}
2398
2399fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2400 toml.parse().map_err(Into::into)
2402}
2403
2404fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2405 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2411 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2412 })?;
2413 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2414 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2415 }
2416 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2417 non_empty(d.prefix()) || non_empty(d.suffix())
2418 }
2419 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2420 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2421 }
2422 let ok = {
2423 let mut got_to_value = false;
2424 let mut table = doc.as_table();
2425 let mut is_root = true;
2426 while table.is_dotted() || is_root {
2427 is_root = false;
2428 if table.len() != 1 {
2429 break;
2430 }
2431 let (k, n) = table.iter().next().expect("len() == 1 above");
2432 match n {
2433 Item::Table(nt) => {
2434 if table.key(k).map_or(false, non_empty_key_decor)
2435 || non_empty_decor(nt.decor())
2436 {
2437 bail!(
2438 "--config argument `{arg}` \
2439 includes non-whitespace decoration"
2440 )
2441 }
2442 table = nt;
2443 }
2444 Item::Value(v) if v.is_inline_table() => {
2445 bail!(
2446 "--config argument `{arg}` \
2447 sets a value to an inline table, which is not accepted"
2448 );
2449 }
2450 Item::Value(v) => {
2451 if table
2452 .key(k)
2453 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2454 || non_empty_decor(v.decor())
2455 {
2456 bail!(
2457 "--config argument `{arg}` \
2458 includes non-whitespace decoration"
2459 )
2460 }
2461 got_to_value = true;
2462 break;
2463 }
2464 Item::ArrayOfTables(_) => {
2465 bail!(
2466 "--config argument `{arg}` \
2467 sets a value to an array of tables, which is not accepted"
2468 );
2469 }
2470
2471 Item::None => {
2472 bail!("--config argument `{arg}` doesn't provide a value")
2473 }
2474 }
2475 }
2476 got_to_value
2477 };
2478 if !ok {
2479 bail!(
2480 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2481 );
2482 }
2483 Ok(doc)
2484}
2485
2486#[derive(Debug, Deserialize, Clone)]
2497pub struct StringList(Vec<String>);
2498
2499impl StringList {
2500 pub fn as_slice(&self) -> &[String] {
2501 &self.0
2502 }
2503}
2504
2505#[macro_export]
2506macro_rules! __shell_print {
2507 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2508 let mut shell = $config.shell();
2509 let out = shell.$which();
2510 drop(out.write_fmt(format_args!($($arg)*)));
2511 if $newline {
2512 drop(out.write_all(b"\n"));
2513 }
2514 });
2515}
2516
2517#[macro_export]
2518macro_rules! drop_println {
2519 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2520 ($config:expr, $($arg:tt)*) => (
2521 $crate::__shell_print!($config, out, true, $($arg)*)
2522 );
2523}
2524
2525#[macro_export]
2526macro_rules! drop_eprintln {
2527 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2528 ($config:expr, $($arg:tt)*) => (
2529 $crate::__shell_print!($config, err, true, $($arg)*)
2530 );
2531}
2532
2533#[macro_export]
2534macro_rules! drop_print {
2535 ($config:expr, $($arg:tt)*) => (
2536 $crate::__shell_print!($config, out, false, $($arg)*)
2537 );
2538}
2539
2540#[macro_export]
2541macro_rules! drop_eprint {
2542 ($config:expr, $($arg:tt)*) => (
2543 $crate::__shell_print!($config, err, false, $($arg)*)
2544 );
2545}
2546
2547enum Tool {
2548 Rustc,
2549 Rustdoc,
2550}
2551
2552impl Tool {
2553 fn as_str(&self) -> &str {
2554 match self {
2555 Tool::Rustc => "rustc",
2556 Tool::Rustdoc => "rustdoc",
2557 }
2558 }
2559}
2560
2561fn disables_multiplexing_for_bad_curl(
2571 curl_version: &str,
2572 http: &mut CargoHttpConfig,
2573 gctx: &GlobalContext,
2574) {
2575 use crate::util::network;
2576
2577 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2578 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2579 if bad_curl_versions
2580 .iter()
2581 .any(|v| curl_version.starts_with(v))
2582 {
2583 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2584 http.multiplexing = Some(false);
2585 }
2586 }
2587}
2588
2589#[cfg(test)]
2590mod tests {
2591 use super::CargoHttpConfig;
2592 use super::GlobalContext;
2593 use super::Shell;
2594 use super::disables_multiplexing_for_bad_curl;
2595
2596 #[test]
2597 fn disables_multiplexing() {
2598 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2599 gctx.set_search_stop_path(std::path::PathBuf::new());
2600 gctx.set_env(Default::default());
2601
2602 let mut http = CargoHttpConfig::default();
2603 http.proxy = Some("127.0.0.1:3128".into());
2604 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2605 assert_eq!(http.multiplexing, Some(false));
2606
2607 let cases = [
2608 (None, None, "7.87.0", None),
2609 (None, None, "7.88.0", None),
2610 (None, None, "7.88.1", None),
2611 (None, None, "8.0.0", None),
2612 (Some("".into()), None, "7.87.0", Some(false)),
2613 (Some("".into()), None, "7.88.0", Some(false)),
2614 (Some("".into()), None, "7.88.1", Some(false)),
2615 (Some("".into()), None, "8.0.0", None),
2616 (Some("".into()), Some(false), "7.87.0", Some(false)),
2617 (Some("".into()), Some(false), "7.88.0", Some(false)),
2618 (Some("".into()), Some(false), "7.88.1", Some(false)),
2619 (Some("".into()), Some(false), "8.0.0", Some(false)),
2620 ];
2621
2622 for (proxy, multiplexing, curl_v, result) in cases {
2623 let mut http = CargoHttpConfig {
2624 multiplexing,
2625 proxy,
2626 ..Default::default()
2627 };
2628 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2629 assert_eq!(http.multiplexing, result);
2630 }
2631 }
2632
2633 #[test]
2634 fn sync_context() {
2635 fn assert_sync<S: Sync>() {}
2636 assert_sync::<GlobalContext>();
2637 }
2638}