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 sysroot: OnceLock<PathBuf>,
230 extra_verbose: bool,
232 frozen: bool,
235 locked: bool,
238 offline: bool,
241 jobserver: Option<&'static jobserver::Client>,
243 unstable_flags: CliUnstable,
245 unstable_flags_cli: Option<Vec<String>>,
247 easy: OnceLock<Mutex<Easy>>,
249 crates_io_source_id: OnceLock<SourceId>,
251 cache_rustc_info: bool,
253 invocation_instant: Instant,
255 invocation_time: jiff::Timestamp,
259 target_dir: Option<Filesystem>,
261 env: Env,
263 updated_sources: Mutex<HashSet<SourceId>>,
265 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
268 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
270 package_cache_lock: CacheLocker,
272 http_config: OnceLock<CargoHttpConfig>,
274 http_async: OnceLock<http_async::Client>,
275 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
276 net_config: OnceLock<CargoNetConfig>,
277 build_config: OnceLock<CargoBuildConfig>,
278 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
279 doc_extern_map: OnceLock<RustdocExternMap>,
280 progress_config: ProgressConfig,
281 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
282 pub nightly_features_allowed: bool,
298 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
300 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
302 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
305}
306
307impl GlobalContext {
308 pub fn new(mut shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
316 static GLOBAL_JOBSERVER: LazyLock<CargoResult<Option<jobserver::Client>>> = LazyLock::new(
317 || {
318 use jobserver::FromEnvErrorKind;
319 let jobserver::FromEnv { client, var } =
325 unsafe { jobserver::Client::from_env_ext(true) };
326
327 match client {
328 Ok(client) => return Ok(Some(client)),
329 Err(e)
330 if matches!(
331 e.kind(),
332 FromEnvErrorKind::NoEnvVar
333 | FromEnvErrorKind::NoJobserver
334 | FromEnvErrorKind::NegativeFd
335 | FromEnvErrorKind::Unsupported
336 ) =>
337 {
338 Ok(None)
339 }
340 Err(e) => {
341 let (name, value) = var.unwrap();
342 Err(anyhow::anyhow!(
343 "failed to connect to jobserver from environment variable `{name}={value:?}`: {e}"
344 ))
345 }
346 }
347 },
348 );
349 let jobserver = match &*GLOBAL_JOBSERVER {
350 Ok(jobserver) => jobserver.as_ref(),
351 Err(e) => {
352 let _ = shell.warn(e);
353 None
354 }
355 };
356
357 let env = Env::new();
358
359 let cache_key = "CARGO_CACHE_RUSTC_INFO";
360 let cache_rustc_info = match env.get_env_os(cache_key) {
361 Some(cache) => cache != "0",
362 _ => true,
363 };
364
365 #[expect(
366 clippy::disallowed_methods,
367 reason = "testing only, no reason for config support"
368 )]
369 let invocation_time = match env::var("__CARGO_TEST_INVOCATION_TIME") {
370 Ok(now) => now.parse().unwrap(),
371 Err(_) => jiff::Timestamp::now(),
372 };
373
374 GlobalContext {
375 home_path: Filesystem::new(homedir),
376 shell: Mutex::new(shell),
377 cwd,
378 search_stop_path: None,
379 values: Default::default(),
380 credential_values: Default::default(),
381 cli_config: None,
382 cargo_exe: Default::default(),
383 rustdoc: Default::default(),
384 sysroot: Default::default(),
385 extra_verbose: false,
386 frozen: false,
387 locked: false,
388 offline: false,
389 jobserver,
390 unstable_flags: CliUnstable::default(),
391 unstable_flags_cli: None,
392 easy: Default::default(),
393 crates_io_source_id: Default::default(),
394 cache_rustc_info,
395 invocation_instant: Instant::now(),
396 invocation_time,
397 target_dir: None,
398 env,
399 updated_sources: Default::default(),
400 credential_cache: Default::default(),
401 registry_config: Default::default(),
402 package_cache_lock: CacheLocker::new(),
403 http_config: Default::default(),
404 http_async: Default::default(),
405 future_incompat_config: Default::default(),
406 net_config: Default::default(),
407 build_config: Default::default(),
408 target_cfgs: Default::default(),
409 doc_extern_map: Default::default(),
410 progress_config: ProgressConfig::default(),
411 env_config: Default::default(),
412 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
413 ws_roots: Default::default(),
414 global_cache_tracker: Default::default(),
415 deferred_global_last_use: Default::default(),
416 }
417 }
418
419 pub fn default() -> CargoResult<GlobalContext> {
424 let shell = Shell::new();
425 let cwd =
426 env::current_dir().context("couldn't get the current directory of the process")?;
427 let homedir = homedir(&cwd).ok_or_else(|| {
428 anyhow!(
429 "Cargo couldn't find your home directory. \
430 This probably means that $HOME was not set."
431 )
432 })?;
433 Ok(GlobalContext::new(shell, cwd, homedir))
434 }
435
436 pub fn home(&self) -> &Filesystem {
438 &self.home_path
439 }
440
441 pub fn diagnostic_home_config(&self) -> String {
445 let home = self.home_path.as_path_unlocked();
446 let path = match self.get_file_path(home, "config", false) {
447 Ok(Some(existing_path)) => existing_path,
448 _ => home.join("config.toml"),
449 };
450 path.to_string_lossy().to_string()
451 }
452
453 pub fn git_path(&self) -> Filesystem {
455 self.home_path.join("git")
456 }
457
458 pub fn git_checkouts_path(&self) -> Filesystem {
461 self.git_path().join("checkouts")
462 }
463
464 pub fn git_db_path(&self) -> Filesystem {
467 self.git_path().join("db")
468 }
469
470 pub fn registry_base_path(&self) -> Filesystem {
472 self.home_path.join("registry")
473 }
474
475 pub fn registry_index_path(&self) -> Filesystem {
477 self.registry_base_path().join("index")
478 }
479
480 pub fn registry_cache_path(&self) -> Filesystem {
482 self.registry_base_path().join("cache")
483 }
484
485 pub fn registry_source_path(&self) -> Filesystem {
487 self.registry_base_path().join("src")
488 }
489
490 pub fn default_registry(&self) -> CargoResult<Option<String>> {
492 Ok(self
493 .get_string("registry.default")?
494 .map(|registry| registry.val))
495 }
496
497 pub fn shell(&self) -> MutexGuard<'_, Shell> {
499 self.shell.lock().unwrap()
500 }
501
502 pub fn debug_assert_shell_not_borrowed(&self) {
508 if cfg!(debug_assertions) {
509 match self.shell.try_lock() {
510 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
511 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
512 }
513 }
514 }
515
516 pub fn rustdoc(&self) -> CargoResult<&Path> {
518 self.rustdoc
519 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
520 .map(AsRef::as_ref)
521 }
522
523 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
525 let cache_location =
526 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
527 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
528 let rustc_workspace_wrapper = self.maybe_get_tool(
529 "rustc_workspace_wrapper",
530 &self.build_config()?.rustc_workspace_wrapper,
531 );
532
533 Rustc::new(
534 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
535 wrapper,
536 rustc_workspace_wrapper,
537 &self
538 .home()
539 .join("bin")
540 .join("rustc")
541 .into_path_unlocked()
542 .with_extension(env::consts::EXE_EXTENSION),
543 if self.cache_rustc_info {
544 cache_location
545 } else {
546 None
547 },
548 self,
549 )
550 }
551
552 pub fn cargo_exe(&self) -> CargoResult<&Path> {
554 self.cargo_exe
555 .try_borrow_with(|| {
556 let from_env = || -> CargoResult<PathBuf> {
557 let exe = self
562 .get_env_os(crate::CARGO_ENV)
563 .map(PathBuf::from)
564 .ok_or_else(|| anyhow!("$CARGO not set"))?;
565 Ok(exe)
566 };
567
568 fn from_current_exe() -> CargoResult<PathBuf> {
569 let exe = env::current_exe()?;
574 Ok(exe)
575 }
576
577 fn from_argv() -> CargoResult<PathBuf> {
578 let argv0 = env::args_os()
585 .map(PathBuf::from)
586 .next()
587 .ok_or_else(|| anyhow!("no argv[0]"))?;
588 paths::resolve_executable(&argv0)
589 }
590
591 fn is_cargo(path: &Path) -> bool {
594 path.file_stem() == Some(OsStr::new("cargo"))
595 }
596
597 let from_current_exe = from_current_exe();
598 if from_current_exe.as_deref().is_ok_and(is_cargo) {
599 return from_current_exe;
600 }
601
602 let from_argv = from_argv();
603 if from_argv.as_deref().is_ok_and(is_cargo) {
604 return from_argv;
605 }
606
607 let exe = from_env()
608 .or(from_current_exe)
609 .or(from_argv)
610 .context("couldn't get the path to cargo executable")?;
611 Ok(exe)
612 })
613 .map(AsRef::as_ref)
614 }
615
616 pub fn get_sysroot<'gctx>(&'gctx self, rustc: &Rustc) -> CargoResult<&'gctx Path> {
618 self.sysroot
619 .try_borrow_with(|| rustc.sysroot(self))
620 .map(AsRef::as_ref)
621 }
622
623 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
625 self.updated_sources.lock().unwrap()
626 }
627
628 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
630 self.credential_cache.lock().unwrap()
631 }
632
633 pub(crate) fn registry_config(
635 &self,
636 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
637 self.registry_config.lock().unwrap()
638 }
639
640 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
646 self.values.try_borrow_with(|| self.load_values())
647 }
648
649 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
656 let _ = self.values()?;
657 Ok(self.values.get_mut().expect("already loaded config values"))
658 }
659
660 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
662 if self.values.get().is_some() {
663 bail!("config values already found")
664 }
665 match self.values.set(values.into()) {
666 Ok(()) => Ok(()),
667 Err(_) => bail!("could not fill values"),
668 }
669 }
670
671 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
674 let path = path.into();
675 debug_assert!(self.cwd.starts_with(&path));
676 self.search_stop_path = Some(path);
677 }
678
679 pub fn reload_cwd(&mut self) -> CargoResult<()> {
683 let cwd =
684 env::current_dir().context("couldn't get the current directory of the process")?;
685 let homedir = homedir(&cwd).ok_or_else(|| {
686 anyhow!(
687 "Cargo couldn't find your home directory. \
688 This probably means that $HOME was not set."
689 )
690 })?;
691
692 self.cwd = cwd;
693 self.home_path = Filesystem::new(homedir);
694 self.reload_rooted_at(self.cwd.clone())?;
695 Ok(())
696 }
697
698 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
701 let values = self.load_values_from(path.as_ref())?;
702 self.values.replace(values);
703 self.merge_cli_args()?;
704 self.load_unstable_flags_from_config()?;
705 Ok(())
706 }
707
708 pub fn cwd(&self) -> &Path {
710 &self.cwd
711 }
712
713 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
719 if let Some(dir) = &self.target_dir {
720 Ok(Some(dir.clone()))
721 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
722 if dir.is_empty() {
724 bail!(
725 "the target directory is set to an empty string in the \
726 `CARGO_TARGET_DIR` environment variable"
727 )
728 }
729
730 Ok(Some(Filesystem::new(self.cwd.join(dir))))
731 } else if let Some(val) = &self.build_config()?.target_dir {
732 let path = val.resolve_path(self);
733
734 if val.raw_value().is_empty() {
736 bail!(
737 "the target directory is set to an empty string in {}",
738 val.value().definition
739 )
740 }
741
742 Ok(Some(Filesystem::new(path)))
743 } else {
744 Ok(None)
745 }
746 }
747
748 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
752 let Some(val) = &self.build_config()?.build_dir else {
753 return Ok(None);
754 };
755 self.custom_build_dir(val, workspace_manifest_path)
756 .map(Some)
757 }
758
759 pub fn custom_build_dir(
763 &self,
764 val: &ConfigRelativePath,
765 workspace_manifest_path: &Path,
766 ) -> CargoResult<Filesystem> {
767 let replacements = [
768 (
769 "{workspace-root}",
770 workspace_manifest_path
771 .parent()
772 .unwrap()
773 .to_str()
774 .context("workspace root was not valid utf-8")?
775 .to_string(),
776 ),
777 (
778 "{cargo-cache-home}",
779 self.home()
780 .as_path_unlocked()
781 .to_str()
782 .context("cargo home was not valid utf-8")?
783 .to_string(),
784 ),
785 ("{workspace-path-hash}", {
786 let real_path = std::fs::canonicalize(workspace_manifest_path)
787 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
788 let hash = crate::util::hex::short_hash(&real_path);
789 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
790 }),
791 ];
792
793 let template_variables = replacements
794 .iter()
795 .map(|(key, _)| key[1..key.len() - 1].to_string())
796 .collect_vec();
797
798 let path = val
799 .resolve_templated_path(self, replacements)
800 .map_err(|e| match e {
801 path::ResolveTemplateError::UnexpectedVariable {
802 variable,
803 raw_template,
804 } => {
805 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
806 if suggestion == "" {
807 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
808 suggestion = format!("\n\nhelp: available template variables are {variables}");
809 }
810 anyhow!(
811 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
812 )
813 }
814 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
815 let (btype, literal) = match bracket_type {
816 path::BracketType::Opening => ("opening", "{"),
817 path::BracketType::Closing => ("closing", "}"),
818 };
819
820 anyhow!(
821 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
822 )
823 }
824 })?;
825
826 if val.raw_value().is_empty() {
828 bail!(
829 "the build directory is set to an empty string in {}",
830 val.value().definition
831 )
832 }
833
834 Ok(Filesystem::new(path))
835 }
836
837 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
842 if let Some(vals) = self.credential_values.get() {
843 let val = self.get_cv_helper(key, vals)?;
844 if val.is_some() {
845 return Ok(val);
846 }
847 }
848 self.get_cv_helper(key, &*self.values()?)
849 }
850
851 fn get_cv_helper(
852 &self,
853 key: &ConfigKey,
854 vals: &HashMap<String, ConfigValue>,
855 ) -> CargoResult<Option<ConfigValue>> {
856 tracing::trace!("get cv {:?}", key);
857 if key.is_root() {
858 return Ok(Some(CV::Table(
861 vals.clone(),
862 Definition::Path(PathBuf::new()),
863 )));
864 }
865 let mut parts = key.parts().enumerate();
866 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
867 return Ok(None);
868 };
869 for (i, part) in parts {
870 match val {
871 CV::Table(map, _) => {
872 val = match map.get(part) {
873 Some(val) => val,
874 None => return Ok(None),
875 }
876 }
877 CV::Integer(_, def)
878 | CV::String(_, def)
879 | CV::List(_, def)
880 | CV::Boolean(_, def) => {
881 let mut key_so_far = ConfigKey::new();
882 for part in key.parts().take(i) {
883 key_so_far.push(part);
884 }
885 bail!(
886 "expected table for configuration key `{}`, \
887 but found {} in {}",
888 key_so_far,
889 val.desc(),
890 def
891 )
892 }
893 }
894 }
895 Ok(Some(val.clone()))
896 }
897
898 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
900 let cv = self.get_cv(key)?;
903 if key.is_root() {
904 return Ok(cv);
906 }
907 let env = self.env.get_str(key.as_env_key());
908 let env_def = Definition::Environment(key.as_env_key().to_string());
909 let use_env = match (&cv, env) {
910 (Some(CV::List(..)), Some(_)) => true,
912 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
913 (None, Some(_)) => true,
914 _ => false,
915 };
916
917 if !use_env {
918 return Ok(cv);
919 }
920
921 let env = env.unwrap();
925 if env == "true" {
926 Ok(Some(CV::Boolean(true, env_def)))
927 } else if env == "false" {
928 Ok(Some(CV::Boolean(false, env_def)))
929 } else if let Ok(i) = env.parse::<i64>() {
930 Ok(Some(CV::Integer(i, env_def)))
931 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
932 match cv {
933 Some(CV::List(mut cv_list, cv_def)) => {
934 self.get_env_list(key, &mut cv_list)?;
936 Ok(Some(CV::List(cv_list, cv_def)))
937 }
938 Some(cv) => {
939 bail!(
943 "unable to merge array env for config `{}`\n\
944 file: {:?}\n\
945 env: {}",
946 key,
947 cv,
948 env
949 );
950 }
951 None => {
952 let mut cv_list = Vec::new();
953 self.get_env_list(key, &mut cv_list)?;
954 Ok(Some(CV::List(cv_list, env_def)))
955 }
956 }
957 } else {
958 match cv {
960 Some(CV::List(mut cv_list, cv_def)) => {
961 self.get_env_list(key, &mut cv_list)?;
963 Ok(Some(CV::List(cv_list, cv_def)))
964 }
965 _ => {
966 Ok(Some(CV::String(env.to_string(), env_def)))
971 }
972 }
973 }
974 }
975
976 pub fn set_env(&mut self, env: HashMap<String, String>) {
978 self.env = Env::from_map(env);
979 }
980
981 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
984 self.env.iter_str()
985 }
986
987 fn env_keys(&self) -> impl Iterator<Item = &str> {
989 self.env.keys_str()
990 }
991
992 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
993 where
994 T: FromStr,
995 <T as FromStr>::Err: fmt::Display,
996 {
997 match self.env.get_str(key.as_env_key()) {
998 Some(value) => {
999 let definition = Definition::Environment(key.as_env_key().to_string());
1000 Ok(Some(Value {
1001 val: value
1002 .parse()
1003 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
1004 definition,
1005 }))
1006 }
1007 None => {
1008 self.check_environment_key_case_mismatch(key);
1009 Ok(None)
1010 }
1011 }
1012 }
1013
1014 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
1019 self.env.get_env(key)
1020 }
1021
1022 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
1027 self.env.get_env_os(key)
1028 }
1029
1030 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
1034 if self.env.contains_key(key.as_env_key()) {
1035 return Ok(true);
1036 }
1037 if env_prefix_ok {
1038 let env_prefix = format!("{}_", key.as_env_key());
1039 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
1040 return Ok(true);
1041 }
1042 }
1043 if self.get_cv(key)?.is_some() {
1044 return Ok(true);
1045 }
1046 self.check_environment_key_case_mismatch(key);
1047
1048 Ok(false)
1049 }
1050
1051 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1052 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1053 let _ = self.shell().warn(format!(
1054 "environment variables are expected to use uppercase letters and underscores, \
1055 the variable `{}` will be ignored and have no effect",
1056 env_key
1057 ));
1058 }
1059 }
1060
1061 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
1065 self.get::<OptValue<String>>(key)
1066 }
1067
1068 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1069 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1070 if is_path {
1071 definition.root(self.cwd()).join(value)
1072 } else {
1073 PathBuf::from(value)
1075 }
1076 }
1077
1078 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1081 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1082 self.check_environment_key_case_mismatch(key);
1083 return Ok(());
1084 };
1085
1086 let env_def = Definition::Environment(key.as_env_key().to_string());
1087
1088 if is_nonmergeable_list(&key) {
1089 assert!(
1090 output
1091 .windows(2)
1092 .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1093 "non-mergeable list must have only one definition: {output:?}",
1094 );
1095
1096 if output
1099 .first()
1100 .map(|o| o.definition() > &env_def)
1101 .unwrap_or_default()
1102 {
1103 return Ok(());
1104 } else {
1105 output.clear();
1106 }
1107 }
1108
1109 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1110 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1112 ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1113 })?;
1114 let values = toml_v.as_array().expect("env var was not array");
1115 for value in values {
1116 let s = value.as_str().ok_or_else(|| {
1119 ConfigError::new(
1120 format!("expected string, found {}", value.type_str()),
1121 env_def.clone(),
1122 )
1123 })?;
1124 output.push(CV::String(s.to_string(), env_def.clone()))
1125 }
1126 } else {
1127 output.extend(
1128 env_val
1129 .split_whitespace()
1130 .map(|s| CV::String(s.to_string(), env_def.clone())),
1131 );
1132 }
1133 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1134 Ok(())
1135 }
1136
1137 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1141 match self.get_cv(key)? {
1142 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1143 Some(val) => self.expected("table", key, &val),
1144 None => Ok(None),
1145 }
1146 }
1147
1148 get_value_typed! {get_integer, i64, Integer, "an integer"}
1149 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1150 get_value_typed! {get_string_priv, String, String, "a string"}
1151
1152 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1154 val.expected(ty, &key.to_string())
1155 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1156 }
1157
1158 pub fn configure(
1164 &mut self,
1165 verbose: u32,
1166 quiet: bool,
1167 color: Option<&str>,
1168 frozen: bool,
1169 locked: bool,
1170 offline: bool,
1171 target_dir: &Option<PathBuf>,
1172 unstable_flags: &[String],
1173 cli_config: &[String],
1174 ) -> CargoResult<()> {
1175 for warning in self
1176 .unstable_flags
1177 .parse(unstable_flags, self.nightly_features_allowed)?
1178 {
1179 self.shell().warn(warning)?;
1180 }
1181 if !unstable_flags.is_empty() {
1182 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1185 }
1186 if !cli_config.is_empty() {
1187 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1188 self.merge_cli_args()?;
1189 }
1190
1191 self.load_unstable_flags_from_config()?;
1192
1193 let term = self.get::<TermConfig>("term").unwrap_or_default();
1197
1198 let extra_verbose = verbose >= 2;
1200 let verbose = verbose != 0;
1201 let verbosity = match (verbose, quiet) {
1202 (true, true) => bail!("cannot set both --verbose and --quiet"),
1203 (true, false) => Verbosity::Verbose,
1204 (false, true) => Verbosity::Quiet,
1205 (false, false) => match (term.verbose, term.quiet) {
1206 (Some(true), Some(true)) => {
1207 bail!("cannot set both `term.verbose` and `term.quiet`")
1208 }
1209 (Some(true), _) => Verbosity::Verbose,
1210 (_, Some(true)) => Verbosity::Quiet,
1211 _ => Verbosity::Normal,
1212 },
1213 };
1214 self.shell().set_verbosity(verbosity);
1215 self.extra_verbose = extra_verbose;
1216
1217 let color = color.or_else(|| term.color.as_deref());
1218 self.shell().set_color_choice(color)?;
1219 if let Some(hyperlinks) = term.hyperlinks {
1220 self.shell().set_hyperlinks(hyperlinks)?;
1221 }
1222 if let Some(unicode) = term.unicode {
1223 self.shell().set_unicode(unicode)?;
1224 }
1225
1226 self.progress_config = term.progress.unwrap_or_default();
1227
1228 self.frozen = frozen;
1229 self.locked = locked;
1230 self.offline = offline
1231 || self
1232 .net_config()
1233 .ok()
1234 .and_then(|n| n.offline)
1235 .unwrap_or(false);
1236 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1237 self.target_dir = cli_target_dir;
1238
1239 self.shell()
1240 .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1241
1242 Ok(())
1243 }
1244
1245 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1246 if self.nightly_features_allowed {
1249 self.unstable_flags = self
1250 .get::<Option<CliUnstable>>("unstable")?
1251 .unwrap_or_default();
1252 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1253 self.unstable_flags.parse(unstable_flags_cli, true)?;
1258 }
1259 }
1260
1261 Ok(())
1262 }
1263
1264 pub fn cli_unstable(&self) -> &CliUnstable {
1265 &self.unstable_flags
1266 }
1267
1268 pub fn extra_verbose(&self) -> bool {
1269 self.extra_verbose
1270 }
1271
1272 pub fn should_embed_metadata(&self) -> bool {
1273 self.cli_unstable().embed_metadata.unwrap_or(true)
1274 }
1275
1276 pub fn network_allowed(&self) -> bool {
1277 !self.offline_flag().is_some()
1278 }
1279
1280 pub fn offline_flag(&self) -> Option<&'static str> {
1281 if self.frozen {
1282 Some("--frozen")
1283 } else if self.offline {
1284 Some("--offline")
1285 } else {
1286 None
1287 }
1288 }
1289
1290 pub fn set_locked(&mut self, locked: bool) {
1291 self.locked = locked;
1292 }
1293
1294 pub fn lock_update_allowed(&self) -> bool {
1295 !self.locked_flag().is_some()
1296 }
1297
1298 pub fn locked_flag(&self) -> Option<&'static str> {
1299 if self.frozen {
1300 Some("--frozen")
1301 } else if self.locked {
1302 Some("--locked")
1303 } else {
1304 None
1305 }
1306 }
1307
1308 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1310 self.load_values_from(&self.cwd)
1311 }
1312
1313 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1317 let mut result = Vec::new();
1318 let mut seen = HashSet::default();
1319 let home = self.home_path.clone().into_path_unlocked();
1320 self.walk_tree(&self.cwd, &home, |path| {
1321 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1322 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1323 result.push(cv);
1324 Ok(())
1325 })
1326 .context("could not load Cargo configuration")?;
1327 Ok(result)
1328 }
1329
1330 fn load_unmerged_include(
1334 &self,
1335 cv: &mut CV,
1336 seen: &mut HashSet<PathBuf>,
1337 output: &mut Vec<CV>,
1338 ) -> CargoResult<()> {
1339 let includes = self.include_paths(cv, false)?;
1340 for include in includes {
1341 let Some(abs_path) = include.resolve_path(self) else {
1342 continue;
1343 };
1344
1345 let mut cv = self
1346 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1347 .with_context(|| {
1348 format!(
1349 "failed to load config include `{}` from `{}`",
1350 include.path.display(),
1351 include.def
1352 )
1353 })?;
1354 self.load_unmerged_include(&mut cv, seen, output)?;
1355 output.push(cv);
1356 }
1357 Ok(())
1358 }
1359
1360 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1362 let mut cfg = CV::Table(HashMap::default(), Definition::BuiltIn);
1365 let home = self.home_path.clone().into_path_unlocked();
1366
1367 self.walk_tree(path, &home, |path| {
1368 let value = self.load_file(path)?;
1369 cfg.merge(value, false).with_context(|| {
1370 format!("failed to merge configuration at `{}`", path.display())
1371 })?;
1372 Ok(())
1373 })
1374 .context("could not load Cargo configuration")?;
1375
1376 match cfg {
1377 CV::Table(map, _) => Ok(map),
1378 _ => unreachable!(),
1379 }
1380 }
1381
1382 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1386 self._load_file(path, &mut HashSet::default(), true, WhyLoad::FileDiscovery)
1387 }
1388
1389 fn _load_file(
1397 &self,
1398 path: &Path,
1399 seen: &mut HashSet<PathBuf>,
1400 includes: bool,
1401 why_load: WhyLoad,
1402 ) -> CargoResult<ConfigValue> {
1403 if !seen.insert(path.to_path_buf()) {
1404 bail!(
1405 "config `include` cycle detected with path `{}`",
1406 path.display()
1407 );
1408 }
1409 tracing::debug!(?path, ?why_load, includes, "load config from file");
1410
1411 let contents = fs::read_to_string(path)
1412 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1413 let toml = parse_document(&contents, path, self).with_context(|| {
1414 format!("could not parse TOML configuration in `{}`", path.display())
1415 })?;
1416 let def = match why_load {
1417 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1418 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1419 };
1420 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1421 format!(
1422 "failed to load TOML configuration from `{}`",
1423 path.display()
1424 )
1425 })?;
1426 if includes {
1427 self.load_includes(value, seen, why_load)
1428 } else {
1429 Ok(value)
1430 }
1431 }
1432
1433 fn load_includes(
1440 &self,
1441 mut value: CV,
1442 seen: &mut HashSet<PathBuf>,
1443 why_load: WhyLoad,
1444 ) -> CargoResult<CV> {
1445 let includes = self.include_paths(&mut value, true)?;
1447
1448 let mut root = CV::Table(HashMap::default(), value.definition().clone());
1450 for include in includes {
1451 let Some(abs_path) = include.resolve_path(self) else {
1452 continue;
1453 };
1454
1455 self._load_file(&abs_path, seen, true, why_load)
1456 .and_then(|include| root.merge(include, true))
1457 .with_context(|| {
1458 format!(
1459 "failed to load config include `{}` from `{}`",
1460 include.path.display(),
1461 include.def
1462 )
1463 })?;
1464 }
1465 root.merge(value, true)?;
1466 Ok(root)
1467 }
1468
1469 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1471 let CV::Table(table, _def) = cv else {
1472 unreachable!()
1473 };
1474 let include = if remove {
1475 table.remove("include").map(Cow::Owned)
1476 } else {
1477 table.get("include").map(Cow::Borrowed)
1478 };
1479 let includes = match include.map(|c| c.into_owned()) {
1480 Some(CV::List(list, _def)) => list
1481 .into_iter()
1482 .enumerate()
1483 .map(|(idx, cv)| match cv {
1484 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1485 CV::Table(mut table, def) => {
1486 let s = match table.remove("path") {
1488 Some(CV::String(s, _)) => s,
1489 Some(other) => bail!(
1490 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1491 other.desc()
1492 ),
1493 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1494 };
1495
1496 let optional = match table.remove("optional") {
1498 Some(CV::Boolean(b, _)) => b,
1499 Some(other) => bail!(
1500 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1501 other.desc()
1502 ),
1503 None => false,
1504 };
1505
1506 let mut include = ConfigInclude::new(s, def);
1507 include.optional = optional;
1508 Ok(include)
1509 }
1510 other => bail!(
1511 "expected a string or table, but found {} at `include[{idx}]` in {}",
1512 other.desc(),
1513 other.definition(),
1514 ),
1515 })
1516 .collect::<CargoResult<Vec<_>>>()?,
1517 Some(other) => bail!(
1518 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1519 other.desc(),
1520 other.definition()
1521 ),
1522 None => {
1523 return Ok(Vec::new());
1524 }
1525 };
1526
1527 for include in &includes {
1528 if include.path.extension() != Some(OsStr::new("toml")) {
1529 bail!(
1530 "expected a config include path ending with `.toml`, \
1531 but found `{}` from `{}`",
1532 include.path.display(),
1533 include.def,
1534 )
1535 }
1536
1537 if let Some(path) = include.path.to_str() {
1538 if is_glob_pattern(path) {
1540 bail!(
1541 "expected a config include path without glob patterns, \
1542 but found `{}` from `{}`",
1543 include.path.display(),
1544 include.def,
1545 )
1546 }
1547 if path.contains(&['{', '}']) {
1548 bail!(
1549 "expected a config include path without template braces, \
1550 but found `{}` from `{}`",
1551 include.path.display(),
1552 include.def,
1553 )
1554 }
1555 }
1556 }
1557
1558 Ok(includes)
1559 }
1560
1561 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1563 let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1564 let Some(cli_args) = &self.cli_config else {
1565 return Ok(loaded_args);
1566 };
1567 let mut seen = HashSet::default();
1568 for arg in cli_args {
1569 let arg_as_path = self.cwd.join(arg);
1570 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1571 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1573 .with_context(|| {
1574 format!("failed to load config from `{}`", arg_as_path.display())
1575 })?
1576 } else {
1577 let doc = toml_dotted_keys(arg)?;
1578 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1579 .with_context(|| {
1580 format!("failed to parse value from --config argument `{arg}`")
1581 })?;
1582
1583 if doc
1584 .get("registry")
1585 .and_then(|v| v.as_table())
1586 .and_then(|t| t.get("token"))
1587 .is_some()
1588 {
1589 bail!("registry.token cannot be set through --config for security reasons");
1590 } else if let Some((k, _)) = doc
1591 .get("registries")
1592 .and_then(|v| v.as_table())
1593 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1594 {
1595 bail!(
1596 "registries.{}.token cannot be set through --config for security reasons",
1597 k
1598 );
1599 }
1600
1601 if doc
1602 .get("registry")
1603 .and_then(|v| v.as_table())
1604 .and_then(|t| t.get("secret-key"))
1605 .is_some()
1606 {
1607 bail!(
1608 "registry.secret-key cannot be set through --config for security reasons"
1609 );
1610 } else if let Some((k, _)) = doc
1611 .get("registries")
1612 .and_then(|v| v.as_table())
1613 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1614 {
1615 bail!(
1616 "registries.{}.secret-key cannot be set through --config for security reasons",
1617 k
1618 );
1619 }
1620
1621 CV::from_toml(Definition::Cli(None), doc)
1622 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1623 };
1624 let tmp_table = self
1625 .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1626 .context("failed to load --config include".to_string())?;
1627 loaded_args
1628 .merge(tmp_table, true)
1629 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1630 }
1631 Ok(loaded_args)
1632 }
1633
1634 fn merge_cli_args(&mut self) -> CargoResult<()> {
1636 let cv_from_cli = self.cli_args_as_table()?;
1637 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1638
1639 let root_cv = mem::take(self.values_mut()?);
1640 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1643 root_cv.merge(cv_from_cli, true)?;
1644
1645 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1647
1648 Ok(())
1649 }
1650
1651 fn get_file_path(
1657 &self,
1658 dir: &Path,
1659 filename_without_extension: &str,
1660 warn: bool,
1661 ) -> CargoResult<Option<PathBuf>> {
1662 let possible = dir.join(filename_without_extension);
1663 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1664
1665 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1666 if warn {
1667 if let Ok(possible_with_extension_handle) =
1668 same_file::Handle::from_path(&possible_with_extension)
1669 {
1670 if possible_handle != possible_with_extension_handle {
1676 self.shell().warn(format!(
1677 "both `{}` and `{}` exist. Using `{}`",
1678 possible.display(),
1679 possible_with_extension.display(),
1680 possible.display()
1681 ))?;
1682 }
1683 } else {
1684 self.shell().print_report(&[
1685 Level::WARNING.secondary_title(
1686 format!(
1687 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1688 possible.display(),
1689 )).element(Level::HELP.message(
1690 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1691 ], false)?;
1692 }
1693 }
1694
1695 Ok(Some(possible))
1696 } else if possible_with_extension.exists() {
1697 Ok(Some(possible_with_extension))
1698 } else {
1699 Ok(None)
1700 }
1701 }
1702
1703 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1704 where
1705 F: FnMut(&Path) -> CargoResult<()>,
1706 {
1707 let mut seen_dir = HashSet::default();
1708
1709 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1710 let config_root = current.join(".cargo");
1711 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1712 walk(&path)?;
1713 }
1714
1715 let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1716 seen_dir.insert(canonical_root);
1717 }
1718
1719 let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1720
1721 if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1725 if let Some(path) = self.get_file_path(home, "config", true)? {
1726 walk(&path)?;
1727 }
1728 }
1729
1730 Ok(())
1731 }
1732
1733 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1735 RegistryName::new(registry)?;
1736 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1737 self.resolve_registry_index(&index).with_context(|| {
1738 format!(
1739 "invalid index URL for registry `{}` defined in {}",
1740 registry, index.definition
1741 )
1742 })
1743 } else {
1744 bail!(
1745 "registry index was not found in any configuration: `{}`",
1746 registry
1747 );
1748 }
1749 }
1750
1751 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1753 if self.get_string("registry.index")?.is_some() {
1754 bail!(
1755 "the `registry.index` config value is no longer supported\n\
1756 Use `[source]` replacement to alter the default index for crates.io."
1757 );
1758 }
1759 Ok(())
1760 }
1761
1762 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1763 let base = index
1765 .definition
1766 .root(self.cwd())
1767 .join("truncated-by-url_with_base");
1768 let _parsed = index.val.into_url()?;
1770 let url = index.val.into_url_with_base(Some(&*base))?;
1771 if url.password().is_some() {
1772 bail!("registry URLs may not contain passwords");
1773 }
1774 Ok(url)
1775 }
1776
1777 pub fn load_credentials(&self) -> CargoResult<()> {
1785 if self.credential_values.filled() {
1786 return Ok(());
1787 }
1788
1789 let home_path = self.home_path.clone().into_path_unlocked();
1790 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1791 return Ok(());
1792 };
1793
1794 let mut value = self.load_file(&credentials)?;
1795 {
1797 let (value_map, def) = value.table_mut("<root>")?;
1798
1799 if let Some(token) = value_map.remove("token") {
1800 value_map.entry("registry".into()).or_insert_with(|| {
1801 let map = HashMap::from_iter([("token".into(), token)]);
1802 CV::Table(map, def.clone())
1803 });
1804 }
1805 }
1806
1807 let mut credential_values = HashMap::default();
1808 if let CV::Table(map, _) = value {
1809 let base_map = self.values()?;
1810 for (k, v) in map {
1811 let entry = match base_map.get(&k) {
1812 Some(base_entry) => {
1813 let mut entry = base_entry.clone();
1814 entry.merge(v, true)?;
1815 entry
1816 }
1817 None => v,
1818 };
1819 credential_values.insert(k, entry);
1820 }
1821 }
1822 self.credential_values
1823 .set(credential_values)
1824 .expect("was not filled at beginning of the function");
1825 Ok(())
1826 }
1827
1828 fn maybe_get_tool(
1831 &self,
1832 tool: &str,
1833 from_config: &Option<ConfigRelativePath>,
1834 ) -> Option<PathBuf> {
1835 let var = tool.to_uppercase();
1836
1837 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1838 Some(tool_path) => {
1839 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1840 let path = if maybe_relative {
1841 self.cwd.join(tool_path)
1842 } else {
1843 PathBuf::from(tool_path)
1844 };
1845 Some(path)
1846 }
1847
1848 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1849 }
1850 }
1851
1852 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1863 let tool_str = tool.as_str();
1864 self.maybe_get_tool(tool_str, from_config)
1865 .or_else(|| {
1866 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1880 if toolchain.to_str()?.contains(&['/', '\\']) {
1883 return None;
1884 }
1885 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1888 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1889 let tool_meta = tool_resolved.metadata().ok()?;
1890 let rustup_meta = rustup_resolved.metadata().ok()?;
1891 if tool_meta.len() != rustup_meta.len() {
1896 return None;
1897 }
1898 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1900 let toolchain_exe = home::rustup_home()
1901 .ok()?
1902 .join("toolchains")
1903 .join(&toolchain)
1904 .join("bin")
1905 .join(&tool_exe);
1906 toolchain_exe.exists().then_some(toolchain_exe)
1907 })
1908 .unwrap_or_else(|| PathBuf::from(tool_str))
1909 }
1910
1911 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1913 let key = ConfigKey::from_str("paths");
1914 match self.get_cv(&key)? {
1916 Some(CV::List(val, definition)) => {
1917 let val = val
1918 .into_iter()
1919 .map(|cv| match cv {
1920 CV::String(s, def) => Ok((s, def)),
1921 other => self.expected("string", &key, &other),
1922 })
1923 .collect::<CargoResult<Vec<_>>>()?;
1924 Ok(Some(Value { val, definition }))
1925 }
1926 Some(val) => self.expected("list", &key, &val),
1927 None => Ok(None),
1928 }
1929 }
1930
1931 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1932 self.jobserver
1933 }
1934
1935 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1936 let http = self
1937 .easy
1938 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1939 {
1940 let mut http = http.lock().unwrap();
1941 http.reset();
1942 let timeout = configure_http_handle(self, &mut http)?;
1943 timeout.configure(&mut http)?;
1944 }
1945 Ok(http)
1946 }
1947
1948 pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1949 self.http_async.try_borrow_with(|| {
1950 let handle_config = HandleConfiguration::new(&self)?;
1951 Ok(http_async::Client::new(handle_config))
1952 })
1953 }
1954
1955 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1956 self.http_config.try_borrow_with(|| {
1957 let mut http = self.get::<CargoHttpConfig>("http")?;
1958 let curl_v = curl::Version::get();
1959 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1960 Ok(http)
1961 })
1962 }
1963
1964 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1965 self.future_incompat_config
1966 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1967 }
1968
1969 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1970 self.net_config
1971 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1972 }
1973
1974 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1975 self.build_config
1976 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1977 }
1978
1979 pub fn progress_config(&self) -> &ProgressConfig {
1980 &self.progress_config
1981 }
1982
1983 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1986 let env_config = self.env_config.try_borrow_with(|| {
1987 CargoResult::Ok(Arc::new({
1988 let env_config = self.get::<EnvConfig>("env")?;
1989 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
2005 if env_config.contains_key(*disallowed) {
2006 bail!(
2007 "setting the `{disallowed}` environment variable is not supported \
2008 in the `[env]` configuration table"
2009 );
2010 }
2011 }
2012 env_config
2013 .into_iter()
2014 .filter_map(|(k, v)| {
2015 if v.is_force() || self.get_env_os(&k).is_none() {
2016 Some((k, v.resolve(self.cwd()).to_os_string()))
2017 } else {
2018 None
2019 }
2020 })
2021 .collect()
2022 }))
2023 })?;
2024
2025 Ok(env_config)
2026 }
2027
2028 pub fn validate_term_config(&self) -> CargoResult<()> {
2034 drop(self.get::<TermConfig>("term")?);
2035 Ok(())
2036 }
2037
2038 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2042 self.target_cfgs
2043 .try_borrow_with(|| target::load_target_cfgs(self))
2044 }
2045
2046 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2047 self.doc_extern_map
2051 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2052 }
2053
2054 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2056 target::get_target_applies_to_host(self)
2057 }
2058
2059 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2061 target::load_host_triple(self, target)
2062 }
2063
2064 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2066 target::load_target_triple(self, target)
2067 }
2068
2069 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2074 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2075 self.check_registry_index_not_set()?;
2076 let url = CRATES_IO_INDEX.into_url().unwrap();
2077 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2078 })?;
2079 Ok(*source_id)
2080 }
2081
2082 pub fn invocation_instant(&self) -> Instant {
2083 self.invocation_instant
2084 }
2085
2086 pub fn invocation_time(&self) -> jiff::Timestamp {
2092 self.invocation_time
2093 }
2094
2095 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2110 let d = Deserializer {
2111 gctx: self,
2112 key: ConfigKey::from_str(key),
2113 env_prefix_ok: true,
2114 };
2115 T::deserialize(d).map_err(|e| e.into())
2116 }
2117
2118 #[track_caller]
2124 #[tracing::instrument(skip_all)]
2125 pub fn assert_package_cache_locked<'a>(
2126 &self,
2127 mode: CacheLockMode,
2128 f: &'a Filesystem,
2129 ) -> &'a Path {
2130 let ret = f.as_path_unlocked();
2131 assert!(
2132 self.package_cache_lock.is_locked(mode),
2133 "package cache lock is not currently held, Cargo forgot to call \
2134 `acquire_package_cache_lock` before we got to this stack frame",
2135 );
2136 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2137 ret
2138 }
2139
2140 #[tracing::instrument(skip_all)]
2146 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2147 self.package_cache_lock.lock(self, mode)
2148 }
2149
2150 #[tracing::instrument(skip_all)]
2156 pub fn try_acquire_package_cache_lock(
2157 &self,
2158 mode: CacheLockMode,
2159 ) -> CargoResult<Option<CacheLock<'_>>> {
2160 self.package_cache_lock.try_lock(self, mode)
2161 }
2162
2163 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2168 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2169 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2170 })?;
2171 Ok(tracker.lock().unwrap())
2172 }
2173
2174 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2176 let deferred = self
2177 .deferred_global_last_use
2178 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2179 Ok(deferred.lock().unwrap())
2180 }
2181
2182 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2184 Ok(self.build_config()?.warnings.unwrap_or_default())
2185 }
2186
2187 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2188 self.ws_roots.lock().unwrap()
2189 }
2190}
2191
2192pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2193 ::home::cargo_home_with_cwd(cwd)
2194 .ok()
2195 .map(|home| paths::normalize_path(&home))
2199}
2200
2201pub fn save_credentials(
2202 gctx: &GlobalContext,
2203 token: Option<RegistryCredentialConfig>,
2204 registry: &SourceId,
2205) -> CargoResult<()> {
2206 let registry = if registry.is_crates_io() {
2207 None
2208 } else {
2209 let name = registry
2210 .alt_registry_key()
2211 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2212 Some(name)
2213 };
2214
2215 let home_path = gctx.home_path.clone().into_path_unlocked();
2219 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2220 Some(path) => match path.file_name() {
2221 Some(filename) => Path::new(filename).to_owned(),
2222 None => Path::new("credentials.toml").to_owned(),
2223 },
2224 None => Path::new("credentials.toml").to_owned(),
2225 };
2226
2227 let mut file = {
2228 gctx.home_path.create_dir()?;
2229 gctx.home_path
2230 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2231 };
2232
2233 let mut contents = String::new();
2234 file.read_to_string(&mut contents).with_context(|| {
2235 format!(
2236 "failed to read configuration file `{}`",
2237 file.path().display()
2238 )
2239 })?;
2240
2241 let mut toml = parse_document(&contents, file.path(), gctx)?;
2242
2243 if let Some(token) = toml.remove("token") {
2245 #[expect(
2246 clippy::disallowed_types,
2247 reason = "need stdlib's HashMap because of TOML compatibility"
2248 )]
2249 let map = std::collections::HashMap::from([("token".to_string(), token)]);
2250 toml.insert("registry".into(), map.into());
2251 }
2252
2253 if let Some(token) = token {
2254 let path_def = Definition::Path(file.path().to_path_buf());
2257 let (key, mut value) = match token {
2258 RegistryCredentialConfig::Token(token) => {
2259 let key = "token".to_string();
2262 let value = ConfigValue::String(token.expose(), path_def.clone());
2263 let map = HashMap::from_iter([(key, value)]);
2264 let table = CV::Table(map, path_def.clone());
2265
2266 if let Some(registry) = registry {
2267 let map = HashMap::from_iter([(registry.to_string(), table)]);
2268 ("registries".into(), CV::Table(map, path_def.clone()))
2269 } else {
2270 ("registry".into(), table)
2271 }
2272 }
2273 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2274 let key = "secret-key".to_string();
2277 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2278 let mut map = HashMap::from_iter([(key, value)]);
2279 if let Some(key_subject) = key_subject {
2280 let key = "secret-key-subject".to_string();
2281 let value = ConfigValue::String(key_subject, path_def.clone());
2282 map.insert(key, value);
2283 }
2284 let table = CV::Table(map, path_def.clone());
2285
2286 if let Some(registry) = registry {
2287 let map = HashMap::from_iter([(registry.to_string(), table)]);
2288 ("registries".into(), CV::Table(map, path_def.clone()))
2289 } else {
2290 ("registry".into(), table)
2291 }
2292 }
2293 _ => unreachable!(),
2294 };
2295
2296 if registry.is_some() {
2297 if let Some(table) = toml.remove("registries") {
2298 let v = CV::from_toml(path_def, table)?;
2299 value.merge(v, false)?;
2300 }
2301 }
2302 toml.insert(key, value.into_toml());
2303 } else {
2304 if let Some(registry) = registry {
2306 if let Some(registries) = toml.get_mut("registries") {
2307 if let Some(reg) = registries.get_mut(registry) {
2308 let rtable = reg.as_table_mut().ok_or_else(|| {
2309 format_err!("expected `[registries.{}]` to be a table", registry)
2310 })?;
2311 rtable.remove("token");
2312 rtable.remove("secret-key");
2313 rtable.remove("secret-key-subject");
2314 }
2315 }
2316 } else if let Some(registry) = toml.get_mut("registry") {
2317 let reg_table = registry
2318 .as_table_mut()
2319 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2320 reg_table.remove("token");
2321 reg_table.remove("secret-key");
2322 reg_table.remove("secret-key-subject");
2323 }
2324 }
2325
2326 let contents = toml.to_string();
2327 file.seek(SeekFrom::Start(0))?;
2328 file.write_all(contents.as_bytes())
2329 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2330 file.file().set_len(contents.len() as u64)?;
2331 set_permissions(file.file(), 0o600)
2332 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2333
2334 return Ok(());
2335
2336 #[cfg(unix)]
2337 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2338 use std::os::unix::fs::PermissionsExt;
2339
2340 let mut perms = file.metadata()?.permissions();
2341 perms.set_mode(mode);
2342 file.set_permissions(perms)?;
2343 Ok(())
2344 }
2345
2346 #[cfg(not(unix))]
2347 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2348 Ok(())
2349 }
2350}
2351
2352struct ConfigInclude {
2358 path: PathBuf,
2361 def: Definition,
2362 optional: bool,
2364}
2365
2366impl ConfigInclude {
2367 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2368 Self {
2369 path: p.into(),
2370 def,
2371 optional: false,
2372 }
2373 }
2374
2375 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2388 let abs_path = match &self.def {
2389 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2390 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2391 }
2392 .join(&self.path);
2393 let abs_path = paths::normalize_path(&abs_path);
2394
2395 if self.optional && !abs_path.exists() {
2396 tracing::info!(
2397 "skipping optional include `{}` in `{}`: file not found at `{}`",
2398 self.path.display(),
2399 self.def,
2400 abs_path.display(),
2401 );
2402 None
2403 } else {
2404 Some(abs_path)
2405 }
2406 }
2407}
2408
2409fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2410 toml.parse().map_err(Into::into)
2412}
2413
2414fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2415 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2421 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2422 })?;
2423 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2424 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2425 }
2426 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2427 non_empty(d.prefix()) || non_empty(d.suffix())
2428 }
2429 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2430 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2431 }
2432 let ok = {
2433 let mut got_to_value = false;
2434 let mut table = doc.as_table();
2435 let mut is_root = true;
2436 while table.is_dotted() || is_root {
2437 is_root = false;
2438 if table.len() != 1 {
2439 break;
2440 }
2441 let (k, n) = table.iter().next().expect("len() == 1 above");
2442 match n {
2443 Item::Table(nt) => {
2444 if table.key(k).map_or(false, non_empty_key_decor)
2445 || non_empty_decor(nt.decor())
2446 {
2447 bail!(
2448 "--config argument `{arg}` \
2449 includes non-whitespace decoration"
2450 )
2451 }
2452 table = nt;
2453 }
2454 Item::Value(v) if v.is_inline_table() => {
2455 bail!(
2456 "--config argument `{arg}` \
2457 sets a value to an inline table, which is not accepted"
2458 );
2459 }
2460 Item::Value(v) => {
2461 if table
2462 .key(k)
2463 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2464 || non_empty_decor(v.decor())
2465 {
2466 bail!(
2467 "--config argument `{arg}` \
2468 includes non-whitespace decoration"
2469 )
2470 }
2471 got_to_value = true;
2472 break;
2473 }
2474 Item::ArrayOfTables(_) => {
2475 bail!(
2476 "--config argument `{arg}` \
2477 sets a value to an array of tables, which is not accepted"
2478 );
2479 }
2480
2481 Item::None => {
2482 bail!("--config argument `{arg}` doesn't provide a value")
2483 }
2484 }
2485 }
2486 got_to_value
2487 };
2488 if !ok {
2489 bail!(
2490 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2491 );
2492 }
2493 Ok(doc)
2494}
2495
2496#[derive(Debug, Deserialize, Clone)]
2507pub struct StringList(Vec<String>);
2508
2509impl StringList {
2510 pub fn as_slice(&self) -> &[String] {
2511 &self.0
2512 }
2513}
2514
2515#[macro_export]
2516macro_rules! __shell_print {
2517 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2518 let mut shell = $config.shell();
2519 let out = shell.$which();
2520 drop(out.write_fmt(format_args!($($arg)*)));
2521 if $newline {
2522 drop(out.write_all(b"\n"));
2523 }
2524 });
2525}
2526
2527#[macro_export]
2528macro_rules! drop_println {
2529 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2530 ($config:expr, $($arg:tt)*) => (
2531 $crate::__shell_print!($config, out, true, $($arg)*)
2532 );
2533}
2534
2535#[macro_export]
2536macro_rules! drop_eprintln {
2537 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2538 ($config:expr, $($arg:tt)*) => (
2539 $crate::__shell_print!($config, err, true, $($arg)*)
2540 );
2541}
2542
2543#[macro_export]
2544macro_rules! drop_print {
2545 ($config:expr, $($arg:tt)*) => (
2546 $crate::__shell_print!($config, out, false, $($arg)*)
2547 );
2548}
2549
2550#[macro_export]
2551macro_rules! drop_eprint {
2552 ($config:expr, $($arg:tt)*) => (
2553 $crate::__shell_print!($config, err, false, $($arg)*)
2554 );
2555}
2556
2557enum Tool {
2558 Rustc,
2559 Rustdoc,
2560}
2561
2562impl Tool {
2563 fn as_str(&self) -> &str {
2564 match self {
2565 Tool::Rustc => "rustc",
2566 Tool::Rustdoc => "rustdoc",
2567 }
2568 }
2569}
2570
2571fn disables_multiplexing_for_bad_curl(
2581 curl_version: &str,
2582 http: &mut CargoHttpConfig,
2583 gctx: &GlobalContext,
2584) {
2585 use crate::util::network;
2586
2587 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2588 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2589 if bad_curl_versions
2590 .iter()
2591 .any(|v| curl_version.starts_with(v))
2592 {
2593 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2594 http.multiplexing = Some(false);
2595 }
2596 }
2597}
2598
2599#[cfg(test)]
2600mod tests {
2601 use super::CargoHttpConfig;
2602 use super::GlobalContext;
2603 use super::Shell;
2604 use super::disables_multiplexing_for_bad_curl;
2605
2606 #[test]
2607 fn disables_multiplexing() {
2608 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2609 gctx.set_search_stop_path(std::path::PathBuf::new());
2610 gctx.set_env(Default::default());
2611
2612 let mut http = CargoHttpConfig::default();
2613 http.proxy = Some("127.0.0.1:3128".into());
2614 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2615 assert_eq!(http.multiplexing, Some(false));
2616
2617 let cases = [
2618 (None, None, "7.87.0", None),
2619 (None, None, "7.88.0", None),
2620 (None, None, "7.88.1", None),
2621 (None, None, "8.0.0", None),
2622 (Some("".into()), None, "7.87.0", Some(false)),
2623 (Some("".into()), None, "7.88.0", Some(false)),
2624 (Some("".into()), None, "7.88.1", Some(false)),
2625 (Some("".into()), None, "8.0.0", None),
2626 (Some("".into()), Some(false), "7.87.0", Some(false)),
2627 (Some("".into()), Some(false), "7.88.0", Some(false)),
2628 (Some("".into()), Some(false), "7.88.1", Some(false)),
2629 (Some("".into()), Some(false), "8.0.0", Some(false)),
2630 ];
2631
2632 for (proxy, multiplexing, curl_v, result) in cases {
2633 let mut http = CargoHttpConfig {
2634 multiplexing,
2635 proxy,
2636 ..Default::default()
2637 };
2638 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2639 assert_eq!(http.multiplexing, result);
2640 }
2641 }
2642
2643 #[test]
2644 fn sync_context() {
2645 fn assert_sync<S: Sync>() {}
2646 assert_sync::<GlobalContext>();
2647 }
2648}