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::String(s, def)) => vec![ConfigInclude::new(s, def)],
1447 Some(CV::List(list, _def)) => list
1448 .into_iter()
1449 .enumerate()
1450 .map(|(idx, cv)| match cv {
1451 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1452 CV::Table(mut table, def) => {
1453 let s = match table.remove("path") {
1455 Some(CV::String(s, _)) => s,
1456 Some(other) => bail!(
1457 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1458 other.desc()
1459 ),
1460 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1461 };
1462
1463 let optional = match table.remove("optional") {
1465 Some(CV::Boolean(b, _)) => b,
1466 Some(other) => bail!(
1467 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1468 other.desc()
1469 ),
1470 None => false,
1471 };
1472
1473 let mut include = ConfigInclude::new(s, def);
1474 include.optional = optional;
1475 Ok(include)
1476 }
1477 other => bail!(
1478 "expected a string or table, but found {} at `include[{idx}]` in {}",
1479 other.desc(),
1480 other.definition(),
1481 ),
1482 })
1483 .collect::<CargoResult<Vec<_>>>()?,
1484 Some(other) => bail!(
1485 "expected a string or list of strings, but found {} at `include` in `{}",
1486 other.desc(),
1487 other.definition()
1488 ),
1489 None => {
1490 return Ok(Vec::new());
1491 }
1492 };
1493
1494 for include in &includes {
1495 if include.path.extension() != Some(OsStr::new("toml")) {
1496 bail!(
1497 "expected a config include path ending with `.toml`, \
1498 but found `{}` from `{}`",
1499 include.path.display(),
1500 include.def,
1501 )
1502 }
1503
1504 if let Some(path) = include.path.to_str() {
1505 if is_glob_pattern(path) {
1507 bail!(
1508 "expected a config include path without glob patterns, \
1509 but found `{}` from `{}`",
1510 include.path.display(),
1511 include.def,
1512 )
1513 }
1514 if path.contains(&['{', '}']) {
1515 bail!(
1516 "expected a config include path without template braces, \
1517 but found `{}` from `{}`",
1518 include.path.display(),
1519 include.def,
1520 )
1521 }
1522 }
1523 }
1524
1525 Ok(includes)
1526 }
1527
1528 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1530 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1531 let Some(cli_args) = &self.cli_config else {
1532 return Ok(loaded_args);
1533 };
1534 let mut seen = HashSet::new();
1535 for arg in cli_args {
1536 let arg_as_path = self.cwd.join(arg);
1537 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1538 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1540 .with_context(|| {
1541 format!("failed to load config from `{}`", arg_as_path.display())
1542 })?
1543 } else {
1544 let doc = toml_dotted_keys(arg)?;
1545 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1546 .with_context(|| {
1547 format!("failed to parse value from --config argument `{arg}`")
1548 })?;
1549
1550 if doc
1551 .get("registry")
1552 .and_then(|v| v.as_table())
1553 .and_then(|t| t.get("token"))
1554 .is_some()
1555 {
1556 bail!("registry.token cannot be set through --config for security reasons");
1557 } else if let Some((k, _)) = doc
1558 .get("registries")
1559 .and_then(|v| v.as_table())
1560 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1561 {
1562 bail!(
1563 "registries.{}.token cannot be set through --config for security reasons",
1564 k
1565 );
1566 }
1567
1568 if doc
1569 .get("registry")
1570 .and_then(|v| v.as_table())
1571 .and_then(|t| t.get("secret-key"))
1572 .is_some()
1573 {
1574 bail!(
1575 "registry.secret-key cannot be set through --config for security reasons"
1576 );
1577 } else if let Some((k, _)) = doc
1578 .get("registries")
1579 .and_then(|v| v.as_table())
1580 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1581 {
1582 bail!(
1583 "registries.{}.secret-key cannot be set through --config for security reasons",
1584 k
1585 );
1586 }
1587
1588 CV::from_toml(Definition::Cli(None), doc)
1589 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1590 };
1591 let tmp_table = self
1592 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1593 .context("failed to load --config include".to_string())?;
1594 loaded_args
1595 .merge(tmp_table, true)
1596 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1597 }
1598 Ok(loaded_args)
1599 }
1600
1601 fn merge_cli_args(&mut self) -> CargoResult<()> {
1603 let cv_from_cli = self.cli_args_as_table()?;
1604 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1605
1606 let root_cv = mem::take(self.values_mut()?);
1607 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1610 root_cv.merge(cv_from_cli, true)?;
1611
1612 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1614
1615 Ok(())
1616 }
1617
1618 fn get_file_path(
1624 &self,
1625 dir: &Path,
1626 filename_without_extension: &str,
1627 warn: bool,
1628 ) -> CargoResult<Option<PathBuf>> {
1629 let possible = dir.join(filename_without_extension);
1630 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1631
1632 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1633 if warn {
1634 if let Ok(possible_with_extension_handle) =
1635 same_file::Handle::from_path(&possible_with_extension)
1636 {
1637 if possible_handle != possible_with_extension_handle {
1643 self.shell().warn(format!(
1644 "both `{}` and `{}` exist. Using `{}`",
1645 possible.display(),
1646 possible_with_extension.display(),
1647 possible.display()
1648 ))?;
1649 }
1650 } else {
1651 self.shell().print_report(&[
1652 Level::WARNING.secondary_title(
1653 format!(
1654 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1655 possible.display(),
1656 )).element(Level::HELP.message(
1657 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1658
1659 ], false)?;
1660 }
1661 }
1662
1663 Ok(Some(possible))
1664 } else if possible_with_extension.exists() {
1665 Ok(Some(possible_with_extension))
1666 } else {
1667 Ok(None)
1668 }
1669 }
1670
1671 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1672 where
1673 F: FnMut(&Path) -> CargoResult<()>,
1674 {
1675 let mut seen_dir = HashSet::new();
1676
1677 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1678 let config_root = current.join(".cargo");
1679 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1680 walk(&path)?;
1681 }
1682 seen_dir.insert(config_root);
1683 }
1684
1685 if !seen_dir.contains(home) {
1689 if let Some(path) = self.get_file_path(home, "config", true)? {
1690 walk(&path)?;
1691 }
1692 }
1693
1694 Ok(())
1695 }
1696
1697 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1699 RegistryName::new(registry)?;
1700 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1701 self.resolve_registry_index(&index).with_context(|| {
1702 format!(
1703 "invalid index URL for registry `{}` defined in {}",
1704 registry, index.definition
1705 )
1706 })
1707 } else {
1708 bail!(
1709 "registry index was not found in any configuration: `{}`",
1710 registry
1711 );
1712 }
1713 }
1714
1715 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1717 if self.get_string("registry.index")?.is_some() {
1718 bail!(
1719 "the `registry.index` config value is no longer supported\n\
1720 Use `[source]` replacement to alter the default index for crates.io."
1721 );
1722 }
1723 Ok(())
1724 }
1725
1726 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1727 let base = index
1729 .definition
1730 .root(self.cwd())
1731 .join("truncated-by-url_with_base");
1732 let _parsed = index.val.into_url()?;
1734 let url = index.val.into_url_with_base(Some(&*base))?;
1735 if url.password().is_some() {
1736 bail!("registry URLs may not contain passwords");
1737 }
1738 Ok(url)
1739 }
1740
1741 pub fn load_credentials(&self) -> CargoResult<()> {
1749 if self.credential_values.filled() {
1750 return Ok(());
1751 }
1752
1753 let home_path = self.home_path.clone().into_path_unlocked();
1754 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1755 return Ok(());
1756 };
1757
1758 let mut value = self.load_file(&credentials)?;
1759 {
1761 let (value_map, def) = value.table_mut("<root>")?;
1762
1763 if let Some(token) = value_map.remove("token") {
1764 value_map.entry("registry".into()).or_insert_with(|| {
1765 let map = HashMap::from([("token".into(), token)]);
1766 CV::Table(map, def.clone())
1767 });
1768 }
1769 }
1770
1771 let mut credential_values = HashMap::new();
1772 if let CV::Table(map, _) = value {
1773 let base_map = self.values()?;
1774 for (k, v) in map {
1775 let entry = match base_map.get(&k) {
1776 Some(base_entry) => {
1777 let mut entry = base_entry.clone();
1778 entry.merge(v, true)?;
1779 entry
1780 }
1781 None => v,
1782 };
1783 credential_values.insert(k, entry);
1784 }
1785 }
1786 self.credential_values
1787 .set(credential_values)
1788 .expect("was not filled at beginning of the function");
1789 Ok(())
1790 }
1791
1792 fn maybe_get_tool(
1795 &self,
1796 tool: &str,
1797 from_config: &Option<ConfigRelativePath>,
1798 ) -> Option<PathBuf> {
1799 let var = tool.to_uppercase();
1800
1801 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1802 Some(tool_path) => {
1803 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1804 let path = if maybe_relative {
1805 self.cwd.join(tool_path)
1806 } else {
1807 PathBuf::from(tool_path)
1808 };
1809 Some(path)
1810 }
1811
1812 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1813 }
1814 }
1815
1816 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1827 let tool_str = tool.as_str();
1828 self.maybe_get_tool(tool_str, from_config)
1829 .or_else(|| {
1830 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1844 if toolchain.to_str()?.contains(&['/', '\\']) {
1847 return None;
1848 }
1849 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1852 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1853 let tool_meta = tool_resolved.metadata().ok()?;
1854 let rustup_meta = rustup_resolved.metadata().ok()?;
1855 if tool_meta.len() != rustup_meta.len() {
1860 return None;
1861 }
1862 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1864 let toolchain_exe = home::rustup_home()
1865 .ok()?
1866 .join("toolchains")
1867 .join(&toolchain)
1868 .join("bin")
1869 .join(&tool_exe);
1870 toolchain_exe.exists().then_some(toolchain_exe)
1871 })
1872 .unwrap_or_else(|| PathBuf::from(tool_str))
1873 }
1874
1875 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1877 let key = ConfigKey::from_str("paths");
1878 match self.get_cv(&key)? {
1880 Some(CV::List(val, definition)) => {
1881 let val = val
1882 .into_iter()
1883 .map(|cv| match cv {
1884 CV::String(s, def) => Ok((s, def)),
1885 other => self.expected("string", &key, &other),
1886 })
1887 .collect::<CargoResult<Vec<_>>>()?;
1888 Ok(Some(Value { val, definition }))
1889 }
1890 Some(val) => self.expected("list", &key, &val),
1891 None => Ok(None),
1892 }
1893 }
1894
1895 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1896 self.jobserver.as_ref()
1897 }
1898
1899 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1900 let http = self
1901 .easy
1902 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1903 {
1904 let mut http = http.lock().unwrap();
1905 http.reset();
1906 let timeout = configure_http_handle(self, &mut http)?;
1907 timeout.configure(&mut http)?;
1908 }
1909 Ok(http)
1910 }
1911
1912 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1913 self.http_config.try_borrow_with(|| {
1914 let mut http = self.get::<CargoHttpConfig>("http")?;
1915 let curl_v = curl::Version::get();
1916 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1917 Ok(http)
1918 })
1919 }
1920
1921 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1922 self.future_incompat_config
1923 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1924 }
1925
1926 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1927 self.net_config
1928 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1929 }
1930
1931 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1932 self.build_config
1933 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1934 }
1935
1936 pub fn progress_config(&self) -> &ProgressConfig {
1937 &self.progress_config
1938 }
1939
1940 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1943 let env_config = self.env_config.try_borrow_with(|| {
1944 CargoResult::Ok(Arc::new({
1945 let env_config = self.get::<EnvConfig>("env")?;
1946 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1962 if env_config.contains_key(*disallowed) {
1963 bail!(
1964 "setting the `{disallowed}` environment variable is not supported \
1965 in the `[env]` configuration table"
1966 );
1967 }
1968 }
1969 env_config
1970 .into_iter()
1971 .filter_map(|(k, v)| {
1972 if v.is_force() || self.get_env_os(&k).is_none() {
1973 Some((k, v.resolve(self.cwd()).to_os_string()))
1974 } else {
1975 None
1976 }
1977 })
1978 .collect()
1979 }))
1980 })?;
1981
1982 Ok(env_config)
1983 }
1984
1985 pub fn validate_term_config(&self) -> CargoResult<()> {
1991 drop(self.get::<TermConfig>("term")?);
1992 Ok(())
1993 }
1994
1995 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1999 self.target_cfgs
2000 .try_borrow_with(|| target::load_target_cfgs(self))
2001 }
2002
2003 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2004 self.doc_extern_map
2008 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2009 }
2010
2011 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2013 target::get_target_applies_to_host(self)
2014 }
2015
2016 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2018 target::load_host_triple(self, target)
2019 }
2020
2021 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2023 target::load_target_triple(self, target)
2024 }
2025
2026 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2031 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2032 self.check_registry_index_not_set()?;
2033 let url = CRATES_IO_INDEX.into_url().unwrap();
2034 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2035 })?;
2036 Ok(*source_id)
2037 }
2038
2039 pub fn creation_time(&self) -> Instant {
2040 self.creation_time
2041 }
2042
2043 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2058 let d = Deserializer {
2059 gctx: self,
2060 key: ConfigKey::from_str(key),
2061 env_prefix_ok: true,
2062 };
2063 T::deserialize(d).map_err(|e| e.into())
2064 }
2065
2066 #[track_caller]
2072 #[tracing::instrument(skip_all)]
2073 pub fn assert_package_cache_locked<'a>(
2074 &self,
2075 mode: CacheLockMode,
2076 f: &'a Filesystem,
2077 ) -> &'a Path {
2078 let ret = f.as_path_unlocked();
2079 assert!(
2080 self.package_cache_lock.is_locked(mode),
2081 "package cache lock is not currently held, Cargo forgot to call \
2082 `acquire_package_cache_lock` before we got to this stack frame",
2083 );
2084 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2085 ret
2086 }
2087
2088 #[tracing::instrument(skip_all)]
2094 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2095 self.package_cache_lock.lock(self, mode)
2096 }
2097
2098 #[tracing::instrument(skip_all)]
2104 pub fn try_acquire_package_cache_lock(
2105 &self,
2106 mode: CacheLockMode,
2107 ) -> CargoResult<Option<CacheLock<'_>>> {
2108 self.package_cache_lock.try_lock(self, mode)
2109 }
2110
2111 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2116 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2117 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2118 })?;
2119 Ok(tracker.lock().unwrap())
2120 }
2121
2122 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2124 let deferred = self
2125 .deferred_global_last_use
2126 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2127 Ok(deferred.lock().unwrap())
2128 }
2129
2130 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2132 if self.unstable_flags.warnings {
2133 Ok(self.build_config()?.warnings.unwrap_or_default())
2134 } else {
2135 Ok(WarningHandling::default())
2136 }
2137 }
2138
2139 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2140 self.ws_roots.lock().unwrap()
2141 }
2142}
2143
2144pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2145 ::home::cargo_home_with_cwd(cwd).ok()
2146}
2147
2148pub fn save_credentials(
2149 gctx: &GlobalContext,
2150 token: Option<RegistryCredentialConfig>,
2151 registry: &SourceId,
2152) -> CargoResult<()> {
2153 let registry = if registry.is_crates_io() {
2154 None
2155 } else {
2156 let name = registry
2157 .alt_registry_key()
2158 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2159 Some(name)
2160 };
2161
2162 let home_path = gctx.home_path.clone().into_path_unlocked();
2166 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2167 Some(path) => match path.file_name() {
2168 Some(filename) => Path::new(filename).to_owned(),
2169 None => Path::new("credentials.toml").to_owned(),
2170 },
2171 None => Path::new("credentials.toml").to_owned(),
2172 };
2173
2174 let mut file = {
2175 gctx.home_path.create_dir()?;
2176 gctx.home_path
2177 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2178 };
2179
2180 let mut contents = String::new();
2181 file.read_to_string(&mut contents).with_context(|| {
2182 format!(
2183 "failed to read configuration file `{}`",
2184 file.path().display()
2185 )
2186 })?;
2187
2188 let mut toml = parse_document(&contents, file.path(), gctx)?;
2189
2190 if let Some(token) = toml.remove("token") {
2192 let map = HashMap::from([("token".to_string(), token)]);
2193 toml.insert("registry".into(), map.into());
2194 }
2195
2196 if let Some(token) = token {
2197 let path_def = Definition::Path(file.path().to_path_buf());
2200 let (key, mut value) = match token {
2201 RegistryCredentialConfig::Token(token) => {
2202 let key = "token".to_string();
2205 let value = ConfigValue::String(token.expose(), path_def.clone());
2206 let map = HashMap::from([(key, value)]);
2207 let table = CV::Table(map, path_def.clone());
2208
2209 if let Some(registry) = registry {
2210 let map = HashMap::from([(registry.to_string(), table)]);
2211 ("registries".into(), CV::Table(map, path_def.clone()))
2212 } else {
2213 ("registry".into(), table)
2214 }
2215 }
2216 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2217 let key = "secret-key".to_string();
2220 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2221 let mut map = HashMap::from([(key, value)]);
2222 if let Some(key_subject) = key_subject {
2223 let key = "secret-key-subject".to_string();
2224 let value = ConfigValue::String(key_subject, path_def.clone());
2225 map.insert(key, value);
2226 }
2227 let table = CV::Table(map, path_def.clone());
2228
2229 if let Some(registry) = registry {
2230 let map = HashMap::from([(registry.to_string(), table)]);
2231 ("registries".into(), CV::Table(map, path_def.clone()))
2232 } else {
2233 ("registry".into(), table)
2234 }
2235 }
2236 _ => unreachable!(),
2237 };
2238
2239 if registry.is_some() {
2240 if let Some(table) = toml.remove("registries") {
2241 let v = CV::from_toml(path_def, table)?;
2242 value.merge(v, false)?;
2243 }
2244 }
2245 toml.insert(key, value.into_toml());
2246 } else {
2247 if let Some(registry) = registry {
2249 if let Some(registries) = toml.get_mut("registries") {
2250 if let Some(reg) = registries.get_mut(registry) {
2251 let rtable = reg.as_table_mut().ok_or_else(|| {
2252 format_err!("expected `[registries.{}]` to be a table", registry)
2253 })?;
2254 rtable.remove("token");
2255 rtable.remove("secret-key");
2256 rtable.remove("secret-key-subject");
2257 }
2258 }
2259 } else if let Some(registry) = toml.get_mut("registry") {
2260 let reg_table = registry
2261 .as_table_mut()
2262 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2263 reg_table.remove("token");
2264 reg_table.remove("secret-key");
2265 reg_table.remove("secret-key-subject");
2266 }
2267 }
2268
2269 let contents = toml.to_string();
2270 file.seek(SeekFrom::Start(0))?;
2271 file.write_all(contents.as_bytes())
2272 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2273 file.file().set_len(contents.len() as u64)?;
2274 set_permissions(file.file(), 0o600)
2275 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2276
2277 return Ok(());
2278
2279 #[cfg(unix)]
2280 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2281 use std::os::unix::fs::PermissionsExt;
2282
2283 let mut perms = file.metadata()?.permissions();
2284 perms.set_mode(mode);
2285 file.set_permissions(perms)?;
2286 Ok(())
2287 }
2288
2289 #[cfg(not(unix))]
2290 #[allow(unused)]
2291 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2292 Ok(())
2293 }
2294}
2295
2296struct ConfigInclude {
2302 path: PathBuf,
2305 def: Definition,
2306 optional: bool,
2308}
2309
2310impl ConfigInclude {
2311 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2312 Self {
2313 path: p.into(),
2314 def,
2315 optional: false,
2316 }
2317 }
2318
2319 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2332 let abs_path = match &self.def {
2333 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2334 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2335 }
2336 .join(&self.path);
2337
2338 if self.optional && !abs_path.exists() {
2339 tracing::info!(
2340 "skipping optional include `{}` in `{}`: file not found at `{}`",
2341 self.path.display(),
2342 self.def,
2343 abs_path.display(),
2344 );
2345 None
2346 } else {
2347 Some(abs_path)
2348 }
2349 }
2350}
2351
2352fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2353 toml.parse().map_err(Into::into)
2355}
2356
2357fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2358 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2364 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2365 })?;
2366 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2367 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2368 }
2369 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2370 non_empty(d.prefix()) || non_empty(d.suffix())
2371 }
2372 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2373 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2374 }
2375 let ok = {
2376 let mut got_to_value = false;
2377 let mut table = doc.as_table();
2378 let mut is_root = true;
2379 while table.is_dotted() || is_root {
2380 is_root = false;
2381 if table.len() != 1 {
2382 break;
2383 }
2384 let (k, n) = table.iter().next().expect("len() == 1 above");
2385 match n {
2386 Item::Table(nt) => {
2387 if table.key(k).map_or(false, non_empty_key_decor)
2388 || non_empty_decor(nt.decor())
2389 {
2390 bail!(
2391 "--config argument `{arg}` \
2392 includes non-whitespace decoration"
2393 )
2394 }
2395 table = nt;
2396 }
2397 Item::Value(v) if v.is_inline_table() => {
2398 bail!(
2399 "--config argument `{arg}` \
2400 sets a value to an inline table, which is not accepted"
2401 );
2402 }
2403 Item::Value(v) => {
2404 if table
2405 .key(k)
2406 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2407 || non_empty_decor(v.decor())
2408 {
2409 bail!(
2410 "--config argument `{arg}` \
2411 includes non-whitespace decoration"
2412 )
2413 }
2414 got_to_value = true;
2415 break;
2416 }
2417 Item::ArrayOfTables(_) => {
2418 bail!(
2419 "--config argument `{arg}` \
2420 sets a value to an array of tables, which is not accepted"
2421 );
2422 }
2423
2424 Item::None => {
2425 bail!("--config argument `{arg}` doesn't provide a value")
2426 }
2427 }
2428 }
2429 got_to_value
2430 };
2431 if !ok {
2432 bail!(
2433 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2434 );
2435 }
2436 Ok(doc)
2437}
2438
2439#[derive(Debug, Deserialize, Clone)]
2450pub struct StringList(Vec<String>);
2451
2452impl StringList {
2453 pub fn as_slice(&self) -> &[String] {
2454 &self.0
2455 }
2456}
2457
2458#[macro_export]
2459macro_rules! __shell_print {
2460 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2461 let mut shell = $config.shell();
2462 let out = shell.$which();
2463 drop(out.write_fmt(format_args!($($arg)*)));
2464 if $newline {
2465 drop(out.write_all(b"\n"));
2466 }
2467 });
2468}
2469
2470#[macro_export]
2471macro_rules! drop_println {
2472 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2473 ($config:expr, $($arg:tt)*) => (
2474 $crate::__shell_print!($config, out, true, $($arg)*)
2475 );
2476}
2477
2478#[macro_export]
2479macro_rules! drop_eprintln {
2480 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2481 ($config:expr, $($arg:tt)*) => (
2482 $crate::__shell_print!($config, err, true, $($arg)*)
2483 );
2484}
2485
2486#[macro_export]
2487macro_rules! drop_print {
2488 ($config:expr, $($arg:tt)*) => (
2489 $crate::__shell_print!($config, out, false, $($arg)*)
2490 );
2491}
2492
2493#[macro_export]
2494macro_rules! drop_eprint {
2495 ($config:expr, $($arg:tt)*) => (
2496 $crate::__shell_print!($config, err, false, $($arg)*)
2497 );
2498}
2499
2500enum Tool {
2501 Rustc,
2502 Rustdoc,
2503}
2504
2505impl Tool {
2506 fn as_str(&self) -> &str {
2507 match self {
2508 Tool::Rustc => "rustc",
2509 Tool::Rustdoc => "rustdoc",
2510 }
2511 }
2512}
2513
2514fn disables_multiplexing_for_bad_curl(
2524 curl_version: &str,
2525 http: &mut CargoHttpConfig,
2526 gctx: &GlobalContext,
2527) {
2528 use crate::util::network;
2529
2530 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2531 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2532 if bad_curl_versions
2533 .iter()
2534 .any(|v| curl_version.starts_with(v))
2535 {
2536 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2537 http.multiplexing = Some(false);
2538 }
2539 }
2540}
2541
2542#[cfg(test)]
2543mod tests {
2544 use super::CargoHttpConfig;
2545 use super::GlobalContext;
2546 use super::Shell;
2547 use super::disables_multiplexing_for_bad_curl;
2548
2549 #[test]
2550 fn disables_multiplexing() {
2551 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2552 gctx.set_search_stop_path(std::path::PathBuf::new());
2553 gctx.set_env(Default::default());
2554
2555 let mut http = CargoHttpConfig::default();
2556 http.proxy = Some("127.0.0.1:3128".into());
2557 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2558 assert_eq!(http.multiplexing, Some(false));
2559
2560 let cases = [
2561 (None, None, "7.87.0", None),
2562 (None, None, "7.88.0", None),
2563 (None, None, "7.88.1", None),
2564 (None, None, "8.0.0", None),
2565 (Some("".into()), None, "7.87.0", Some(false)),
2566 (Some("".into()), None, "7.88.0", Some(false)),
2567 (Some("".into()), None, "7.88.1", Some(false)),
2568 (Some("".into()), None, "8.0.0", None),
2569 (Some("".into()), Some(false), "7.87.0", Some(false)),
2570 (Some("".into()), Some(false), "7.88.0", Some(false)),
2571 (Some("".into()), Some(false), "7.88.1", Some(false)),
2572 (Some("".into()), Some(false), "8.0.0", Some(false)),
2573 ];
2574
2575 for (proxy, multiplexing, curl_v, result) in cases {
2576 let mut http = CargoHttpConfig {
2577 multiplexing,
2578 proxy,
2579 ..Default::default()
2580 };
2581 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2582 assert_eq!(http.multiplexing, result);
2583 }
2584 }
2585
2586 #[test]
2587 fn sync_context() {
2588 fn assert_sync<S: Sync>() {}
2589 assert_sync::<GlobalContext>();
2590 }
2591}