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