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