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