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