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,
195 FileDiscovery,
197}
198
199#[derive(Debug)]
201pub struct CredentialCacheValue {
202 pub token_value: Secret<String>,
203 pub expiration: Option<OffsetDateTime>,
204 pub operation_independent: bool,
205}
206
207#[derive(Debug)]
210pub struct GlobalContext {
211 home_path: Filesystem,
213 shell: Mutex<Shell>,
215 values: OnceLock<HashMap<String, ConfigValue>>,
217 credential_values: OnceLock<HashMap<String, ConfigValue>>,
219 cli_config: Option<Vec<String>>,
221 cwd: PathBuf,
223 search_stop_path: Option<PathBuf>,
225 cargo_exe: OnceLock<PathBuf>,
227 rustdoc: OnceLock<PathBuf>,
229 extra_verbose: bool,
231 frozen: bool,
234 locked: bool,
237 offline: bool,
240 jobserver: Option<jobserver::Client>,
242 unstable_flags: CliUnstable,
244 unstable_flags_cli: Option<Vec<String>>,
246 easy: OnceLock<Mutex<Easy>>,
248 crates_io_source_id: OnceLock<SourceId>,
250 cache_rustc_info: bool,
252 creation_time: Instant,
254 target_dir: Option<Filesystem>,
256 env: Env,
258 updated_sources: Mutex<HashSet<SourceId>>,
260 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
263 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
265 package_cache_lock: CacheLocker,
267 http_config: OnceLock<CargoHttpConfig>,
269 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
270 net_config: OnceLock<CargoNetConfig>,
271 build_config: OnceLock<CargoBuildConfig>,
272 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
273 doc_extern_map: OnceLock<RustdocExternMap>,
274 progress_config: ProgressConfig,
275 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
276 pub nightly_features_allowed: bool,
292 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
294 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
296 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
299}
300
301impl GlobalContext {
302 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
310 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
311 static INIT: Once = Once::new();
312
313 INIT.call_once(|| unsafe {
316 if let Some(client) = jobserver::Client::from_env() {
317 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
318 }
319 });
320
321 let env = Env::new();
322
323 let cache_key = "CARGO_CACHE_RUSTC_INFO";
324 let cache_rustc_info = match env.get_env_os(cache_key) {
325 Some(cache) => cache != "0",
326 _ => true,
327 };
328
329 GlobalContext {
330 home_path: Filesystem::new(homedir),
331 shell: Mutex::new(shell),
332 cwd,
333 search_stop_path: None,
334 values: Default::default(),
335 credential_values: Default::default(),
336 cli_config: None,
337 cargo_exe: Default::default(),
338 rustdoc: Default::default(),
339 extra_verbose: false,
340 frozen: false,
341 locked: false,
342 offline: false,
343 jobserver: unsafe {
344 if GLOBAL_JOBSERVER.is_null() {
345 None
346 } else {
347 Some((*GLOBAL_JOBSERVER).clone())
348 }
349 },
350 unstable_flags: CliUnstable::default(),
351 unstable_flags_cli: None,
352 easy: Default::default(),
353 crates_io_source_id: Default::default(),
354 cache_rustc_info,
355 creation_time: Instant::now(),
356 target_dir: None,
357 env,
358 updated_sources: Default::default(),
359 credential_cache: Default::default(),
360 registry_config: Default::default(),
361 package_cache_lock: CacheLocker::new(),
362 http_config: Default::default(),
363 future_incompat_config: Default::default(),
364 net_config: Default::default(),
365 build_config: Default::default(),
366 target_cfgs: Default::default(),
367 doc_extern_map: Default::default(),
368 progress_config: ProgressConfig::default(),
369 env_config: Default::default(),
370 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
371 ws_roots: Default::default(),
372 global_cache_tracker: Default::default(),
373 deferred_global_last_use: Default::default(),
374 }
375 }
376
377 pub fn default() -> CargoResult<GlobalContext> {
382 let shell = Shell::new();
383 let cwd =
384 env::current_dir().context("couldn't get the current directory of the process")?;
385 let homedir = homedir(&cwd).ok_or_else(|| {
386 anyhow!(
387 "Cargo couldn't find your home directory. \
388 This probably means that $HOME was not set."
389 )
390 })?;
391 Ok(GlobalContext::new(shell, cwd, homedir))
392 }
393
394 pub fn home(&self) -> &Filesystem {
396 &self.home_path
397 }
398
399 pub fn diagnostic_home_config(&self) -> String {
403 let home = self.home_path.as_path_unlocked();
404 let path = match self.get_file_path(home, "config", false) {
405 Ok(Some(existing_path)) => existing_path,
406 _ => home.join("config.toml"),
407 };
408 path.to_string_lossy().to_string()
409 }
410
411 pub fn git_path(&self) -> Filesystem {
413 self.home_path.join("git")
414 }
415
416 pub fn git_checkouts_path(&self) -> Filesystem {
419 self.git_path().join("checkouts")
420 }
421
422 pub fn git_db_path(&self) -> Filesystem {
425 self.git_path().join("db")
426 }
427
428 pub fn registry_base_path(&self) -> Filesystem {
430 self.home_path.join("registry")
431 }
432
433 pub fn registry_index_path(&self) -> Filesystem {
435 self.registry_base_path().join("index")
436 }
437
438 pub fn registry_cache_path(&self) -> Filesystem {
440 self.registry_base_path().join("cache")
441 }
442
443 pub fn registry_source_path(&self) -> Filesystem {
445 self.registry_base_path().join("src")
446 }
447
448 pub fn default_registry(&self) -> CargoResult<Option<String>> {
450 Ok(self
451 .get_string("registry.default")?
452 .map(|registry| registry.val))
453 }
454
455 pub fn shell(&self) -> MutexGuard<'_, Shell> {
457 self.shell.lock().unwrap()
458 }
459
460 pub fn debug_assert_shell_not_borrowed(&self) {
466 if cfg!(debug_assertions) {
467 match self.shell.try_lock() {
468 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
469 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
470 }
471 }
472 }
473
474 pub fn rustdoc(&self) -> CargoResult<&Path> {
476 self.rustdoc
477 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
478 .map(AsRef::as_ref)
479 }
480
481 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
483 let cache_location =
484 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
485 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
486 let rustc_workspace_wrapper = self.maybe_get_tool(
487 "rustc_workspace_wrapper",
488 &self.build_config()?.rustc_workspace_wrapper,
489 );
490
491 Rustc::new(
492 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
493 wrapper,
494 rustc_workspace_wrapper,
495 &self
496 .home()
497 .join("bin")
498 .join("rustc")
499 .into_path_unlocked()
500 .with_extension(env::consts::EXE_EXTENSION),
501 if self.cache_rustc_info {
502 cache_location
503 } else {
504 None
505 },
506 self,
507 )
508 }
509
510 pub fn cargo_exe(&self) -> CargoResult<&Path> {
512 self.cargo_exe
513 .try_borrow_with(|| {
514 let from_env = || -> CargoResult<PathBuf> {
515 let exe = self
520 .get_env_os(crate::CARGO_ENV)
521 .map(PathBuf::from)
522 .ok_or_else(|| anyhow!("$CARGO not set"))?;
523 Ok(exe)
524 };
525
526 fn from_current_exe() -> CargoResult<PathBuf> {
527 let exe = env::current_exe()?;
532 Ok(exe)
533 }
534
535 fn from_argv() -> CargoResult<PathBuf> {
536 let argv0 = env::args_os()
543 .map(PathBuf::from)
544 .next()
545 .ok_or_else(|| anyhow!("no argv[0]"))?;
546 paths::resolve_executable(&argv0)
547 }
548
549 fn is_cargo(path: &Path) -> bool {
552 path.file_stem() == Some(OsStr::new("cargo"))
553 }
554
555 let from_current_exe = from_current_exe();
556 if from_current_exe.as_deref().is_ok_and(is_cargo) {
557 return from_current_exe;
558 }
559
560 let from_argv = from_argv();
561 if from_argv.as_deref().is_ok_and(is_cargo) {
562 return from_argv;
563 }
564
565 let exe = from_env()
566 .or(from_current_exe)
567 .or(from_argv)
568 .context("couldn't get the path to cargo executable")?;
569 Ok(exe)
570 })
571 .map(AsRef::as_ref)
572 }
573
574 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
576 self.updated_sources.lock().unwrap()
577 }
578
579 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
581 self.credential_cache.lock().unwrap()
582 }
583
584 pub(crate) fn registry_config(
586 &self,
587 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
588 self.registry_config.lock().unwrap()
589 }
590
591 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
597 self.values.try_borrow_with(|| self.load_values())
598 }
599
600 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
607 let _ = self.values()?;
608 Ok(self.values.get_mut().expect("already loaded config values"))
609 }
610
611 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
613 if self.values.get().is_some() {
614 bail!("config values already found")
615 }
616 match self.values.set(values.into()) {
617 Ok(()) => Ok(()),
618 Err(_) => bail!("could not fill values"),
619 }
620 }
621
622 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
625 let path = path.into();
626 debug_assert!(self.cwd.starts_with(&path));
627 self.search_stop_path = Some(path);
628 }
629
630 pub fn reload_cwd(&mut self) -> CargoResult<()> {
634 let cwd =
635 env::current_dir().context("couldn't get the current directory of the process")?;
636 let homedir = homedir(&cwd).ok_or_else(|| {
637 anyhow!(
638 "Cargo couldn't find your home directory. \
639 This probably means that $HOME was not set."
640 )
641 })?;
642
643 self.cwd = cwd;
644 self.home_path = Filesystem::new(homedir);
645 self.reload_rooted_at(self.cwd.clone())?;
646 Ok(())
647 }
648
649 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
652 let values = self.load_values_from(path.as_ref())?;
653 self.values.replace(values);
654 self.merge_cli_args()?;
655 self.load_unstable_flags_from_config()?;
656 Ok(())
657 }
658
659 pub fn cwd(&self) -> &Path {
661 &self.cwd
662 }
663
664 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
670 if let Some(dir) = &self.target_dir {
671 Ok(Some(dir.clone()))
672 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
673 if dir.is_empty() {
675 bail!(
676 "the target directory is set to an empty string in the \
677 `CARGO_TARGET_DIR` environment variable"
678 )
679 }
680
681 Ok(Some(Filesystem::new(self.cwd.join(dir))))
682 } else if let Some(val) = &self.build_config()?.target_dir {
683 let path = val.resolve_path(self);
684
685 if val.raw_value().is_empty() {
687 bail!(
688 "the target directory is set to an empty string in {}",
689 val.value().definition
690 )
691 }
692
693 Ok(Some(Filesystem::new(path)))
694 } else {
695 Ok(None)
696 }
697 }
698
699 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
703 let Some(val) = &self.build_config()?.build_dir else {
704 return Ok(None);
705 };
706 self.custom_build_dir(val, workspace_manifest_path)
707 .map(Some)
708 }
709
710 pub fn custom_build_dir(
714 &self,
715 val: &ConfigRelativePath,
716 workspace_manifest_path: &Path,
717 ) -> CargoResult<Filesystem> {
718 let replacements = [
719 (
720 "{workspace-root}",
721 workspace_manifest_path
722 .parent()
723 .unwrap()
724 .to_str()
725 .context("workspace root was not valid utf-8")?
726 .to_string(),
727 ),
728 (
729 "{cargo-cache-home}",
730 self.home()
731 .as_path_unlocked()
732 .to_str()
733 .context("cargo home was not valid utf-8")?
734 .to_string(),
735 ),
736 ("{workspace-path-hash}", {
737 let real_path = std::fs::canonicalize(workspace_manifest_path)
738 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
739 let hash = crate::util::hex::short_hash(&real_path);
740 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
741 }),
742 ];
743
744 let template_variables = replacements
745 .iter()
746 .map(|(key, _)| key[1..key.len() - 1].to_string())
747 .collect_vec();
748
749 let path = val
750 .resolve_templated_path(self, replacements)
751 .map_err(|e| match e {
752 path::ResolveTemplateError::UnexpectedVariable {
753 variable,
754 raw_template,
755 } => {
756 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
757 if suggestion == "" {
758 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
759 suggestion = format!("\n\nhelp: available template variables are {variables}");
760 }
761 anyhow!(
762 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
763 )
764 },
765 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
766 let (btype, literal) = match bracket_type {
767 path::BracketType::Opening => ("opening", "{"),
768 path::BracketType::Closing => ("closing", "}"),
769 };
770
771 anyhow!(
772 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
773 )
774 }
775 })?;
776
777 if val.raw_value().is_empty() {
779 bail!(
780 "the build directory is set to an empty string in {}",
781 val.value().definition
782 )
783 }
784
785 Ok(Filesystem::new(path))
786 }
787
788 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
793 if let Some(vals) = self.credential_values.get() {
794 let val = self.get_cv_helper(key, vals)?;
795 if val.is_some() {
796 return Ok(val);
797 }
798 }
799 self.get_cv_helper(key, &*self.values()?)
800 }
801
802 fn get_cv_helper(
803 &self,
804 key: &ConfigKey,
805 vals: &HashMap<String, ConfigValue>,
806 ) -> CargoResult<Option<ConfigValue>> {
807 tracing::trace!("get cv {:?}", key);
808 if key.is_root() {
809 return Ok(Some(CV::Table(
812 vals.clone(),
813 Definition::Path(PathBuf::new()),
814 )));
815 }
816 let mut parts = key.parts().enumerate();
817 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
818 return Ok(None);
819 };
820 for (i, part) in parts {
821 match val {
822 CV::Table(map, _) => {
823 val = match map.get(part) {
824 Some(val) => val,
825 None => return Ok(None),
826 }
827 }
828 CV::Integer(_, def)
829 | CV::String(_, def)
830 | CV::List(_, def)
831 | CV::Boolean(_, def) => {
832 let mut key_so_far = ConfigKey::new();
833 for part in key.parts().take(i) {
834 key_so_far.push(part);
835 }
836 bail!(
837 "expected table for configuration key `{}`, \
838 but found {} in {}",
839 key_so_far,
840 val.desc(),
841 def
842 )
843 }
844 }
845 }
846 Ok(Some(val.clone()))
847 }
848
849 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
851 let cv = self.get_cv(key)?;
854 if key.is_root() {
855 return Ok(cv);
857 }
858 let env = self.env.get_str(key.as_env_key());
859 let env_def = Definition::Environment(key.as_env_key().to_string());
860 let use_env = match (&cv, env) {
861 (Some(CV::List(..)), Some(_)) => true,
863 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
864 (None, Some(_)) => true,
865 _ => false,
866 };
867
868 if !use_env {
869 return Ok(cv);
870 }
871
872 let env = env.unwrap();
876 if env == "true" {
877 Ok(Some(CV::Boolean(true, env_def)))
878 } else if env == "false" {
879 Ok(Some(CV::Boolean(false, env_def)))
880 } else if let Ok(i) = env.parse::<i64>() {
881 Ok(Some(CV::Integer(i, env_def)))
882 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
883 match cv {
884 Some(CV::List(mut cv_list, cv_def)) => {
885 self.get_env_list(key, &mut cv_list)?;
887 Ok(Some(CV::List(cv_list, cv_def)))
888 }
889 Some(cv) => {
890 bail!(
894 "unable to merge array env for config `{}`\n\
895 file: {:?}\n\
896 env: {}",
897 key,
898 cv,
899 env
900 );
901 }
902 None => {
903 let mut cv_list = Vec::new();
904 self.get_env_list(key, &mut cv_list)?;
905 Ok(Some(CV::List(cv_list, env_def)))
906 }
907 }
908 } else {
909 match cv {
911 Some(CV::List(mut cv_list, cv_def)) => {
912 self.get_env_list(key, &mut cv_list)?;
914 Ok(Some(CV::List(cv_list, cv_def)))
915 }
916 _ => {
917 Ok(Some(CV::String(env.to_string(), env_def)))
922 }
923 }
924 }
925 }
926
927 pub fn set_env(&mut self, env: HashMap<String, String>) {
929 self.env = Env::from_map(env);
930 }
931
932 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
935 self.env.iter_str()
936 }
937
938 fn env_keys(&self) -> impl Iterator<Item = &str> {
940 self.env.keys_str()
941 }
942
943 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
944 where
945 T: FromStr,
946 <T as FromStr>::Err: fmt::Display,
947 {
948 match self.env.get_str(key.as_env_key()) {
949 Some(value) => {
950 let definition = Definition::Environment(key.as_env_key().to_string());
951 Ok(Some(Value {
952 val: value
953 .parse()
954 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
955 definition,
956 }))
957 }
958 None => {
959 self.check_environment_key_case_mismatch(key);
960 Ok(None)
961 }
962 }
963 }
964
965 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
970 self.env.get_env(key)
971 }
972
973 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
978 self.env.get_env_os(key)
979 }
980
981 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
985 if self.env.contains_key(key.as_env_key()) {
986 return Ok(true);
987 }
988 if env_prefix_ok {
989 let env_prefix = format!("{}_", key.as_env_key());
990 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
991 return Ok(true);
992 }
993 }
994 if self.get_cv(key)?.is_some() {
995 return Ok(true);
996 }
997 self.check_environment_key_case_mismatch(key);
998
999 Ok(false)
1000 }
1001
1002 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1003 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1004 let _ = self.shell().warn(format!(
1005 "environment variables are expected to use uppercase letters and underscores, \
1006 the variable `{}` will be ignored and have no effect",
1007 env_key
1008 ));
1009 }
1010 }
1011
1012 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
1016 self.get::<OptValue<String>>(key)
1017 }
1018
1019 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1020 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1021 if is_path {
1022 definition.root(self.cwd()).join(value)
1023 } else {
1024 PathBuf::from(value)
1026 }
1027 }
1028
1029 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1032 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1033 self.check_environment_key_case_mismatch(key);
1034 return Ok(());
1035 };
1036
1037 let env_def = Definition::Environment(key.as_env_key().to_string());
1038
1039 if is_nonmergeable_list(&key) {
1040 assert!(
1041 output
1042 .windows(2)
1043 .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1044 "non-mergeable list must have only one definition: {output:?}",
1045 );
1046
1047 if output
1050 .first()
1051 .map(|o| o.definition() > &env_def)
1052 .unwrap_or_default()
1053 {
1054 return Ok(());
1055 } else {
1056 output.clear();
1057 }
1058 }
1059
1060 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1061 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1063 ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1064 })?;
1065 let values = toml_v.as_array().expect("env var was not array");
1066 for value in values {
1067 let s = value.as_str().ok_or_else(|| {
1070 ConfigError::new(
1071 format!("expected string, found {}", value.type_str()),
1072 env_def.clone(),
1073 )
1074 })?;
1075 output.push(CV::String(s.to_string(), env_def.clone()))
1076 }
1077 } else {
1078 output.extend(
1079 env_val
1080 .split_whitespace()
1081 .map(|s| CV::String(s.to_string(), env_def.clone())),
1082 );
1083 }
1084 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1085 Ok(())
1086 }
1087
1088 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1092 match self.get_cv(key)? {
1093 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1094 Some(val) => self.expected("table", key, &val),
1095 None => Ok(None),
1096 }
1097 }
1098
1099 get_value_typed! {get_integer, i64, Integer, "an integer"}
1100 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1101 get_value_typed! {get_string_priv, String, String, "a string"}
1102
1103 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1105 val.expected(ty, &key.to_string())
1106 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1107 }
1108
1109 pub fn configure(
1115 &mut self,
1116 verbose: u32,
1117 quiet: bool,
1118 color: Option<&str>,
1119 frozen: bool,
1120 locked: bool,
1121 offline: bool,
1122 target_dir: &Option<PathBuf>,
1123 unstable_flags: &[String],
1124 cli_config: &[String],
1125 ) -> CargoResult<()> {
1126 for warning in self
1127 .unstable_flags
1128 .parse(unstable_flags, self.nightly_features_allowed)?
1129 {
1130 self.shell().warn(warning)?;
1131 }
1132 if !unstable_flags.is_empty() {
1133 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1136 }
1137 if !cli_config.is_empty() {
1138 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1139 self.merge_cli_args()?;
1140 }
1141
1142 self.load_unstable_flags_from_config()?;
1146 if self.unstable_flags.config_include {
1147 self.reload_rooted_at(self.cwd.clone())?;
1154 }
1155
1156 let term = self.get::<TermConfig>("term").unwrap_or_default();
1160
1161 let extra_verbose = verbose >= 2;
1163 let verbose = verbose != 0;
1164 let verbosity = match (verbose, quiet) {
1165 (true, true) => bail!("cannot set both --verbose and --quiet"),
1166 (true, false) => Verbosity::Verbose,
1167 (false, true) => Verbosity::Quiet,
1168 (false, false) => match (term.verbose, term.quiet) {
1169 (Some(true), Some(true)) => {
1170 bail!("cannot set both `term.verbose` and `term.quiet`")
1171 }
1172 (Some(true), _) => Verbosity::Verbose,
1173 (_, Some(true)) => Verbosity::Quiet,
1174 _ => Verbosity::Normal,
1175 },
1176 };
1177 self.shell().set_verbosity(verbosity);
1178 self.extra_verbose = extra_verbose;
1179
1180 let color = color.or_else(|| term.color.as_deref());
1181 self.shell().set_color_choice(color)?;
1182 if let Some(hyperlinks) = term.hyperlinks {
1183 self.shell().set_hyperlinks(hyperlinks)?;
1184 }
1185 if let Some(unicode) = term.unicode {
1186 self.shell().set_unicode(unicode)?;
1187 }
1188
1189 self.progress_config = term.progress.unwrap_or_default();
1190
1191 self.frozen = frozen;
1192 self.locked = locked;
1193 self.offline = offline
1194 || self
1195 .net_config()
1196 .ok()
1197 .and_then(|n| n.offline)
1198 .unwrap_or(false);
1199 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1200 self.target_dir = cli_target_dir;
1201
1202 self.shell()
1203 .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1204
1205 Ok(())
1206 }
1207
1208 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1209 if self.nightly_features_allowed {
1212 self.unstable_flags = self
1213 .get::<Option<CliUnstable>>("unstable")?
1214 .unwrap_or_default();
1215 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1216 self.unstable_flags.parse(unstable_flags_cli, true)?;
1221 }
1222 }
1223
1224 Ok(())
1225 }
1226
1227 pub fn cli_unstable(&self) -> &CliUnstable {
1228 &self.unstable_flags
1229 }
1230
1231 pub fn extra_verbose(&self) -> bool {
1232 self.extra_verbose
1233 }
1234
1235 pub fn network_allowed(&self) -> bool {
1236 !self.offline_flag().is_some()
1237 }
1238
1239 pub fn offline_flag(&self) -> Option<&'static str> {
1240 if self.frozen {
1241 Some("--frozen")
1242 } else if self.offline {
1243 Some("--offline")
1244 } else {
1245 None
1246 }
1247 }
1248
1249 pub fn set_locked(&mut self, locked: bool) {
1250 self.locked = locked;
1251 }
1252
1253 pub fn lock_update_allowed(&self) -> bool {
1254 !self.locked_flag().is_some()
1255 }
1256
1257 pub fn locked_flag(&self) -> Option<&'static str> {
1258 if self.frozen {
1259 Some("--frozen")
1260 } else if self.locked {
1261 Some("--locked")
1262 } else {
1263 None
1264 }
1265 }
1266
1267 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1269 self.load_values_from(&self.cwd)
1270 }
1271
1272 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1276 let mut result = Vec::new();
1277 let mut seen = HashSet::new();
1278 let home = self.home_path.clone().into_path_unlocked();
1279 self.walk_tree(&self.cwd, &home, |path| {
1280 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1281 if self.cli_unstable().config_include {
1282 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1283 }
1284 result.push(cv);
1285 Ok(())
1286 })
1287 .context("could not load Cargo configuration")?;
1288 Ok(result)
1289 }
1290
1291 fn load_unmerged_include(
1295 &self,
1296 cv: &mut CV,
1297 seen: &mut HashSet<PathBuf>,
1298 output: &mut Vec<CV>,
1299 ) -> CargoResult<()> {
1300 let includes = self.include_paths(cv, false)?;
1301 for include in includes {
1302 let Some(abs_path) = include.resolve_path(self) else {
1303 continue;
1304 };
1305
1306 let mut cv = self
1307 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1308 .with_context(|| {
1309 format!(
1310 "failed to load config include `{}` from `{}`",
1311 include.path.display(),
1312 include.def
1313 )
1314 })?;
1315 self.load_unmerged_include(&mut cv, seen, output)?;
1316 output.push(cv);
1317 }
1318 Ok(())
1319 }
1320
1321 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1323 let mut cfg = CV::Table(HashMap::new(), Definition::BuiltIn);
1326 let home = self.home_path.clone().into_path_unlocked();
1327
1328 self.walk_tree(path, &home, |path| {
1329 let value = self.load_file(path)?;
1330 cfg.merge(value, false).with_context(|| {
1331 format!("failed to merge configuration at `{}`", path.display())
1332 })?;
1333 Ok(())
1334 })
1335 .context("could not load Cargo configuration")?;
1336
1337 match cfg {
1338 CV::Table(map, _) => Ok(map),
1339 _ => unreachable!(),
1340 }
1341 }
1342
1343 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1347 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1348 }
1349
1350 fn _load_file(
1360 &self,
1361 path: &Path,
1362 seen: &mut HashSet<PathBuf>,
1363 includes: bool,
1364 why_load: WhyLoad,
1365 ) -> CargoResult<ConfigValue> {
1366 if !seen.insert(path.to_path_buf()) {
1367 bail!(
1368 "config `include` cycle detected with path `{}`",
1369 path.display()
1370 );
1371 }
1372 tracing::debug!(?path, ?why_load, includes, "load config from file");
1373
1374 let contents = fs::read_to_string(path)
1375 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1376 let toml = parse_document(&contents, path, self).with_context(|| {
1377 format!("could not parse TOML configuration in `{}`", path.display())
1378 })?;
1379 let def = match why_load {
1380 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1381 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1382 };
1383 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1384 format!(
1385 "failed to load TOML configuration from `{}`",
1386 path.display()
1387 )
1388 })?;
1389 if includes {
1390 self.load_includes(value, seen, why_load)
1391 } else {
1392 Ok(value)
1393 }
1394 }
1395
1396 fn load_includes(
1403 &self,
1404 mut value: CV,
1405 seen: &mut HashSet<PathBuf>,
1406 why_load: WhyLoad,
1407 ) -> CargoResult<CV> {
1408 let includes = self.include_paths(&mut value, true)?;
1410 if !self.cli_unstable().config_include {
1412 return Ok(value);
1413 }
1414 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1416 for include in includes {
1417 let Some(abs_path) = include.resolve_path(self) else {
1418 continue;
1419 };
1420
1421 self._load_file(&abs_path, seen, true, why_load)
1422 .and_then(|include| root.merge(include, true))
1423 .with_context(|| {
1424 format!(
1425 "failed to load config include `{}` from `{}`",
1426 include.path.display(),
1427 include.def
1428 )
1429 })?;
1430 }
1431 root.merge(value, true)?;
1432 Ok(root)
1433 }
1434
1435 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1437 let CV::Table(table, _def) = cv else {
1438 unreachable!()
1439 };
1440 let include = if remove {
1441 table.remove("include").map(Cow::Owned)
1442 } else {
1443 table.get("include").map(Cow::Borrowed)
1444 };
1445 let includes = match include.map(|c| c.into_owned()) {
1446 Some(CV::List(list, _def)) => list
1447 .into_iter()
1448 .enumerate()
1449 .map(|(idx, cv)| match cv {
1450 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1451 CV::Table(mut table, def) => {
1452 let s = match table.remove("path") {
1454 Some(CV::String(s, _)) => s,
1455 Some(other) => bail!(
1456 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1457 other.desc()
1458 ),
1459 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1460 };
1461
1462 let optional = match table.remove("optional") {
1464 Some(CV::Boolean(b, _)) => b,
1465 Some(other) => bail!(
1466 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1467 other.desc()
1468 ),
1469 None => false,
1470 };
1471
1472 let mut include = ConfigInclude::new(s, def);
1473 include.optional = optional;
1474 Ok(include)
1475 }
1476 other => bail!(
1477 "expected a string or table, but found {} at `include[{idx}]` in {}",
1478 other.desc(),
1479 other.definition(),
1480 ),
1481 })
1482 .collect::<CargoResult<Vec<_>>>()?,
1483 Some(other) => bail!(
1484 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1485 other.desc(),
1486 other.definition()
1487 ),
1488 None => {
1489 return Ok(Vec::new());
1490 }
1491 };
1492
1493 for include in &includes {
1494 if include.path.extension() != Some(OsStr::new("toml")) {
1495 bail!(
1496 "expected a config include path ending with `.toml`, \
1497 but found `{}` from `{}`",
1498 include.path.display(),
1499 include.def,
1500 )
1501 }
1502
1503 if let Some(path) = include.path.to_str() {
1504 if is_glob_pattern(path) {
1506 bail!(
1507 "expected a config include path without glob patterns, \
1508 but found `{}` from `{}`",
1509 include.path.display(),
1510 include.def,
1511 )
1512 }
1513 if path.contains(&['{', '}']) {
1514 bail!(
1515 "expected a config include path without template braces, \
1516 but found `{}` from `{}`",
1517 include.path.display(),
1518 include.def,
1519 )
1520 }
1521 }
1522 }
1523
1524 Ok(includes)
1525 }
1526
1527 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1529 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1530 let Some(cli_args) = &self.cli_config else {
1531 return Ok(loaded_args);
1532 };
1533 let mut seen = HashSet::new();
1534 for arg in cli_args {
1535 let arg_as_path = self.cwd.join(arg);
1536 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1537 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1539 .with_context(|| {
1540 format!("failed to load config from `{}`", arg_as_path.display())
1541 })?
1542 } else {
1543 let doc = toml_dotted_keys(arg)?;
1544 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1545 .with_context(|| {
1546 format!("failed to parse value from --config argument `{arg}`")
1547 })?;
1548
1549 if doc
1550 .get("registry")
1551 .and_then(|v| v.as_table())
1552 .and_then(|t| t.get("token"))
1553 .is_some()
1554 {
1555 bail!("registry.token cannot be set through --config for security reasons");
1556 } else if let Some((k, _)) = doc
1557 .get("registries")
1558 .and_then(|v| v.as_table())
1559 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1560 {
1561 bail!(
1562 "registries.{}.token cannot be set through --config for security reasons",
1563 k
1564 );
1565 }
1566
1567 if doc
1568 .get("registry")
1569 .and_then(|v| v.as_table())
1570 .and_then(|t| t.get("secret-key"))
1571 .is_some()
1572 {
1573 bail!(
1574 "registry.secret-key cannot be set through --config for security reasons"
1575 );
1576 } else if let Some((k, _)) = doc
1577 .get("registries")
1578 .and_then(|v| v.as_table())
1579 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1580 {
1581 bail!(
1582 "registries.{}.secret-key cannot be set through --config for security reasons",
1583 k
1584 );
1585 }
1586
1587 CV::from_toml(Definition::Cli(None), doc)
1588 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1589 };
1590 let tmp_table = self
1591 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1592 .context("failed to load --config include".to_string())?;
1593 loaded_args
1594 .merge(tmp_table, true)
1595 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1596 }
1597 Ok(loaded_args)
1598 }
1599
1600 fn merge_cli_args(&mut self) -> CargoResult<()> {
1602 let cv_from_cli = self.cli_args_as_table()?;
1603 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1604
1605 let root_cv = mem::take(self.values_mut()?);
1606 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1609 root_cv.merge(cv_from_cli, true)?;
1610
1611 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1613
1614 Ok(())
1615 }
1616
1617 fn get_file_path(
1623 &self,
1624 dir: &Path,
1625 filename_without_extension: &str,
1626 warn: bool,
1627 ) -> CargoResult<Option<PathBuf>> {
1628 let possible = dir.join(filename_without_extension);
1629 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1630
1631 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1632 if warn {
1633 if let Ok(possible_with_extension_handle) =
1634 same_file::Handle::from_path(&possible_with_extension)
1635 {
1636 if possible_handle != possible_with_extension_handle {
1642 self.shell().warn(format!(
1643 "both `{}` and `{}` exist. Using `{}`",
1644 possible.display(),
1645 possible_with_extension.display(),
1646 possible.display()
1647 ))?;
1648 }
1649 } else {
1650 self.shell().print_report(&[
1651 Level::WARNING.secondary_title(
1652 format!(
1653 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1654 possible.display(),
1655 )).element(Level::HELP.message(
1656 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1657
1658 ], false)?;
1659 }
1660 }
1661
1662 Ok(Some(possible))
1663 } else if possible_with_extension.exists() {
1664 Ok(Some(possible_with_extension))
1665 } else {
1666 Ok(None)
1667 }
1668 }
1669
1670 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1671 where
1672 F: FnMut(&Path) -> CargoResult<()>,
1673 {
1674 let mut seen_dir = HashSet::new();
1675
1676 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1677 let config_root = current.join(".cargo");
1678 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1679 walk(&path)?;
1680 }
1681 seen_dir.insert(config_root);
1682 }
1683
1684 if !seen_dir.contains(home) {
1688 if let Some(path) = self.get_file_path(home, "config", true)? {
1689 walk(&path)?;
1690 }
1691 }
1692
1693 Ok(())
1694 }
1695
1696 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1698 RegistryName::new(registry)?;
1699 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1700 self.resolve_registry_index(&index).with_context(|| {
1701 format!(
1702 "invalid index URL for registry `{}` defined in {}",
1703 registry, index.definition
1704 )
1705 })
1706 } else {
1707 bail!(
1708 "registry index was not found in any configuration: `{}`",
1709 registry
1710 );
1711 }
1712 }
1713
1714 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1716 if self.get_string("registry.index")?.is_some() {
1717 bail!(
1718 "the `registry.index` config value is no longer supported\n\
1719 Use `[source]` replacement to alter the default index for crates.io."
1720 );
1721 }
1722 Ok(())
1723 }
1724
1725 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1726 let base = index
1728 .definition
1729 .root(self.cwd())
1730 .join("truncated-by-url_with_base");
1731 let _parsed = index.val.into_url()?;
1733 let url = index.val.into_url_with_base(Some(&*base))?;
1734 if url.password().is_some() {
1735 bail!("registry URLs may not contain passwords");
1736 }
1737 Ok(url)
1738 }
1739
1740 pub fn load_credentials(&self) -> CargoResult<()> {
1748 if self.credential_values.filled() {
1749 return Ok(());
1750 }
1751
1752 let home_path = self.home_path.clone().into_path_unlocked();
1753 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1754 return Ok(());
1755 };
1756
1757 let mut value = self.load_file(&credentials)?;
1758 {
1760 let (value_map, def) = value.table_mut("<root>")?;
1761
1762 if let Some(token) = value_map.remove("token") {
1763 value_map.entry("registry".into()).or_insert_with(|| {
1764 let map = HashMap::from([("token".into(), token)]);
1765 CV::Table(map, def.clone())
1766 });
1767 }
1768 }
1769
1770 let mut credential_values = HashMap::new();
1771 if let CV::Table(map, _) = value {
1772 let base_map = self.values()?;
1773 for (k, v) in map {
1774 let entry = match base_map.get(&k) {
1775 Some(base_entry) => {
1776 let mut entry = base_entry.clone();
1777 entry.merge(v, true)?;
1778 entry
1779 }
1780 None => v,
1781 };
1782 credential_values.insert(k, entry);
1783 }
1784 }
1785 self.credential_values
1786 .set(credential_values)
1787 .expect("was not filled at beginning of the function");
1788 Ok(())
1789 }
1790
1791 fn maybe_get_tool(
1794 &self,
1795 tool: &str,
1796 from_config: &Option<ConfigRelativePath>,
1797 ) -> Option<PathBuf> {
1798 let var = tool.to_uppercase();
1799
1800 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1801 Some(tool_path) => {
1802 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1803 let path = if maybe_relative {
1804 self.cwd.join(tool_path)
1805 } else {
1806 PathBuf::from(tool_path)
1807 };
1808 Some(path)
1809 }
1810
1811 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1812 }
1813 }
1814
1815 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1826 let tool_str = tool.as_str();
1827 self.maybe_get_tool(tool_str, from_config)
1828 .or_else(|| {
1829 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1843 if toolchain.to_str()?.contains(&['/', '\\']) {
1846 return None;
1847 }
1848 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1851 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1852 let tool_meta = tool_resolved.metadata().ok()?;
1853 let rustup_meta = rustup_resolved.metadata().ok()?;
1854 if tool_meta.len() != rustup_meta.len() {
1859 return None;
1860 }
1861 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1863 let toolchain_exe = home::rustup_home()
1864 .ok()?
1865 .join("toolchains")
1866 .join(&toolchain)
1867 .join("bin")
1868 .join(&tool_exe);
1869 toolchain_exe.exists().then_some(toolchain_exe)
1870 })
1871 .unwrap_or_else(|| PathBuf::from(tool_str))
1872 }
1873
1874 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1876 let key = ConfigKey::from_str("paths");
1877 match self.get_cv(&key)? {
1879 Some(CV::List(val, definition)) => {
1880 let val = val
1881 .into_iter()
1882 .map(|cv| match cv {
1883 CV::String(s, def) => Ok((s, def)),
1884 other => self.expected("string", &key, &other),
1885 })
1886 .collect::<CargoResult<Vec<_>>>()?;
1887 Ok(Some(Value { val, definition }))
1888 }
1889 Some(val) => self.expected("list", &key, &val),
1890 None => Ok(None),
1891 }
1892 }
1893
1894 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1895 self.jobserver.as_ref()
1896 }
1897
1898 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1899 let http = self
1900 .easy
1901 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1902 {
1903 let mut http = http.lock().unwrap();
1904 http.reset();
1905 let timeout = configure_http_handle(self, &mut http)?;
1906 timeout.configure(&mut http)?;
1907 }
1908 Ok(http)
1909 }
1910
1911 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1912 self.http_config.try_borrow_with(|| {
1913 let mut http = self.get::<CargoHttpConfig>("http")?;
1914 let curl_v = curl::Version::get();
1915 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1916 Ok(http)
1917 })
1918 }
1919
1920 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1921 self.future_incompat_config
1922 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1923 }
1924
1925 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1926 self.net_config
1927 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1928 }
1929
1930 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1931 self.build_config
1932 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1933 }
1934
1935 pub fn progress_config(&self) -> &ProgressConfig {
1936 &self.progress_config
1937 }
1938
1939 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1942 let env_config = self.env_config.try_borrow_with(|| {
1943 CargoResult::Ok(Arc::new({
1944 let env_config = self.get::<EnvConfig>("env")?;
1945 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1961 if env_config.contains_key(*disallowed) {
1962 bail!(
1963 "setting the `{disallowed}` environment variable is not supported \
1964 in the `[env]` configuration table"
1965 );
1966 }
1967 }
1968 env_config
1969 .into_iter()
1970 .filter_map(|(k, v)| {
1971 if v.is_force() || self.get_env_os(&k).is_none() {
1972 Some((k, v.resolve(self.cwd()).to_os_string()))
1973 } else {
1974 None
1975 }
1976 })
1977 .collect()
1978 }))
1979 })?;
1980
1981 Ok(env_config)
1982 }
1983
1984 pub fn validate_term_config(&self) -> CargoResult<()> {
1990 drop(self.get::<TermConfig>("term")?);
1991 Ok(())
1992 }
1993
1994 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1998 self.target_cfgs
1999 .try_borrow_with(|| target::load_target_cfgs(self))
2000 }
2001
2002 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2003 self.doc_extern_map
2007 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2008 }
2009
2010 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2012 target::get_target_applies_to_host(self)
2013 }
2014
2015 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2017 target::load_host_triple(self, target)
2018 }
2019
2020 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2022 target::load_target_triple(self, target)
2023 }
2024
2025 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2030 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2031 self.check_registry_index_not_set()?;
2032 let url = CRATES_IO_INDEX.into_url().unwrap();
2033 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2034 })?;
2035 Ok(*source_id)
2036 }
2037
2038 pub fn creation_time(&self) -> Instant {
2039 self.creation_time
2040 }
2041
2042 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2057 let d = Deserializer {
2058 gctx: self,
2059 key: ConfigKey::from_str(key),
2060 env_prefix_ok: true,
2061 };
2062 T::deserialize(d).map_err(|e| e.into())
2063 }
2064
2065 #[track_caller]
2071 #[tracing::instrument(skip_all)]
2072 pub fn assert_package_cache_locked<'a>(
2073 &self,
2074 mode: CacheLockMode,
2075 f: &'a Filesystem,
2076 ) -> &'a Path {
2077 let ret = f.as_path_unlocked();
2078 assert!(
2079 self.package_cache_lock.is_locked(mode),
2080 "package cache lock is not currently held, Cargo forgot to call \
2081 `acquire_package_cache_lock` before we got to this stack frame",
2082 );
2083 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2084 ret
2085 }
2086
2087 #[tracing::instrument(skip_all)]
2093 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2094 self.package_cache_lock.lock(self, mode)
2095 }
2096
2097 #[tracing::instrument(skip_all)]
2103 pub fn try_acquire_package_cache_lock(
2104 &self,
2105 mode: CacheLockMode,
2106 ) -> CargoResult<Option<CacheLock<'_>>> {
2107 self.package_cache_lock.try_lock(self, mode)
2108 }
2109
2110 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2115 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2116 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2117 })?;
2118 Ok(tracker.lock().unwrap())
2119 }
2120
2121 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2123 let deferred = self
2124 .deferred_global_last_use
2125 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2126 Ok(deferred.lock().unwrap())
2127 }
2128
2129 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2131 if self.unstable_flags.warnings {
2132 Ok(self.build_config()?.warnings.unwrap_or_default())
2133 } else {
2134 Ok(WarningHandling::default())
2135 }
2136 }
2137
2138 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2139 self.ws_roots.lock().unwrap()
2140 }
2141}
2142
2143pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2144 ::home::cargo_home_with_cwd(cwd).ok()
2145}
2146
2147pub fn save_credentials(
2148 gctx: &GlobalContext,
2149 token: Option<RegistryCredentialConfig>,
2150 registry: &SourceId,
2151) -> CargoResult<()> {
2152 let registry = if registry.is_crates_io() {
2153 None
2154 } else {
2155 let name = registry
2156 .alt_registry_key()
2157 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2158 Some(name)
2159 };
2160
2161 let home_path = gctx.home_path.clone().into_path_unlocked();
2165 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2166 Some(path) => match path.file_name() {
2167 Some(filename) => Path::new(filename).to_owned(),
2168 None => Path::new("credentials.toml").to_owned(),
2169 },
2170 None => Path::new("credentials.toml").to_owned(),
2171 };
2172
2173 let mut file = {
2174 gctx.home_path.create_dir()?;
2175 gctx.home_path
2176 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2177 };
2178
2179 let mut contents = String::new();
2180 file.read_to_string(&mut contents).with_context(|| {
2181 format!(
2182 "failed to read configuration file `{}`",
2183 file.path().display()
2184 )
2185 })?;
2186
2187 let mut toml = parse_document(&contents, file.path(), gctx)?;
2188
2189 if let Some(token) = toml.remove("token") {
2191 let map = HashMap::from([("token".to_string(), token)]);
2192 toml.insert("registry".into(), map.into());
2193 }
2194
2195 if let Some(token) = token {
2196 let path_def = Definition::Path(file.path().to_path_buf());
2199 let (key, mut value) = match token {
2200 RegistryCredentialConfig::Token(token) => {
2201 let key = "token".to_string();
2204 let value = ConfigValue::String(token.expose(), path_def.clone());
2205 let map = HashMap::from([(key, value)]);
2206 let table = CV::Table(map, path_def.clone());
2207
2208 if let Some(registry) = registry {
2209 let map = HashMap::from([(registry.to_string(), table)]);
2210 ("registries".into(), CV::Table(map, path_def.clone()))
2211 } else {
2212 ("registry".into(), table)
2213 }
2214 }
2215 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2216 let key = "secret-key".to_string();
2219 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2220 let mut map = HashMap::from([(key, value)]);
2221 if let Some(key_subject) = key_subject {
2222 let key = "secret-key-subject".to_string();
2223 let value = ConfigValue::String(key_subject, path_def.clone());
2224 map.insert(key, value);
2225 }
2226 let table = CV::Table(map, path_def.clone());
2227
2228 if let Some(registry) = registry {
2229 let map = HashMap::from([(registry.to_string(), table)]);
2230 ("registries".into(), CV::Table(map, path_def.clone()))
2231 } else {
2232 ("registry".into(), table)
2233 }
2234 }
2235 _ => unreachable!(),
2236 };
2237
2238 if registry.is_some() {
2239 if let Some(table) = toml.remove("registries") {
2240 let v = CV::from_toml(path_def, table)?;
2241 value.merge(v, false)?;
2242 }
2243 }
2244 toml.insert(key, value.into_toml());
2245 } else {
2246 if let Some(registry) = registry {
2248 if let Some(registries) = toml.get_mut("registries") {
2249 if let Some(reg) = registries.get_mut(registry) {
2250 let rtable = reg.as_table_mut().ok_or_else(|| {
2251 format_err!("expected `[registries.{}]` to be a table", registry)
2252 })?;
2253 rtable.remove("token");
2254 rtable.remove("secret-key");
2255 rtable.remove("secret-key-subject");
2256 }
2257 }
2258 } else if let Some(registry) = toml.get_mut("registry") {
2259 let reg_table = registry
2260 .as_table_mut()
2261 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2262 reg_table.remove("token");
2263 reg_table.remove("secret-key");
2264 reg_table.remove("secret-key-subject");
2265 }
2266 }
2267
2268 let contents = toml.to_string();
2269 file.seek(SeekFrom::Start(0))?;
2270 file.write_all(contents.as_bytes())
2271 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2272 file.file().set_len(contents.len() as u64)?;
2273 set_permissions(file.file(), 0o600)
2274 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2275
2276 return Ok(());
2277
2278 #[cfg(unix)]
2279 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2280 use std::os::unix::fs::PermissionsExt;
2281
2282 let mut perms = file.metadata()?.permissions();
2283 perms.set_mode(mode);
2284 file.set_permissions(perms)?;
2285 Ok(())
2286 }
2287
2288 #[cfg(not(unix))]
2289 #[allow(unused)]
2290 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2291 Ok(())
2292 }
2293}
2294
2295struct ConfigInclude {
2301 path: PathBuf,
2304 def: Definition,
2305 optional: bool,
2307}
2308
2309impl ConfigInclude {
2310 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2311 Self {
2312 path: p.into(),
2313 def,
2314 optional: false,
2315 }
2316 }
2317
2318 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2331 let abs_path = match &self.def {
2332 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2333 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2334 }
2335 .join(&self.path);
2336
2337 if self.optional && !abs_path.exists() {
2338 tracing::info!(
2339 "skipping optional include `{}` in `{}`: file not found at `{}`",
2340 self.path.display(),
2341 self.def,
2342 abs_path.display(),
2343 );
2344 None
2345 } else {
2346 Some(abs_path)
2347 }
2348 }
2349}
2350
2351fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2352 toml.parse().map_err(Into::into)
2354}
2355
2356fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2357 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2363 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2364 })?;
2365 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2366 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2367 }
2368 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2369 non_empty(d.prefix()) || non_empty(d.suffix())
2370 }
2371 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2372 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2373 }
2374 let ok = {
2375 let mut got_to_value = false;
2376 let mut table = doc.as_table();
2377 let mut is_root = true;
2378 while table.is_dotted() || is_root {
2379 is_root = false;
2380 if table.len() != 1 {
2381 break;
2382 }
2383 let (k, n) = table.iter().next().expect("len() == 1 above");
2384 match n {
2385 Item::Table(nt) => {
2386 if table.key(k).map_or(false, non_empty_key_decor)
2387 || non_empty_decor(nt.decor())
2388 {
2389 bail!(
2390 "--config argument `{arg}` \
2391 includes non-whitespace decoration"
2392 )
2393 }
2394 table = nt;
2395 }
2396 Item::Value(v) if v.is_inline_table() => {
2397 bail!(
2398 "--config argument `{arg}` \
2399 sets a value to an inline table, which is not accepted"
2400 );
2401 }
2402 Item::Value(v) => {
2403 if table
2404 .key(k)
2405 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2406 || non_empty_decor(v.decor())
2407 {
2408 bail!(
2409 "--config argument `{arg}` \
2410 includes non-whitespace decoration"
2411 )
2412 }
2413 got_to_value = true;
2414 break;
2415 }
2416 Item::ArrayOfTables(_) => {
2417 bail!(
2418 "--config argument `{arg}` \
2419 sets a value to an array of tables, which is not accepted"
2420 );
2421 }
2422
2423 Item::None => {
2424 bail!("--config argument `{arg}` doesn't provide a value")
2425 }
2426 }
2427 }
2428 got_to_value
2429 };
2430 if !ok {
2431 bail!(
2432 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2433 );
2434 }
2435 Ok(doc)
2436}
2437
2438#[derive(Debug, Deserialize, Clone)]
2449pub struct StringList(Vec<String>);
2450
2451impl StringList {
2452 pub fn as_slice(&self) -> &[String] {
2453 &self.0
2454 }
2455}
2456
2457#[macro_export]
2458macro_rules! __shell_print {
2459 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2460 let mut shell = $config.shell();
2461 let out = shell.$which();
2462 drop(out.write_fmt(format_args!($($arg)*)));
2463 if $newline {
2464 drop(out.write_all(b"\n"));
2465 }
2466 });
2467}
2468
2469#[macro_export]
2470macro_rules! drop_println {
2471 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2472 ($config:expr, $($arg:tt)*) => (
2473 $crate::__shell_print!($config, out, true, $($arg)*)
2474 );
2475}
2476
2477#[macro_export]
2478macro_rules! drop_eprintln {
2479 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2480 ($config:expr, $($arg:tt)*) => (
2481 $crate::__shell_print!($config, err, true, $($arg)*)
2482 );
2483}
2484
2485#[macro_export]
2486macro_rules! drop_print {
2487 ($config:expr, $($arg:tt)*) => (
2488 $crate::__shell_print!($config, out, false, $($arg)*)
2489 );
2490}
2491
2492#[macro_export]
2493macro_rules! drop_eprint {
2494 ($config:expr, $($arg:tt)*) => (
2495 $crate::__shell_print!($config, err, false, $($arg)*)
2496 );
2497}
2498
2499enum Tool {
2500 Rustc,
2501 Rustdoc,
2502}
2503
2504impl Tool {
2505 fn as_str(&self) -> &str {
2506 match self {
2507 Tool::Rustc => "rustc",
2508 Tool::Rustdoc => "rustdoc",
2509 }
2510 }
2511}
2512
2513fn disables_multiplexing_for_bad_curl(
2523 curl_version: &str,
2524 http: &mut CargoHttpConfig,
2525 gctx: &GlobalContext,
2526) {
2527 use crate::util::network;
2528
2529 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2530 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2531 if bad_curl_versions
2532 .iter()
2533 .any(|v| curl_version.starts_with(v))
2534 {
2535 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2536 http.multiplexing = Some(false);
2537 }
2538 }
2539}
2540
2541#[cfg(test)]
2542mod tests {
2543 use super::CargoHttpConfig;
2544 use super::GlobalContext;
2545 use super::Shell;
2546 use super::disables_multiplexing_for_bad_curl;
2547
2548 #[test]
2549 fn disables_multiplexing() {
2550 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2551 gctx.set_search_stop_path(std::path::PathBuf::new());
2552 gctx.set_env(Default::default());
2553
2554 let mut http = CargoHttpConfig::default();
2555 http.proxy = Some("127.0.0.1:3128".into());
2556 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2557 assert_eq!(http.multiplexing, Some(false));
2558
2559 let cases = [
2560 (None, None, "7.87.0", None),
2561 (None, None, "7.88.0", None),
2562 (None, None, "7.88.1", None),
2563 (None, None, "8.0.0", None),
2564 (Some("".into()), None, "7.87.0", Some(false)),
2565 (Some("".into()), None, "7.88.0", Some(false)),
2566 (Some("".into()), None, "7.88.1", Some(false)),
2567 (Some("".into()), None, "8.0.0", None),
2568 (Some("".into()), Some(false), "7.87.0", Some(false)),
2569 (Some("".into()), Some(false), "7.88.0", Some(false)),
2570 (Some("".into()), Some(false), "7.88.1", Some(false)),
2571 (Some("".into()), Some(false), "8.0.0", Some(false)),
2572 ];
2573
2574 for (proxy, multiplexing, curl_v, result) in cases {
2575 let mut http = CargoHttpConfig {
2576 multiplexing,
2577 proxy,
2578 ..Default::default()
2579 };
2580 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2581 assert_eq!(http.multiplexing, result);
2582 }
2583 }
2584
2585 #[test]
2586 fn sync_context() {
2587 fn assert_sync<S: Sync>() {}
2588 assert_sync::<GlobalContext>();
2589 }
2590}