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