1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
65use std::borrow::Cow;
66use std::collections::{HashMap, HashSet};
67use std::env;
68use std::ffi::{OsStr, OsString};
69use std::fmt;
70use std::fs::{self, File};
71use std::io::SeekFrom;
72use std::io::prelude::*;
73use std::mem;
74use std::path::{Path, PathBuf};
75use std::str::FromStr;
76use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
77use std::time::Instant;
78
79use self::ConfigValue as CV;
80use crate::core::compiler::rustdoc::RustdocExternMap;
81use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
82use crate::core::shell::Verbosity;
83use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
84use crate::ops::RegistryCredentialConfig;
85use crate::sources::CRATES_IO_INDEX;
86use crate::sources::CRATES_IO_REGISTRY;
87use crate::util::OnceExt as _;
88use crate::util::errors::CargoResult;
89use crate::util::network::http::configure_http_handle;
90use crate::util::network::http::http_handle;
91use crate::util::{CanonicalUrl, closest_msg, internal};
92use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
93
94use annotate_snippets::Level;
95use anyhow::{Context as _, anyhow, bail, format_err};
96use cargo_credential::Secret;
97use cargo_util::paths;
98use cargo_util_schemas::manifest::RegistryName;
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::{ConfigRelativePath, PathAndArgs};
125
126mod target;
127pub use target::{TargetCfgConfig, TargetConfig};
128
129mod environment;
130use environment::Env;
131
132mod schema;
133pub use schema::*;
134
135use super::auth::RegistryConfig;
136
137macro_rules! get_value_typed {
139 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
140 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
142 let cv = self.get_cv(key)?;
143 let env = self.get_config_env::<$ty>(key)?;
144 match (cv, env) {
145 (Some(CV::$variant(val, definition)), Some(env)) => {
146 if definition.is_higher_priority(&env.definition) {
147 Ok(Some(Value { val, definition }))
148 } else {
149 Ok(Some(env))
150 }
151 }
152 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
153 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
154 (None, Some(env)) => Ok(Some(env)),
155 (None, None) => Ok(None),
156 }
157 }
158 };
159}
160
161pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
162 "paths",
163 "alias",
164 "build",
165 "credential-alias",
166 "doc",
167 "env",
168 "future-incompat-report",
169 "cache",
170 "cargo-new",
171 "http",
172 "install",
173 "net",
174 "patch",
175 "profile",
176 "resolver",
177 "registries",
178 "registry",
179 "source",
180 "target",
181 "term",
182];
183
184#[derive(Clone, Copy, Debug)]
186enum WhyLoad {
187 Cli,
194 FileDiscovery,
196}
197
198#[derive(Debug)]
200pub struct CredentialCacheValue {
201 pub token_value: Secret<String>,
202 pub expiration: Option<OffsetDateTime>,
203 pub operation_independent: bool,
204}
205
206#[derive(Debug)]
209pub struct GlobalContext {
210 home_path: Filesystem,
212 shell: Mutex<Shell>,
214 values: OnceLock<HashMap<String, ConfigValue>>,
216 credential_values: OnceLock<HashMap<String, ConfigValue>>,
218 cli_config: Option<Vec<String>>,
220 cwd: PathBuf,
222 search_stop_path: Option<PathBuf>,
224 cargo_exe: OnceLock<PathBuf>,
226 rustdoc: OnceLock<PathBuf>,
228 extra_verbose: bool,
230 frozen: bool,
233 locked: bool,
236 offline: bool,
239 jobserver: Option<jobserver::Client>,
241 unstable_flags: CliUnstable,
243 unstable_flags_cli: Option<Vec<String>>,
245 easy: OnceLock<Mutex<Easy>>,
247 crates_io_source_id: OnceLock<SourceId>,
249 cache_rustc_info: bool,
251 creation_time: Instant,
253 target_dir: Option<Filesystem>,
255 env: Env,
257 updated_sources: Mutex<HashSet<SourceId>>,
259 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
262 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
264 package_cache_lock: CacheLocker,
266 http_config: OnceLock<CargoHttpConfig>,
268 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
269 net_config: OnceLock<CargoNetConfig>,
270 build_config: OnceLock<CargoBuildConfig>,
271 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
272 doc_extern_map: OnceLock<RustdocExternMap>,
273 progress_config: ProgressConfig,
274 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
275 pub nightly_features_allowed: bool,
291 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
293 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
295 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
298}
299
300impl GlobalContext {
301 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
309 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
310 static INIT: Once = Once::new();
311
312 INIT.call_once(|| unsafe {
315 if let Some(client) = jobserver::Client::from_env() {
316 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
317 }
318 });
319
320 let env = Env::new();
321
322 let cache_key = "CARGO_CACHE_RUSTC_INFO";
323 let cache_rustc_info = match env.get_env_os(cache_key) {
324 Some(cache) => cache != "0",
325 _ => true,
326 };
327
328 GlobalContext {
329 home_path: Filesystem::new(homedir),
330 shell: Mutex::new(shell),
331 cwd,
332 search_stop_path: None,
333 values: Default::default(),
334 credential_values: Default::default(),
335 cli_config: None,
336 cargo_exe: Default::default(),
337 rustdoc: Default::default(),
338 extra_verbose: false,
339 frozen: false,
340 locked: false,
341 offline: false,
342 jobserver: unsafe {
343 if GLOBAL_JOBSERVER.is_null() {
344 None
345 } else {
346 Some((*GLOBAL_JOBSERVER).clone())
347 }
348 },
349 unstable_flags: CliUnstable::default(),
350 unstable_flags_cli: None,
351 easy: Default::default(),
352 crates_io_source_id: Default::default(),
353 cache_rustc_info,
354 creation_time: Instant::now(),
355 target_dir: None,
356 env,
357 updated_sources: Default::default(),
358 credential_cache: Default::default(),
359 registry_config: Default::default(),
360 package_cache_lock: CacheLocker::new(),
361 http_config: Default::default(),
362 future_incompat_config: Default::default(),
363 net_config: Default::default(),
364 build_config: Default::default(),
365 target_cfgs: Default::default(),
366 doc_extern_map: Default::default(),
367 progress_config: ProgressConfig::default(),
368 env_config: Default::default(),
369 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
370 ws_roots: Default::default(),
371 global_cache_tracker: Default::default(),
372 deferred_global_last_use: Default::default(),
373 }
374 }
375
376 pub fn default() -> CargoResult<GlobalContext> {
381 let shell = Shell::new();
382 let cwd =
383 env::current_dir().context("couldn't get the current directory of the process")?;
384 let homedir = homedir(&cwd).ok_or_else(|| {
385 anyhow!(
386 "Cargo couldn't find your home directory. \
387 This probably means that $HOME was not set."
388 )
389 })?;
390 Ok(GlobalContext::new(shell, cwd, homedir))
391 }
392
393 pub fn home(&self) -> &Filesystem {
395 &self.home_path
396 }
397
398 pub fn diagnostic_home_config(&self) -> String {
402 let home = self.home_path.as_path_unlocked();
403 let path = match self.get_file_path(home, "config", false) {
404 Ok(Some(existing_path)) => existing_path,
405 _ => home.join("config.toml"),
406 };
407 path.to_string_lossy().to_string()
408 }
409
410 pub fn git_path(&self) -> Filesystem {
412 self.home_path.join("git")
413 }
414
415 pub fn git_checkouts_path(&self) -> Filesystem {
418 self.git_path().join("checkouts")
419 }
420
421 pub fn git_db_path(&self) -> Filesystem {
424 self.git_path().join("db")
425 }
426
427 pub fn registry_base_path(&self) -> Filesystem {
429 self.home_path.join("registry")
430 }
431
432 pub fn registry_index_path(&self) -> Filesystem {
434 self.registry_base_path().join("index")
435 }
436
437 pub fn registry_cache_path(&self) -> Filesystem {
439 self.registry_base_path().join("cache")
440 }
441
442 pub fn registry_source_path(&self) -> Filesystem {
444 self.registry_base_path().join("src")
445 }
446
447 pub fn default_registry(&self) -> CargoResult<Option<String>> {
449 Ok(self
450 .get_string("registry.default")?
451 .map(|registry| registry.val))
452 }
453
454 pub fn shell(&self) -> MutexGuard<'_, Shell> {
456 self.shell.lock().unwrap()
457 }
458
459 pub fn debug_assert_shell_not_borrowed(&self) {
465 if cfg!(debug_assertions) {
466 match self.shell.try_lock() {
467 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
468 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
469 }
470 }
471 }
472
473 pub fn rustdoc(&self) -> CargoResult<&Path> {
475 self.rustdoc
476 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
477 .map(AsRef::as_ref)
478 }
479
480 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
482 let cache_location =
483 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
484 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
485 let rustc_workspace_wrapper = self.maybe_get_tool(
486 "rustc_workspace_wrapper",
487 &self.build_config()?.rustc_workspace_wrapper,
488 );
489
490 Rustc::new(
491 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
492 wrapper,
493 rustc_workspace_wrapper,
494 &self
495 .home()
496 .join("bin")
497 .join("rustc")
498 .into_path_unlocked()
499 .with_extension(env::consts::EXE_EXTENSION),
500 if self.cache_rustc_info {
501 cache_location
502 } else {
503 None
504 },
505 self,
506 )
507 }
508
509 pub fn cargo_exe(&self) -> CargoResult<&Path> {
511 self.cargo_exe
512 .try_borrow_with(|| {
513 let from_env = || -> CargoResult<PathBuf> {
514 let exe = self
519 .get_env_os(crate::CARGO_ENV)
520 .map(PathBuf::from)
521 .ok_or_else(|| anyhow!("$CARGO not set"))?;
522 Ok(exe)
523 };
524
525 fn from_current_exe() -> CargoResult<PathBuf> {
526 let exe = env::current_exe()?;
531 Ok(exe)
532 }
533
534 fn from_argv() -> CargoResult<PathBuf> {
535 let argv0 = env::args_os()
542 .map(PathBuf::from)
543 .next()
544 .ok_or_else(|| anyhow!("no argv[0]"))?;
545 paths::resolve_executable(&argv0)
546 }
547
548 fn is_cargo(path: &Path) -> bool {
551 path.file_stem() == Some(OsStr::new("cargo"))
552 }
553
554 let from_current_exe = from_current_exe();
555 if from_current_exe.as_deref().is_ok_and(is_cargo) {
556 return from_current_exe;
557 }
558
559 let from_argv = from_argv();
560 if from_argv.as_deref().is_ok_and(is_cargo) {
561 return from_argv;
562 }
563
564 let exe = from_env()
565 .or(from_current_exe)
566 .or(from_argv)
567 .context("couldn't get the path to cargo executable")?;
568 Ok(exe)
569 })
570 .map(AsRef::as_ref)
571 }
572
573 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
575 self.updated_sources.lock().unwrap()
576 }
577
578 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
580 self.credential_cache.lock().unwrap()
581 }
582
583 pub(crate) fn registry_config(
585 &self,
586 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
587 self.registry_config.lock().unwrap()
588 }
589
590 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
596 self.values.try_borrow_with(|| self.load_values())
597 }
598
599 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
606 let _ = self.values()?;
607 Ok(self.values.get_mut().expect("already loaded config values"))
608 }
609
610 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
612 if self.values.get().is_some() {
613 bail!("config values already found")
614 }
615 match self.values.set(values.into()) {
616 Ok(()) => Ok(()),
617 Err(_) => bail!("could not fill values"),
618 }
619 }
620
621 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
624 let path = path.into();
625 debug_assert!(self.cwd.starts_with(&path));
626 self.search_stop_path = Some(path);
627 }
628
629 pub fn reload_cwd(&mut self) -> CargoResult<()> {
633 let cwd =
634 env::current_dir().context("couldn't get the current directory of the process")?;
635 let homedir = homedir(&cwd).ok_or_else(|| {
636 anyhow!(
637 "Cargo couldn't find your home directory. \
638 This probably means that $HOME was not set."
639 )
640 })?;
641
642 self.cwd = cwd;
643 self.home_path = Filesystem::new(homedir);
644 self.reload_rooted_at(self.cwd.clone())?;
645 Ok(())
646 }
647
648 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
651 let values = self.load_values_from(path.as_ref())?;
652 self.values.replace(values);
653 self.merge_cli_args()?;
654 self.load_unstable_flags_from_config()?;
655 Ok(())
656 }
657
658 pub fn cwd(&self) -> &Path {
660 &self.cwd
661 }
662
663 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
669 if let Some(dir) = &self.target_dir {
670 Ok(Some(dir.clone()))
671 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
672 if dir.is_empty() {
674 bail!(
675 "the target directory is set to an empty string in the \
676 `CARGO_TARGET_DIR` environment variable"
677 )
678 }
679
680 Ok(Some(Filesystem::new(self.cwd.join(dir))))
681 } else if let Some(val) = &self.build_config()?.target_dir {
682 let path = val.resolve_path(self);
683
684 if val.raw_value().is_empty() {
686 bail!(
687 "the target directory is set to an empty string in {}",
688 val.value().definition
689 )
690 }
691
692 Ok(Some(Filesystem::new(path)))
693 } else {
694 Ok(None)
695 }
696 }
697
698 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
702 let Some(val) = &self.build_config()?.build_dir else {
703 return Ok(None);
704 };
705 self.custom_build_dir(val, workspace_manifest_path)
706 .map(Some)
707 }
708
709 pub fn custom_build_dir(
713 &self,
714 val: &ConfigRelativePath,
715 workspace_manifest_path: &Path,
716 ) -> CargoResult<Filesystem> {
717 let replacements = [
718 (
719 "{workspace-root}",
720 workspace_manifest_path
721 .parent()
722 .unwrap()
723 .to_str()
724 .context("workspace root was not valid utf-8")?
725 .to_string(),
726 ),
727 (
728 "{cargo-cache-home}",
729 self.home()
730 .as_path_unlocked()
731 .to_str()
732 .context("cargo home was not valid utf-8")?
733 .to_string(),
734 ),
735 ("{workspace-path-hash}", {
736 let real_path = std::fs::canonicalize(workspace_manifest_path)
737 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
738 let hash = crate::util::hex::short_hash(&real_path);
739 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
740 }),
741 ];
742
743 let template_variables = replacements
744 .iter()
745 .map(|(key, _)| key[1..key.len() - 1].to_string())
746 .collect_vec();
747
748 let path = val
749 .resolve_templated_path(self, replacements)
750 .map_err(|e| match e {
751 path::ResolveTemplateError::UnexpectedVariable {
752 variable,
753 raw_template,
754 } => {
755 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
756 if suggestion == "" {
757 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
758 suggestion = format!("\n\nhelp: available template variables are {variables}");
759 }
760 anyhow!(
761 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
762 )
763 },
764 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
765 let (btype, literal) = match bracket_type {
766 path::BracketType::Opening => ("opening", "{"),
767 path::BracketType::Closing => ("closing", "}"),
768 };
769
770 anyhow!(
771 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
772 )
773 }
774 })?;
775
776 if val.raw_value().is_empty() {
778 bail!(
779 "the build directory is set to an empty string in {}",
780 val.value().definition
781 )
782 }
783
784 Ok(Filesystem::new(path))
785 }
786
787 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
792 if let Some(vals) = self.credential_values.get() {
793 let val = self.get_cv_helper(key, vals)?;
794 if val.is_some() {
795 return Ok(val);
796 }
797 }
798 self.get_cv_helper(key, &*self.values()?)
799 }
800
801 fn get_cv_helper(
802 &self,
803 key: &ConfigKey,
804 vals: &HashMap<String, ConfigValue>,
805 ) -> CargoResult<Option<ConfigValue>> {
806 tracing::trace!("get cv {:?}", key);
807 if key.is_root() {
808 return Ok(Some(CV::Table(
811 vals.clone(),
812 Definition::Path(PathBuf::new()),
813 )));
814 }
815 let mut parts = key.parts().enumerate();
816 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
817 return Ok(None);
818 };
819 for (i, part) in parts {
820 match val {
821 CV::Table(map, _) => {
822 val = match map.get(part) {
823 Some(val) => val,
824 None => return Ok(None),
825 }
826 }
827 CV::Integer(_, def)
828 | CV::String(_, def)
829 | CV::List(_, def)
830 | CV::Boolean(_, def) => {
831 let mut key_so_far = ConfigKey::new();
832 for part in key.parts().take(i) {
833 key_so_far.push(part);
834 }
835 bail!(
836 "expected table for configuration key `{}`, \
837 but found {} in {}",
838 key_so_far,
839 val.desc(),
840 def
841 )
842 }
843 }
844 }
845 Ok(Some(val.clone()))
846 }
847
848 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
850 let cv = self.get_cv(key)?;
853 if key.is_root() {
854 return Ok(cv);
856 }
857 let env = self.env.get_str(key.as_env_key());
858 let env_def = Definition::Environment(key.as_env_key().to_string());
859 let use_env = match (&cv, env) {
860 (Some(CV::List(..)), Some(_)) => true,
862 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
863 (None, Some(_)) => true,
864 _ => false,
865 };
866
867 if !use_env {
868 return Ok(cv);
869 }
870
871 let env = env.unwrap();
875 if env == "true" {
876 Ok(Some(CV::Boolean(true, env_def)))
877 } else if env == "false" {
878 Ok(Some(CV::Boolean(false, env_def)))
879 } else if let Ok(i) = env.parse::<i64>() {
880 Ok(Some(CV::Integer(i, env_def)))
881 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
882 match cv {
883 Some(CV::List(mut cv_list, cv_def)) => {
884 self.get_env_list(key, &mut cv_list)?;
886 Ok(Some(CV::List(cv_list, cv_def)))
887 }
888 Some(cv) => {
889 bail!(
893 "unable to merge array env for config `{}`\n\
894 file: {:?}\n\
895 env: {}",
896 key,
897 cv,
898 env
899 );
900 }
901 None => {
902 let mut cv_list = Vec::new();
903 self.get_env_list(key, &mut cv_list)?;
904 Ok(Some(CV::List(cv_list, env_def)))
905 }
906 }
907 } else {
908 match cv {
910 Some(CV::List(mut cv_list, cv_def)) => {
911 self.get_env_list(key, &mut cv_list)?;
913 Ok(Some(CV::List(cv_list, cv_def)))
914 }
915 _ => {
916 Ok(Some(CV::String(env.to_string(), env_def)))
921 }
922 }
923 }
924 }
925
926 pub fn set_env(&mut self, env: HashMap<String, String>) {
928 self.env = Env::from_map(env);
929 }
930
931 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
934 self.env.iter_str()
935 }
936
937 fn env_keys(&self) -> impl Iterator<Item = &str> {
939 self.env.keys_str()
940 }
941
942 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
943 where
944 T: FromStr,
945 <T as FromStr>::Err: fmt::Display,
946 {
947 match self.env.get_str(key.as_env_key()) {
948 Some(value) => {
949 let definition = Definition::Environment(key.as_env_key().to_string());
950 Ok(Some(Value {
951 val: value
952 .parse()
953 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
954 definition,
955 }))
956 }
957 None => {
958 self.check_environment_key_case_mismatch(key);
959 Ok(None)
960 }
961 }
962 }
963
964 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
969 self.env.get_env(key)
970 }
971
972 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
977 self.env.get_env_os(key)
978 }
979
980 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
984 if self.env.contains_key(key.as_env_key()) {
985 return Ok(true);
986 }
987 if env_prefix_ok {
988 let env_prefix = format!("{}_", key.as_env_key());
989 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
990 return Ok(true);
991 }
992 }
993 if self.get_cv(key)?.is_some() {
994 return Ok(true);
995 }
996 self.check_environment_key_case_mismatch(key);
997
998 Ok(false)
999 }
1000
1001 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1002 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1003 let _ = self.shell().warn(format!(
1004 "environment variables are expected to use uppercase letters and underscores, \
1005 the variable `{}` will be ignored and have no effect",
1006 env_key
1007 ));
1008 }
1009 }
1010
1011 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
1015 self.get::<OptValue<String>>(key)
1016 }
1017
1018 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1019 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1020 if is_path {
1021 definition.root(self.cwd()).join(value)
1022 } else {
1023 PathBuf::from(value)
1025 }
1026 }
1027
1028 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1031 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1032 self.check_environment_key_case_mismatch(key);
1033 return Ok(());
1034 };
1035
1036 let env_def = Definition::Environment(key.as_env_key().to_string());
1037
1038 if is_nonmergeable_list(&key) {
1039 assert!(
1040 output
1041 .windows(2)
1042 .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1043 "non-mergeable list must have only one definition: {output:?}",
1044 );
1045
1046 if output
1049 .first()
1050 .map(|o| o.definition() > &env_def)
1051 .unwrap_or_default()
1052 {
1053 return Ok(());
1054 } else {
1055 output.clear();
1056 }
1057 }
1058
1059 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1060 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1062 ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1063 })?;
1064 let values = toml_v.as_array().expect("env var was not array");
1065 for value in values {
1066 let s = value.as_str().ok_or_else(|| {
1069 ConfigError::new(
1070 format!("expected string, found {}", value.type_str()),
1071 env_def.clone(),
1072 )
1073 })?;
1074 output.push(CV::String(s.to_string(), env_def.clone()))
1075 }
1076 } else {
1077 output.extend(
1078 env_val
1079 .split_whitespace()
1080 .map(|s| CV::String(s.to_string(), env_def.clone())),
1081 );
1082 }
1083 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1084 Ok(())
1085 }
1086
1087 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1091 match self.get_cv(key)? {
1092 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1093 Some(val) => self.expected("table", key, &val),
1094 None => Ok(None),
1095 }
1096 }
1097
1098 get_value_typed! {get_integer, i64, Integer, "an integer"}
1099 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1100 get_value_typed! {get_string_priv, String, String, "a string"}
1101
1102 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1104 val.expected(ty, &key.to_string())
1105 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1106 }
1107
1108 pub fn configure(
1114 &mut self,
1115 verbose: u32,
1116 quiet: bool,
1117 color: Option<&str>,
1118 frozen: bool,
1119 locked: bool,
1120 offline: bool,
1121 target_dir: &Option<PathBuf>,
1122 unstable_flags: &[String],
1123 cli_config: &[String],
1124 ) -> CargoResult<()> {
1125 for warning in self
1126 .unstable_flags
1127 .parse(unstable_flags, self.nightly_features_allowed)?
1128 {
1129 self.shell().warn(warning)?;
1130 }
1131 if !unstable_flags.is_empty() {
1132 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1135 }
1136 if !cli_config.is_empty() {
1137 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1138 self.merge_cli_args()?;
1139 }
1140
1141 self.load_unstable_flags_from_config()?;
1145 if self.unstable_flags.config_include {
1146 self.reload_rooted_at(self.cwd.clone())?;
1153 }
1154
1155 let term = self.get::<TermConfig>("term").unwrap_or_default();
1159
1160 let extra_verbose = verbose >= 2;
1162 let verbose = verbose != 0;
1163 let verbosity = match (verbose, quiet) {
1164 (true, true) => bail!("cannot set both --verbose and --quiet"),
1165 (true, false) => Verbosity::Verbose,
1166 (false, true) => Verbosity::Quiet,
1167 (false, false) => match (term.verbose, term.quiet) {
1168 (Some(true), Some(true)) => {
1169 bail!("cannot set both `term.verbose` and `term.quiet`")
1170 }
1171 (Some(true), _) => Verbosity::Verbose,
1172 (_, Some(true)) => Verbosity::Quiet,
1173 _ => Verbosity::Normal,
1174 },
1175 };
1176 self.shell().set_verbosity(verbosity);
1177 self.extra_verbose = extra_verbose;
1178
1179 let color = color.or_else(|| term.color.as_deref());
1180 self.shell().set_color_choice(color)?;
1181 if let Some(hyperlinks) = term.hyperlinks {
1182 self.shell().set_hyperlinks(hyperlinks)?;
1183 }
1184 if let Some(unicode) = term.unicode {
1185 self.shell().set_unicode(unicode)?;
1186 }
1187
1188 self.progress_config = term.progress.unwrap_or_default();
1189
1190 self.frozen = frozen;
1191 self.locked = locked;
1192 self.offline = offline
1193 || self
1194 .net_config()
1195 .ok()
1196 .and_then(|n| n.offline)
1197 .unwrap_or(false);
1198 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1199 self.target_dir = cli_target_dir;
1200
1201 self.shell()
1202 .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1203
1204 Ok(())
1205 }
1206
1207 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1208 if self.nightly_features_allowed {
1211 self.unstable_flags = self
1212 .get::<Option<CliUnstable>>("unstable")?
1213 .unwrap_or_default();
1214 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1215 self.unstable_flags.parse(unstable_flags_cli, true)?;
1220 }
1221 }
1222
1223 Ok(())
1224 }
1225
1226 pub fn cli_unstable(&self) -> &CliUnstable {
1227 &self.unstable_flags
1228 }
1229
1230 pub fn extra_verbose(&self) -> bool {
1231 self.extra_verbose
1232 }
1233
1234 pub fn network_allowed(&self) -> bool {
1235 !self.offline_flag().is_some()
1236 }
1237
1238 pub fn offline_flag(&self) -> Option<&'static str> {
1239 if self.frozen {
1240 Some("--frozen")
1241 } else if self.offline {
1242 Some("--offline")
1243 } else {
1244 None
1245 }
1246 }
1247
1248 pub fn set_locked(&mut self, locked: bool) {
1249 self.locked = locked;
1250 }
1251
1252 pub fn lock_update_allowed(&self) -> bool {
1253 !self.locked_flag().is_some()
1254 }
1255
1256 pub fn locked_flag(&self) -> Option<&'static str> {
1257 if self.frozen {
1258 Some("--frozen")
1259 } else if self.locked {
1260 Some("--locked")
1261 } else {
1262 None
1263 }
1264 }
1265
1266 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1268 self.load_values_from(&self.cwd)
1269 }
1270
1271 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1275 let mut result = Vec::new();
1276 let mut seen = HashSet::new();
1277 let home = self.home_path.clone().into_path_unlocked();
1278 self.walk_tree(&self.cwd, &home, |path| {
1279 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1280 if self.cli_unstable().config_include {
1281 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1282 }
1283 result.push(cv);
1284 Ok(())
1285 })
1286 .context("could not load Cargo configuration")?;
1287 Ok(result)
1288 }
1289
1290 fn load_unmerged_include(
1294 &self,
1295 cv: &mut CV,
1296 seen: &mut HashSet<PathBuf>,
1297 output: &mut Vec<CV>,
1298 ) -> CargoResult<()> {
1299 let includes = self.include_paths(cv, false)?;
1300 for include in includes {
1301 let Some(abs_path) = include.resolve_path(self) else {
1302 continue;
1303 };
1304
1305 let mut cv = self
1306 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1307 .with_context(|| {
1308 format!(
1309 "failed to load config include `{}` from `{}`",
1310 include.path.display(),
1311 include.def
1312 )
1313 })?;
1314 self.load_unmerged_include(&mut cv, seen, output)?;
1315 output.push(cv);
1316 }
1317 Ok(())
1318 }
1319
1320 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1322 let mut cfg = CV::Table(HashMap::new(), Definition::BuiltIn);
1325 let home = self.home_path.clone().into_path_unlocked();
1326
1327 self.walk_tree(path, &home, |path| {
1328 let value = self.load_file(path)?;
1329 cfg.merge(value, false).with_context(|| {
1330 format!("failed to merge configuration at `{}`", path.display())
1331 })?;
1332 Ok(())
1333 })
1334 .context("could not load Cargo configuration")?;
1335
1336 match cfg {
1337 CV::Table(map, _) => Ok(map),
1338 _ => unreachable!(),
1339 }
1340 }
1341
1342 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1346 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1347 }
1348
1349 fn _load_file(
1359 &self,
1360 path: &Path,
1361 seen: &mut HashSet<PathBuf>,
1362 includes: bool,
1363 why_load: WhyLoad,
1364 ) -> CargoResult<ConfigValue> {
1365 if !seen.insert(path.to_path_buf()) {
1366 bail!(
1367 "config `include` cycle detected with path `{}`",
1368 path.display()
1369 );
1370 }
1371 tracing::debug!(?path, ?why_load, includes, "load config from file");
1372
1373 let contents = fs::read_to_string(path)
1374 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1375 let toml = parse_document(&contents, path, self).with_context(|| {
1376 format!("could not parse TOML configuration in `{}`", path.display())
1377 })?;
1378 let def = match why_load {
1379 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1380 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1381 };
1382 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1383 format!(
1384 "failed to load TOML configuration from `{}`",
1385 path.display()
1386 )
1387 })?;
1388 if includes {
1389 self.load_includes(value, seen, why_load)
1390 } else {
1391 Ok(value)
1392 }
1393 }
1394
1395 fn load_includes(
1402 &self,
1403 mut value: CV,
1404 seen: &mut HashSet<PathBuf>,
1405 why_load: WhyLoad,
1406 ) -> CargoResult<CV> {
1407 let includes = self.include_paths(&mut value, true)?;
1409 if !self.cli_unstable().config_include {
1411 return Ok(value);
1412 }
1413 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1415 for include in includes {
1416 let Some(abs_path) = include.resolve_path(self) else {
1417 continue;
1418 };
1419
1420 self._load_file(&abs_path, seen, true, why_load)
1421 .and_then(|include| root.merge(include, true))
1422 .with_context(|| {
1423 format!(
1424 "failed to load config include `{}` from `{}`",
1425 include.path.display(),
1426 include.def
1427 )
1428 })?;
1429 }
1430 root.merge(value, true)?;
1431 Ok(root)
1432 }
1433
1434 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1436 let CV::Table(table, _def) = cv else {
1437 unreachable!()
1438 };
1439 let include = if remove {
1440 table.remove("include").map(Cow::Owned)
1441 } else {
1442 table.get("include").map(Cow::Borrowed)
1443 };
1444 let includes = match include.map(|c| c.into_owned()) {
1445 Some(CV::String(s, def)) => vec![ConfigInclude::new(s, def)],
1446 Some(CV::List(list, _def)) => list
1447 .into_iter()
1448 .enumerate()
1449 .map(|(idx, cv)| match cv {
1450 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1451 CV::Table(mut table, def) => {
1452 let s = match table.remove("path") {
1454 Some(CV::String(s, _)) => s,
1455 Some(other) => bail!(
1456 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1457 other.desc()
1458 ),
1459 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1460 };
1461
1462 let optional = match table.remove("optional") {
1464 Some(CV::Boolean(b, _)) => b,
1465 Some(other) => bail!(
1466 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1467 other.desc()
1468 ),
1469 None => false,
1470 };
1471
1472 let mut include = ConfigInclude::new(s, def);
1473 include.optional = optional;
1474 Ok(include)
1475 }
1476 other => bail!(
1477 "expected a string or table, but found {} at `include[{idx}]` in {}",
1478 other.desc(),
1479 other.definition(),
1480 ),
1481 })
1482 .collect::<CargoResult<Vec<_>>>()?,
1483 Some(other) => bail!(
1484 "expected a string or list of strings, but found {} at `include` in `{}",
1485 other.desc(),
1486 other.definition()
1487 ),
1488 None => {
1489 return Ok(Vec::new());
1490 }
1491 };
1492
1493 for include in &includes {
1494 if include.path.extension() != Some(OsStr::new("toml")) {
1495 bail!(
1496 "expected a config include path ending with `.toml`, \
1497 but found `{}` from `{}`",
1498 include.path.display(),
1499 include.def,
1500 )
1501 }
1502 }
1503
1504 Ok(includes)
1505 }
1506
1507 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1509 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1510 let Some(cli_args) = &self.cli_config else {
1511 return Ok(loaded_args);
1512 };
1513 let mut seen = HashSet::new();
1514 for arg in cli_args {
1515 let arg_as_path = self.cwd.join(arg);
1516 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1517 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1519 .with_context(|| {
1520 format!("failed to load config from `{}`", arg_as_path.display())
1521 })?
1522 } else {
1523 let doc = toml_dotted_keys(arg)?;
1524 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1525 .with_context(|| {
1526 format!("failed to parse value from --config argument `{arg}`")
1527 })?;
1528
1529 if doc
1530 .get("registry")
1531 .and_then(|v| v.as_table())
1532 .and_then(|t| t.get("token"))
1533 .is_some()
1534 {
1535 bail!("registry.token cannot be set through --config for security reasons");
1536 } else if let Some((k, _)) = doc
1537 .get("registries")
1538 .and_then(|v| v.as_table())
1539 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1540 {
1541 bail!(
1542 "registries.{}.token cannot be set through --config for security reasons",
1543 k
1544 );
1545 }
1546
1547 if doc
1548 .get("registry")
1549 .and_then(|v| v.as_table())
1550 .and_then(|t| t.get("secret-key"))
1551 .is_some()
1552 {
1553 bail!(
1554 "registry.secret-key cannot be set through --config for security reasons"
1555 );
1556 } else if let Some((k, _)) = doc
1557 .get("registries")
1558 .and_then(|v| v.as_table())
1559 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1560 {
1561 bail!(
1562 "registries.{}.secret-key cannot be set through --config for security reasons",
1563 k
1564 );
1565 }
1566
1567 CV::from_toml(Definition::Cli(None), doc)
1568 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1569 };
1570 let tmp_table = self
1571 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1572 .context("failed to load --config include".to_string())?;
1573 loaded_args
1574 .merge(tmp_table, true)
1575 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1576 }
1577 Ok(loaded_args)
1578 }
1579
1580 fn merge_cli_args(&mut self) -> CargoResult<()> {
1582 let cv_from_cli = self.cli_args_as_table()?;
1583 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1584
1585 let root_cv = mem::take(self.values_mut()?);
1586 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1589 root_cv.merge(cv_from_cli, true)?;
1590
1591 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1593
1594 Ok(())
1595 }
1596
1597 fn get_file_path(
1603 &self,
1604 dir: &Path,
1605 filename_without_extension: &str,
1606 warn: bool,
1607 ) -> CargoResult<Option<PathBuf>> {
1608 let possible = dir.join(filename_without_extension);
1609 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1610
1611 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1612 if warn {
1613 if let Ok(possible_with_extension_handle) =
1614 same_file::Handle::from_path(&possible_with_extension)
1615 {
1616 if possible_handle != possible_with_extension_handle {
1622 self.shell().warn(format!(
1623 "both `{}` and `{}` exist. Using `{}`",
1624 possible.display(),
1625 possible_with_extension.display(),
1626 possible.display()
1627 ))?;
1628 }
1629 } else {
1630 self.shell().print_report(&[
1631 Level::WARNING.secondary_title(
1632 format!(
1633 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1634 possible.display(),
1635 )).element(Level::HELP.message(
1636 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1637
1638 ], false)?;
1639 }
1640 }
1641
1642 Ok(Some(possible))
1643 } else if possible_with_extension.exists() {
1644 Ok(Some(possible_with_extension))
1645 } else {
1646 Ok(None)
1647 }
1648 }
1649
1650 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1651 where
1652 F: FnMut(&Path) -> CargoResult<()>,
1653 {
1654 let mut seen_dir = HashSet::new();
1655
1656 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1657 let config_root = current.join(".cargo");
1658 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1659 walk(&path)?;
1660 }
1661 seen_dir.insert(config_root);
1662 }
1663
1664 if !seen_dir.contains(home) {
1668 if let Some(path) = self.get_file_path(home, "config", true)? {
1669 walk(&path)?;
1670 }
1671 }
1672
1673 Ok(())
1674 }
1675
1676 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1678 RegistryName::new(registry)?;
1679 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1680 self.resolve_registry_index(&index).with_context(|| {
1681 format!(
1682 "invalid index URL for registry `{}` defined in {}",
1683 registry, index.definition
1684 )
1685 })
1686 } else {
1687 bail!(
1688 "registry index was not found in any configuration: `{}`",
1689 registry
1690 );
1691 }
1692 }
1693
1694 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1696 if self.get_string("registry.index")?.is_some() {
1697 bail!(
1698 "the `registry.index` config value is no longer supported\n\
1699 Use `[source]` replacement to alter the default index for crates.io."
1700 );
1701 }
1702 Ok(())
1703 }
1704
1705 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1706 let base = index
1708 .definition
1709 .root(self.cwd())
1710 .join("truncated-by-url_with_base");
1711 let _parsed = index.val.into_url()?;
1713 let url = index.val.into_url_with_base(Some(&*base))?;
1714 if url.password().is_some() {
1715 bail!("registry URLs may not contain passwords");
1716 }
1717 Ok(url)
1718 }
1719
1720 pub fn load_credentials(&self) -> CargoResult<()> {
1728 if self.credential_values.filled() {
1729 return Ok(());
1730 }
1731
1732 let home_path = self.home_path.clone().into_path_unlocked();
1733 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1734 return Ok(());
1735 };
1736
1737 let mut value = self.load_file(&credentials)?;
1738 {
1740 let (value_map, def) = value.table_mut("<root>")?;
1741
1742 if let Some(token) = value_map.remove("token") {
1743 value_map.entry("registry".into()).or_insert_with(|| {
1744 let map = HashMap::from([("token".into(), token)]);
1745 CV::Table(map, def.clone())
1746 });
1747 }
1748 }
1749
1750 let mut credential_values = HashMap::new();
1751 if let CV::Table(map, _) = value {
1752 let base_map = self.values()?;
1753 for (k, v) in map {
1754 let entry = match base_map.get(&k) {
1755 Some(base_entry) => {
1756 let mut entry = base_entry.clone();
1757 entry.merge(v, true)?;
1758 entry
1759 }
1760 None => v,
1761 };
1762 credential_values.insert(k, entry);
1763 }
1764 }
1765 self.credential_values
1766 .set(credential_values)
1767 .expect("was not filled at beginning of the function");
1768 Ok(())
1769 }
1770
1771 fn maybe_get_tool(
1774 &self,
1775 tool: &str,
1776 from_config: &Option<ConfigRelativePath>,
1777 ) -> Option<PathBuf> {
1778 let var = tool.to_uppercase();
1779
1780 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1781 Some(tool_path) => {
1782 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1783 let path = if maybe_relative {
1784 self.cwd.join(tool_path)
1785 } else {
1786 PathBuf::from(tool_path)
1787 };
1788 Some(path)
1789 }
1790
1791 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1792 }
1793 }
1794
1795 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1806 let tool_str = tool.as_str();
1807 self.maybe_get_tool(tool_str, from_config)
1808 .or_else(|| {
1809 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1823 if toolchain.to_str()?.contains(&['/', '\\']) {
1826 return None;
1827 }
1828 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1831 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1832 let tool_meta = tool_resolved.metadata().ok()?;
1833 let rustup_meta = rustup_resolved.metadata().ok()?;
1834 if tool_meta.len() != rustup_meta.len() {
1839 return None;
1840 }
1841 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1843 let toolchain_exe = home::rustup_home()
1844 .ok()?
1845 .join("toolchains")
1846 .join(&toolchain)
1847 .join("bin")
1848 .join(&tool_exe);
1849 toolchain_exe.exists().then_some(toolchain_exe)
1850 })
1851 .unwrap_or_else(|| PathBuf::from(tool_str))
1852 }
1853
1854 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1856 let key = ConfigKey::from_str("paths");
1857 match self.get_cv(&key)? {
1859 Some(CV::List(val, definition)) => {
1860 let val = val
1861 .into_iter()
1862 .map(|cv| match cv {
1863 CV::String(s, def) => Ok((s, def)),
1864 other => self.expected("string", &key, &other),
1865 })
1866 .collect::<CargoResult<Vec<_>>>()?;
1867 Ok(Some(Value { val, definition }))
1868 }
1869 Some(val) => self.expected("list", &key, &val),
1870 None => Ok(None),
1871 }
1872 }
1873
1874 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1875 self.jobserver.as_ref()
1876 }
1877
1878 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1879 let http = self
1880 .easy
1881 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1882 {
1883 let mut http = http.lock().unwrap();
1884 http.reset();
1885 let timeout = configure_http_handle(self, &mut http)?;
1886 timeout.configure(&mut http)?;
1887 }
1888 Ok(http)
1889 }
1890
1891 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1892 self.http_config.try_borrow_with(|| {
1893 let mut http = self.get::<CargoHttpConfig>("http")?;
1894 let curl_v = curl::Version::get();
1895 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1896 Ok(http)
1897 })
1898 }
1899
1900 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1901 self.future_incompat_config
1902 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1903 }
1904
1905 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1906 self.net_config
1907 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1908 }
1909
1910 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1911 self.build_config
1912 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1913 }
1914
1915 pub fn progress_config(&self) -> &ProgressConfig {
1916 &self.progress_config
1917 }
1918
1919 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1922 let env_config = self.env_config.try_borrow_with(|| {
1923 CargoResult::Ok(Arc::new({
1924 let env_config = self.get::<EnvConfig>("env")?;
1925 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1941 if env_config.contains_key(*disallowed) {
1942 bail!(
1943 "setting the `{disallowed}` environment variable is not supported \
1944 in the `[env]` configuration table"
1945 );
1946 }
1947 }
1948 env_config
1949 .into_iter()
1950 .filter_map(|(k, v)| {
1951 if v.is_force() || self.get_env_os(&k).is_none() {
1952 Some((k, v.resolve(self.cwd()).to_os_string()))
1953 } else {
1954 None
1955 }
1956 })
1957 .collect()
1958 }))
1959 })?;
1960
1961 Ok(env_config)
1962 }
1963
1964 pub fn validate_term_config(&self) -> CargoResult<()> {
1970 drop(self.get::<TermConfig>("term")?);
1971 Ok(())
1972 }
1973
1974 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1978 self.target_cfgs
1979 .try_borrow_with(|| target::load_target_cfgs(self))
1980 }
1981
1982 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1983 self.doc_extern_map
1987 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1988 }
1989
1990 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1992 target::get_target_applies_to_host(self)
1993 }
1994
1995 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1997 target::load_host_triple(self, target)
1998 }
1999
2000 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2002 target::load_target_triple(self, target)
2003 }
2004
2005 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2010 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2011 self.check_registry_index_not_set()?;
2012 let url = CRATES_IO_INDEX.into_url().unwrap();
2013 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2014 })?;
2015 Ok(*source_id)
2016 }
2017
2018 pub fn creation_time(&self) -> Instant {
2019 self.creation_time
2020 }
2021
2022 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2037 let d = Deserializer {
2038 gctx: self,
2039 key: ConfigKey::from_str(key),
2040 env_prefix_ok: true,
2041 };
2042 T::deserialize(d).map_err(|e| e.into())
2043 }
2044
2045 #[track_caller]
2051 #[tracing::instrument(skip_all)]
2052 pub fn assert_package_cache_locked<'a>(
2053 &self,
2054 mode: CacheLockMode,
2055 f: &'a Filesystem,
2056 ) -> &'a Path {
2057 let ret = f.as_path_unlocked();
2058 assert!(
2059 self.package_cache_lock.is_locked(mode),
2060 "package cache lock is not currently held, Cargo forgot to call \
2061 `acquire_package_cache_lock` before we got to this stack frame",
2062 );
2063 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2064 ret
2065 }
2066
2067 #[tracing::instrument(skip_all)]
2073 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2074 self.package_cache_lock.lock(self, mode)
2075 }
2076
2077 #[tracing::instrument(skip_all)]
2083 pub fn try_acquire_package_cache_lock(
2084 &self,
2085 mode: CacheLockMode,
2086 ) -> CargoResult<Option<CacheLock<'_>>> {
2087 self.package_cache_lock.try_lock(self, mode)
2088 }
2089
2090 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2095 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2096 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2097 })?;
2098 Ok(tracker.lock().unwrap())
2099 }
2100
2101 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2103 let deferred = self
2104 .deferred_global_last_use
2105 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2106 Ok(deferred.lock().unwrap())
2107 }
2108
2109 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2111 if self.unstable_flags.warnings {
2112 Ok(self.build_config()?.warnings.unwrap_or_default())
2113 } else {
2114 Ok(WarningHandling::default())
2115 }
2116 }
2117
2118 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2119 self.ws_roots.lock().unwrap()
2120 }
2121}
2122
2123pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2124 ::home::cargo_home_with_cwd(cwd).ok()
2125}
2126
2127pub fn save_credentials(
2128 gctx: &GlobalContext,
2129 token: Option<RegistryCredentialConfig>,
2130 registry: &SourceId,
2131) -> CargoResult<()> {
2132 let registry = if registry.is_crates_io() {
2133 None
2134 } else {
2135 let name = registry
2136 .alt_registry_key()
2137 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2138 Some(name)
2139 };
2140
2141 let home_path = gctx.home_path.clone().into_path_unlocked();
2145 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2146 Some(path) => match path.file_name() {
2147 Some(filename) => Path::new(filename).to_owned(),
2148 None => Path::new("credentials.toml").to_owned(),
2149 },
2150 None => Path::new("credentials.toml").to_owned(),
2151 };
2152
2153 let mut file = {
2154 gctx.home_path.create_dir()?;
2155 gctx.home_path
2156 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2157 };
2158
2159 let mut contents = String::new();
2160 file.read_to_string(&mut contents).with_context(|| {
2161 format!(
2162 "failed to read configuration file `{}`",
2163 file.path().display()
2164 )
2165 })?;
2166
2167 let mut toml = parse_document(&contents, file.path(), gctx)?;
2168
2169 if let Some(token) = toml.remove("token") {
2171 let map = HashMap::from([("token".to_string(), token)]);
2172 toml.insert("registry".into(), map.into());
2173 }
2174
2175 if let Some(token) = token {
2176 let path_def = Definition::Path(file.path().to_path_buf());
2179 let (key, mut value) = match token {
2180 RegistryCredentialConfig::Token(token) => {
2181 let key = "token".to_string();
2184 let value = ConfigValue::String(token.expose(), path_def.clone());
2185 let map = HashMap::from([(key, value)]);
2186 let table = CV::Table(map, path_def.clone());
2187
2188 if let Some(registry) = registry {
2189 let map = HashMap::from([(registry.to_string(), table)]);
2190 ("registries".into(), CV::Table(map, path_def.clone()))
2191 } else {
2192 ("registry".into(), table)
2193 }
2194 }
2195 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2196 let key = "secret-key".to_string();
2199 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2200 let mut map = HashMap::from([(key, value)]);
2201 if let Some(key_subject) = key_subject {
2202 let key = "secret-key-subject".to_string();
2203 let value = ConfigValue::String(key_subject, path_def.clone());
2204 map.insert(key, value);
2205 }
2206 let table = CV::Table(map, path_def.clone());
2207
2208 if let Some(registry) = registry {
2209 let map = HashMap::from([(registry.to_string(), table)]);
2210 ("registries".into(), CV::Table(map, path_def.clone()))
2211 } else {
2212 ("registry".into(), table)
2213 }
2214 }
2215 _ => unreachable!(),
2216 };
2217
2218 if registry.is_some() {
2219 if let Some(table) = toml.remove("registries") {
2220 let v = CV::from_toml(path_def, table)?;
2221 value.merge(v, false)?;
2222 }
2223 }
2224 toml.insert(key, value.into_toml());
2225 } else {
2226 if let Some(registry) = registry {
2228 if let Some(registries) = toml.get_mut("registries") {
2229 if let Some(reg) = registries.get_mut(registry) {
2230 let rtable = reg.as_table_mut().ok_or_else(|| {
2231 format_err!("expected `[registries.{}]` to be a table", registry)
2232 })?;
2233 rtable.remove("token");
2234 rtable.remove("secret-key");
2235 rtable.remove("secret-key-subject");
2236 }
2237 }
2238 } else if let Some(registry) = toml.get_mut("registry") {
2239 let reg_table = registry
2240 .as_table_mut()
2241 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2242 reg_table.remove("token");
2243 reg_table.remove("secret-key");
2244 reg_table.remove("secret-key-subject");
2245 }
2246 }
2247
2248 let contents = toml.to_string();
2249 file.seek(SeekFrom::Start(0))?;
2250 file.write_all(contents.as_bytes())
2251 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2252 file.file().set_len(contents.len() as u64)?;
2253 set_permissions(file.file(), 0o600)
2254 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2255
2256 return Ok(());
2257
2258 #[cfg(unix)]
2259 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2260 use std::os::unix::fs::PermissionsExt;
2261
2262 let mut perms = file.metadata()?.permissions();
2263 perms.set_mode(mode);
2264 file.set_permissions(perms)?;
2265 Ok(())
2266 }
2267
2268 #[cfg(not(unix))]
2269 #[allow(unused)]
2270 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2271 Ok(())
2272 }
2273}
2274
2275struct ConfigInclude {
2281 path: PathBuf,
2284 def: Definition,
2285 optional: bool,
2287}
2288
2289impl ConfigInclude {
2290 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2291 Self {
2292 path: p.into(),
2293 def,
2294 optional: false,
2295 }
2296 }
2297
2298 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2311 let abs_path = match &self.def {
2312 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2313 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2314 }
2315 .join(&self.path);
2316
2317 if self.optional && !abs_path.exists() {
2318 tracing::info!(
2319 "skipping optional include `{}` in `{}`: file not found at `{}`",
2320 self.path.display(),
2321 self.def,
2322 abs_path.display(),
2323 );
2324 None
2325 } else {
2326 Some(abs_path)
2327 }
2328 }
2329}
2330
2331fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2332 toml.parse().map_err(Into::into)
2334}
2335
2336fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2337 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2343 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2344 })?;
2345 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2346 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2347 }
2348 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2349 non_empty(d.prefix()) || non_empty(d.suffix())
2350 }
2351 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2352 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2353 }
2354 let ok = {
2355 let mut got_to_value = false;
2356 let mut table = doc.as_table();
2357 let mut is_root = true;
2358 while table.is_dotted() || is_root {
2359 is_root = false;
2360 if table.len() != 1 {
2361 break;
2362 }
2363 let (k, n) = table.iter().next().expect("len() == 1 above");
2364 match n {
2365 Item::Table(nt) => {
2366 if table.key(k).map_or(false, non_empty_key_decor)
2367 || non_empty_decor(nt.decor())
2368 {
2369 bail!(
2370 "--config argument `{arg}` \
2371 includes non-whitespace decoration"
2372 )
2373 }
2374 table = nt;
2375 }
2376 Item::Value(v) if v.is_inline_table() => {
2377 bail!(
2378 "--config argument `{arg}` \
2379 sets a value to an inline table, which is not accepted"
2380 );
2381 }
2382 Item::Value(v) => {
2383 if table
2384 .key(k)
2385 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2386 || non_empty_decor(v.decor())
2387 {
2388 bail!(
2389 "--config argument `{arg}` \
2390 includes non-whitespace decoration"
2391 )
2392 }
2393 got_to_value = true;
2394 break;
2395 }
2396 Item::ArrayOfTables(_) => {
2397 bail!(
2398 "--config argument `{arg}` \
2399 sets a value to an array of tables, which is not accepted"
2400 );
2401 }
2402
2403 Item::None => {
2404 bail!("--config argument `{arg}` doesn't provide a value")
2405 }
2406 }
2407 }
2408 got_to_value
2409 };
2410 if !ok {
2411 bail!(
2412 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2413 );
2414 }
2415 Ok(doc)
2416}
2417
2418#[derive(Debug, Deserialize, Clone)]
2429pub struct StringList(Vec<String>);
2430
2431impl StringList {
2432 pub fn as_slice(&self) -> &[String] {
2433 &self.0
2434 }
2435}
2436
2437#[macro_export]
2438macro_rules! __shell_print {
2439 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2440 let mut shell = $config.shell();
2441 let out = shell.$which();
2442 drop(out.write_fmt(format_args!($($arg)*)));
2443 if $newline {
2444 drop(out.write_all(b"\n"));
2445 }
2446 });
2447}
2448
2449#[macro_export]
2450macro_rules! drop_println {
2451 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2452 ($config:expr, $($arg:tt)*) => (
2453 $crate::__shell_print!($config, out, true, $($arg)*)
2454 );
2455}
2456
2457#[macro_export]
2458macro_rules! drop_eprintln {
2459 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2460 ($config:expr, $($arg:tt)*) => (
2461 $crate::__shell_print!($config, err, true, $($arg)*)
2462 );
2463}
2464
2465#[macro_export]
2466macro_rules! drop_print {
2467 ($config:expr, $($arg:tt)*) => (
2468 $crate::__shell_print!($config, out, false, $($arg)*)
2469 );
2470}
2471
2472#[macro_export]
2473macro_rules! drop_eprint {
2474 ($config:expr, $($arg:tt)*) => (
2475 $crate::__shell_print!($config, err, false, $($arg)*)
2476 );
2477}
2478
2479enum Tool {
2480 Rustc,
2481 Rustdoc,
2482}
2483
2484impl Tool {
2485 fn as_str(&self) -> &str {
2486 match self {
2487 Tool::Rustc => "rustc",
2488 Tool::Rustdoc => "rustdoc",
2489 }
2490 }
2491}
2492
2493fn disables_multiplexing_for_bad_curl(
2503 curl_version: &str,
2504 http: &mut CargoHttpConfig,
2505 gctx: &GlobalContext,
2506) {
2507 use crate::util::network;
2508
2509 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2510 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2511 if bad_curl_versions
2512 .iter()
2513 .any(|v| curl_version.starts_with(v))
2514 {
2515 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2516 http.multiplexing = Some(false);
2517 }
2518 }
2519}
2520
2521#[cfg(test)]
2522mod tests {
2523 use super::CargoHttpConfig;
2524 use super::GlobalContext;
2525 use super::Shell;
2526 use super::disables_multiplexing_for_bad_curl;
2527
2528 #[test]
2529 fn disables_multiplexing() {
2530 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2531 gctx.set_search_stop_path(std::path::PathBuf::new());
2532 gctx.set_env(Default::default());
2533
2534 let mut http = CargoHttpConfig::default();
2535 http.proxy = Some("127.0.0.1:3128".into());
2536 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2537 assert_eq!(http.multiplexing, Some(false));
2538
2539 let cases = [
2540 (None, None, "7.87.0", None),
2541 (None, None, "7.88.0", None),
2542 (None, None, "7.88.1", None),
2543 (None, None, "8.0.0", None),
2544 (Some("".into()), None, "7.87.0", Some(false)),
2545 (Some("".into()), None, "7.88.0", Some(false)),
2546 (Some("".into()), None, "7.88.1", Some(false)),
2547 (Some("".into()), None, "8.0.0", None),
2548 (Some("".into()), Some(false), "7.87.0", Some(false)),
2549 (Some("".into()), Some(false), "7.88.0", Some(false)),
2550 (Some("".into()), Some(false), "7.88.1", Some(false)),
2551 (Some("".into()), Some(false), "8.0.0", Some(false)),
2552 ];
2553
2554 for (proxy, multiplexing, curl_v, result) in cases {
2555 let mut http = CargoHttpConfig {
2556 multiplexing,
2557 proxy,
2558 ..Default::default()
2559 };
2560 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2561 assert_eq!(http.multiplexing, result);
2562 }
2563 }
2564
2565 #[test]
2566 fn sync_context() {
2567 fn assert_sync<S: Sync>() {}
2568 assert_sync::<GlobalContext>();
2569 }
2570}