1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
53use std::borrow::Cow;
54use std::collections::hash_map::Entry::{Occupied, Vacant};
55use std::collections::{HashMap, HashSet};
56use std::env;
57use std::ffi::{OsStr, OsString};
58use std::fmt;
59use std::fs::{self, File};
60use std::io::SeekFrom;
61use std::io::prelude::*;
62use std::mem;
63use std::path::{Path, PathBuf};
64use std::str::FromStr;
65use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
66use std::time::Instant;
67
68use self::ConfigValue as CV;
69use crate::core::compiler::rustdoc::RustdocExternMap;
70use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
71use crate::core::shell::Verbosity;
72use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
73use crate::ops::RegistryCredentialConfig;
74use crate::sources::CRATES_IO_INDEX;
75use crate::sources::CRATES_IO_REGISTRY;
76use crate::util::OnceExt as _;
77use crate::util::errors::CargoResult;
78use crate::util::network::http::configure_http_handle;
79use crate::util::network::http::http_handle;
80use crate::util::{CanonicalUrl, closest_msg, internal};
81use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
82use anyhow::{Context as _, anyhow, bail, format_err};
83use cargo_credential::Secret;
84use cargo_util::paths;
85use cargo_util_schemas::manifest::RegistryName;
86use curl::easy::Easy;
87use itertools::Itertools;
88use serde::Deserialize;
89use serde::de::IntoDeserializer as _;
90use serde_untagged::UntaggedEnumVisitor;
91use time::OffsetDateTime;
92use toml_edit::Item;
93use url::Url;
94
95mod de;
96use de::Deserializer;
97
98mod value;
99pub use value::{Definition, OptValue, Value};
100
101mod key;
102pub use key::ConfigKey;
103
104mod path;
105pub use path::{ConfigRelativePath, PathAndArgs};
106
107mod target;
108pub use target::{TargetCfgConfig, TargetConfig};
109
110mod environment;
111use environment::Env;
112
113use super::auth::RegistryConfig;
114
115macro_rules! get_value_typed {
117 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
118 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
120 let cv = self.get_cv(key)?;
121 let env = self.get_config_env::<$ty>(key)?;
122 match (cv, env) {
123 (Some(CV::$variant(val, definition)), Some(env)) => {
124 if definition.is_higher_priority(&env.definition) {
125 Ok(Some(Value { val, definition }))
126 } else {
127 Ok(Some(env))
128 }
129 }
130 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
131 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
132 (None, Some(env)) => Ok(Some(env)),
133 (None, None) => Ok(None),
134 }
135 }
136 };
137}
138
139#[derive(Clone, Copy, Debug)]
141enum WhyLoad {
142 Cli,
149 FileDiscovery,
151}
152
153#[derive(Debug)]
155pub struct CredentialCacheValue {
156 pub token_value: Secret<String>,
157 pub expiration: Option<OffsetDateTime>,
158 pub operation_independent: bool,
159}
160
161#[derive(Debug)]
164pub struct GlobalContext {
165 home_path: Filesystem,
167 shell: Mutex<Shell>,
169 values: OnceLock<HashMap<String, ConfigValue>>,
171 credential_values: OnceLock<HashMap<String, ConfigValue>>,
173 cli_config: Option<Vec<String>>,
175 cwd: PathBuf,
177 search_stop_path: Option<PathBuf>,
179 cargo_exe: OnceLock<PathBuf>,
181 rustdoc: OnceLock<PathBuf>,
183 extra_verbose: bool,
185 frozen: bool,
188 locked: bool,
191 offline: bool,
194 jobserver: Option<jobserver::Client>,
196 unstable_flags: CliUnstable,
198 unstable_flags_cli: Option<Vec<String>>,
200 easy: OnceLock<Mutex<Easy>>,
202 crates_io_source_id: OnceLock<SourceId>,
204 cache_rustc_info: bool,
206 creation_time: Instant,
208 target_dir: Option<Filesystem>,
210 env: Env,
212 updated_sources: Mutex<HashSet<SourceId>>,
214 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
217 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
219 package_cache_lock: CacheLocker,
221 http_config: OnceLock<CargoHttpConfig>,
223 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
224 net_config: OnceLock<CargoNetConfig>,
225 build_config: OnceLock<CargoBuildConfig>,
226 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
227 doc_extern_map: OnceLock<RustdocExternMap>,
228 progress_config: ProgressConfig,
229 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
230 pub nightly_features_allowed: bool,
246 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
248 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
250 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
253}
254
255impl GlobalContext {
256 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
264 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
265 static INIT: Once = Once::new();
266
267 INIT.call_once(|| unsafe {
270 if let Some(client) = jobserver::Client::from_env() {
271 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
272 }
273 });
274
275 let env = Env::new();
276
277 let cache_key = "CARGO_CACHE_RUSTC_INFO";
278 let cache_rustc_info = match env.get_env_os(cache_key) {
279 Some(cache) => cache != "0",
280 _ => true,
281 };
282
283 GlobalContext {
284 home_path: Filesystem::new(homedir),
285 shell: Mutex::new(shell),
286 cwd,
287 search_stop_path: None,
288 values: Default::default(),
289 credential_values: Default::default(),
290 cli_config: None,
291 cargo_exe: Default::default(),
292 rustdoc: Default::default(),
293 extra_verbose: false,
294 frozen: false,
295 locked: false,
296 offline: false,
297 jobserver: unsafe {
298 if GLOBAL_JOBSERVER.is_null() {
299 None
300 } else {
301 Some((*GLOBAL_JOBSERVER).clone())
302 }
303 },
304 unstable_flags: CliUnstable::default(),
305 unstable_flags_cli: None,
306 easy: Default::default(),
307 crates_io_source_id: Default::default(),
308 cache_rustc_info,
309 creation_time: Instant::now(),
310 target_dir: None,
311 env,
312 updated_sources: Default::default(),
313 credential_cache: Default::default(),
314 registry_config: Default::default(),
315 package_cache_lock: CacheLocker::new(),
316 http_config: Default::default(),
317 future_incompat_config: Default::default(),
318 net_config: Default::default(),
319 build_config: Default::default(),
320 target_cfgs: Default::default(),
321 doc_extern_map: Default::default(),
322 progress_config: ProgressConfig::default(),
323 env_config: Default::default(),
324 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
325 ws_roots: Default::default(),
326 global_cache_tracker: Default::default(),
327 deferred_global_last_use: Default::default(),
328 }
329 }
330
331 pub fn default() -> CargoResult<GlobalContext> {
336 let shell = Shell::new();
337 let cwd =
338 env::current_dir().context("couldn't get the current directory of the process")?;
339 let homedir = homedir(&cwd).ok_or_else(|| {
340 anyhow!(
341 "Cargo couldn't find your home directory. \
342 This probably means that $HOME was not set."
343 )
344 })?;
345 Ok(GlobalContext::new(shell, cwd, homedir))
346 }
347
348 pub fn home(&self) -> &Filesystem {
350 &self.home_path
351 }
352
353 pub fn diagnostic_home_config(&self) -> String {
357 let home = self.home_path.as_path_unlocked();
358 let path = match self.get_file_path(home, "config", false) {
359 Ok(Some(existing_path)) => existing_path,
360 _ => home.join("config.toml"),
361 };
362 path.to_string_lossy().to_string()
363 }
364
365 pub fn git_path(&self) -> Filesystem {
367 self.home_path.join("git")
368 }
369
370 pub fn git_checkouts_path(&self) -> Filesystem {
373 self.git_path().join("checkouts")
374 }
375
376 pub fn git_db_path(&self) -> Filesystem {
379 self.git_path().join("db")
380 }
381
382 pub fn registry_base_path(&self) -> Filesystem {
384 self.home_path.join("registry")
385 }
386
387 pub fn registry_index_path(&self) -> Filesystem {
389 self.registry_base_path().join("index")
390 }
391
392 pub fn registry_cache_path(&self) -> Filesystem {
394 self.registry_base_path().join("cache")
395 }
396
397 pub fn registry_source_path(&self) -> Filesystem {
399 self.registry_base_path().join("src")
400 }
401
402 pub fn default_registry(&self) -> CargoResult<Option<String>> {
404 Ok(self
405 .get_string("registry.default")?
406 .map(|registry| registry.val))
407 }
408
409 pub fn shell(&self) -> MutexGuard<'_, Shell> {
411 self.shell.lock().unwrap()
412 }
413
414 pub fn debug_assert_shell_not_borrowed(&self) {
420 if cfg!(debug_assertions) {
421 match self.shell.try_lock() {
422 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
423 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
424 }
425 }
426 }
427
428 pub fn rustdoc(&self) -> CargoResult<&Path> {
430 self.rustdoc
431 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
432 .map(AsRef::as_ref)
433 }
434
435 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
437 let cache_location =
438 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
439 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
440 let rustc_workspace_wrapper = self.maybe_get_tool(
441 "rustc_workspace_wrapper",
442 &self.build_config()?.rustc_workspace_wrapper,
443 );
444
445 Rustc::new(
446 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
447 wrapper,
448 rustc_workspace_wrapper,
449 &self
450 .home()
451 .join("bin")
452 .join("rustc")
453 .into_path_unlocked()
454 .with_extension(env::consts::EXE_EXTENSION),
455 if self.cache_rustc_info {
456 cache_location
457 } else {
458 None
459 },
460 self,
461 )
462 }
463
464 pub fn cargo_exe(&self) -> CargoResult<&Path> {
466 self.cargo_exe
467 .try_borrow_with(|| {
468 let from_env = || -> CargoResult<PathBuf> {
469 let exe = self
474 .get_env_os(crate::CARGO_ENV)
475 .map(PathBuf::from)
476 .ok_or_else(|| anyhow!("$CARGO not set"))?;
477 Ok(exe)
478 };
479
480 fn from_current_exe() -> CargoResult<PathBuf> {
481 let exe = env::current_exe()?;
486 Ok(exe)
487 }
488
489 fn from_argv() -> CargoResult<PathBuf> {
490 let argv0 = env::args_os()
497 .map(PathBuf::from)
498 .next()
499 .ok_or_else(|| anyhow!("no argv[0]"))?;
500 paths::resolve_executable(&argv0)
501 }
502
503 fn is_cargo(path: &Path) -> bool {
506 path.file_stem() == Some(OsStr::new("cargo"))
507 }
508
509 let from_current_exe = from_current_exe();
510 if from_current_exe.as_deref().is_ok_and(is_cargo) {
511 return from_current_exe;
512 }
513
514 let from_argv = from_argv();
515 if from_argv.as_deref().is_ok_and(is_cargo) {
516 return from_argv;
517 }
518
519 let exe = from_env()
520 .or(from_current_exe)
521 .or(from_argv)
522 .context("couldn't get the path to cargo executable")?;
523 Ok(exe)
524 })
525 .map(AsRef::as_ref)
526 }
527
528 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
530 self.updated_sources.lock().unwrap()
531 }
532
533 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
535 self.credential_cache.lock().unwrap()
536 }
537
538 pub(crate) fn registry_config(
540 &self,
541 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
542 self.registry_config.lock().unwrap()
543 }
544
545 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
551 self.values.try_borrow_with(|| self.load_values())
552 }
553
554 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
561 let _ = self.values()?;
562 Ok(self.values.get_mut().expect("already loaded config values"))
563 }
564
565 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
567 if self.values.get().is_some() {
568 bail!("config values already found")
569 }
570 match self.values.set(values.into()) {
571 Ok(()) => Ok(()),
572 Err(_) => bail!("could not fill values"),
573 }
574 }
575
576 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
579 let path = path.into();
580 debug_assert!(self.cwd.starts_with(&path));
581 self.search_stop_path = Some(path);
582 }
583
584 pub fn reload_cwd(&mut self) -> CargoResult<()> {
588 let cwd =
589 env::current_dir().context("couldn't get the current directory of the process")?;
590 let homedir = homedir(&cwd).ok_or_else(|| {
591 anyhow!(
592 "Cargo couldn't find your home directory. \
593 This probably means that $HOME was not set."
594 )
595 })?;
596
597 self.cwd = cwd;
598 self.home_path = Filesystem::new(homedir);
599 self.reload_rooted_at(self.cwd.clone())?;
600 Ok(())
601 }
602
603 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
606 let values = self.load_values_from(path.as_ref())?;
607 self.values.replace(values);
608 self.merge_cli_args()?;
609 self.load_unstable_flags_from_config()?;
610 Ok(())
611 }
612
613 pub fn cwd(&self) -> &Path {
615 &self.cwd
616 }
617
618 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
624 if let Some(dir) = &self.target_dir {
625 Ok(Some(dir.clone()))
626 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
627 if dir.is_empty() {
629 bail!(
630 "the target directory is set to an empty string in the \
631 `CARGO_TARGET_DIR` environment variable"
632 )
633 }
634
635 Ok(Some(Filesystem::new(self.cwd.join(dir))))
636 } else if let Some(val) = &self.build_config()?.target_dir {
637 let path = val.resolve_path(self);
638
639 if val.raw_value().is_empty() {
641 bail!(
642 "the target directory is set to an empty string in {}",
643 val.value().definition
644 )
645 }
646
647 Ok(Some(Filesystem::new(path)))
648 } else {
649 Ok(None)
650 }
651 }
652
653 pub fn build_dir(&self, workspace_manifest_path: &PathBuf) -> CargoResult<Option<Filesystem>> {
659 if let Some(val) = &self.build_config()?.build_dir {
660 let replacements = vec![
661 (
662 "{workspace-root}",
663 workspace_manifest_path
664 .parent()
665 .unwrap()
666 .to_str()
667 .context("workspace root was not valid utf-8")?
668 .to_string(),
669 ),
670 (
671 "{cargo-cache-home}",
672 self.home()
673 .as_path_unlocked()
674 .to_str()
675 .context("cargo home was not valid utf-8")?
676 .to_string(),
677 ),
678 ("{workspace-path-hash}", {
679 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
680 let hash = crate::util::hex::short_hash(&real_path);
681 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
682 }),
683 ];
684
685 let template_variables = replacements
686 .iter()
687 .map(|(key, _)| key[1..key.len() - 1].to_string())
688 .collect_vec();
689
690 let path = val
691 .resolve_templated_path(self, replacements)
692 .map_err(|e| match e {
693 path::ResolveTemplateError::UnexpectedVariable {
694 variable,
695 raw_template,
696 } => {
697 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
698 if suggestion == "" {
699 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
700 suggestion = format!("\n\nhelp: available template variables are {variables}");
701 }
702 anyhow!(
703 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
704 )
705 },
706 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
707 let (btype, literal) = match bracket_type {
708 path::BracketType::Opening => ("opening", "{"),
709 path::BracketType::Closing => ("closing", "}"),
710 };
711
712 anyhow!(
713 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
714 )
715 }
716 })?;
717
718 if val.raw_value().is_empty() {
720 bail!(
721 "the build directory is set to an empty string in {}",
722 val.value().definition
723 )
724 }
725
726 Ok(Some(Filesystem::new(path)))
727 } else {
728 return self.target_dir();
731 }
732 }
733
734 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
739 if let Some(vals) = self.credential_values.get() {
740 let val = self.get_cv_helper(key, vals)?;
741 if val.is_some() {
742 return Ok(val);
743 }
744 }
745 self.get_cv_helper(key, &*self.values()?)
746 }
747
748 fn get_cv_helper(
749 &self,
750 key: &ConfigKey,
751 vals: &HashMap<String, ConfigValue>,
752 ) -> CargoResult<Option<ConfigValue>> {
753 tracing::trace!("get cv {:?}", key);
754 if key.is_root() {
755 return Ok(Some(CV::Table(
758 vals.clone(),
759 Definition::Path(PathBuf::new()),
760 )));
761 }
762 let mut parts = key.parts().enumerate();
763 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
764 return Ok(None);
765 };
766 for (i, part) in parts {
767 match val {
768 CV::Table(map, _) => {
769 val = match map.get(part) {
770 Some(val) => val,
771 None => return Ok(None),
772 }
773 }
774 CV::Integer(_, def)
775 | CV::String(_, def)
776 | CV::List(_, def)
777 | CV::Boolean(_, def) => {
778 let mut key_so_far = ConfigKey::new();
779 for part in key.parts().take(i) {
780 key_so_far.push(part);
781 }
782 bail!(
783 "expected table for configuration key `{}`, \
784 but found {} in {}",
785 key_so_far,
786 val.desc(),
787 def
788 )
789 }
790 }
791 }
792 Ok(Some(val.clone()))
793 }
794
795 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
797 let cv = self.get_cv(key)?;
800 if key.is_root() {
801 return Ok(cv);
803 }
804 let env = self.env.get_str(key.as_env_key());
805 let env_def = Definition::Environment(key.as_env_key().to_string());
806 let use_env = match (&cv, env) {
807 (Some(CV::List(..)), Some(_)) => true,
809 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
810 (None, Some(_)) => true,
811 _ => false,
812 };
813
814 if !use_env {
815 return Ok(cv);
816 }
817
818 let env = env.unwrap();
822 if env == "true" {
823 Ok(Some(CV::Boolean(true, env_def)))
824 } else if env == "false" {
825 Ok(Some(CV::Boolean(false, env_def)))
826 } else if let Ok(i) = env.parse::<i64>() {
827 Ok(Some(CV::Integer(i, env_def)))
828 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
829 match cv {
830 Some(CV::List(mut cv_list, cv_def)) => {
831 self.get_env_list(key, &mut cv_list)?;
833 Ok(Some(CV::List(cv_list, cv_def)))
834 }
835 Some(cv) => {
836 bail!(
840 "unable to merge array env for config `{}`\n\
841 file: {:?}\n\
842 env: {}",
843 key,
844 cv,
845 env
846 );
847 }
848 None => {
849 let mut cv_list = Vec::new();
850 self.get_env_list(key, &mut cv_list)?;
851 Ok(Some(CV::List(cv_list, env_def)))
852 }
853 }
854 } else {
855 match cv {
857 Some(CV::List(mut cv_list, cv_def)) => {
858 self.get_env_list(key, &mut cv_list)?;
860 Ok(Some(CV::List(cv_list, cv_def)))
861 }
862 _ => {
863 Ok(Some(CV::String(env.to_string(), env_def)))
868 }
869 }
870 }
871 }
872
873 pub fn set_env(&mut self, env: HashMap<String, String>) {
875 self.env = Env::from_map(env);
876 }
877
878 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
881 self.env.iter_str()
882 }
883
884 fn env_keys(&self) -> impl Iterator<Item = &str> {
886 self.env.keys_str()
887 }
888
889 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
890 where
891 T: FromStr,
892 <T as FromStr>::Err: fmt::Display,
893 {
894 match self.env.get_str(key.as_env_key()) {
895 Some(value) => {
896 let definition = Definition::Environment(key.as_env_key().to_string());
897 Ok(Some(Value {
898 val: value
899 .parse()
900 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
901 definition,
902 }))
903 }
904 None => {
905 self.check_environment_key_case_mismatch(key);
906 Ok(None)
907 }
908 }
909 }
910
911 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
916 self.env.get_env(key)
917 }
918
919 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
924 self.env.get_env_os(key)
925 }
926
927 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
931 if self.env.contains_key(key.as_env_key()) {
932 return Ok(true);
933 }
934 if env_prefix_ok {
935 let env_prefix = format!("{}_", key.as_env_key());
936 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
937 return Ok(true);
938 }
939 }
940 if self.get_cv(key)?.is_some() {
941 return Ok(true);
942 }
943 self.check_environment_key_case_mismatch(key);
944
945 Ok(false)
946 }
947
948 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
949 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
950 let _ = self.shell().warn(format!(
951 "environment variables are expected to use uppercase letters and underscores, \
952 the variable `{}` will be ignored and have no effect",
953 env_key
954 ));
955 }
956 }
957
958 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
962 self.get::<OptValue<String>>(key)
963 }
964
965 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
971 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
972 v.map(|v| Value {
973 val: v.val.resolve_program(self),
974 definition: v.definition,
975 })
976 })
977 }
978
979 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
980 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
981 if is_path {
982 definition.root(self).join(value)
983 } else {
984 PathBuf::from(value)
986 }
987 }
988
989 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
997 let key = ConfigKey::from_str(key);
998 self._get_list(&key)
999 }
1000
1001 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1002 match self.get_cv(key)? {
1003 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
1004 Some(val) => self.expected("list", key, &val),
1005 None => Ok(None),
1006 }
1007 }
1008
1009 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
1011 let mut res = Vec::new();
1012
1013 match self.get_cv(key)? {
1014 Some(CV::List(val, _def)) => res.extend(val),
1015 Some(CV::String(val, def)) => {
1016 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
1017 res.extend(split_vs);
1018 }
1019 Some(val) => {
1020 return self.expected("string or array of strings", key, &val);
1021 }
1022 None => {}
1023 }
1024
1025 self.get_env_list(key, &mut res)?;
1026
1027 Ok(res)
1028 }
1029
1030 fn get_env_list(
1033 &self,
1034 key: &ConfigKey,
1035 output: &mut Vec<(String, Definition)>,
1036 ) -> CargoResult<()> {
1037 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1038 self.check_environment_key_case_mismatch(key);
1039 return Ok(());
1040 };
1041
1042 if is_nonmergable_list(&key) {
1043 output.clear();
1044 }
1045
1046 let def = Definition::Environment(key.as_env_key().to_string());
1047 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1048 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1050 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1051 })?;
1052 let values = toml_v.as_array().expect("env var was not array");
1053 for value in values {
1054 let s = value.as_str().ok_or_else(|| {
1056 ConfigError::new(
1057 format!("expected string, found {}", value.type_str()),
1058 def.clone(),
1059 )
1060 })?;
1061 output.push((s.to_string(), def.clone()));
1062 }
1063 } else {
1064 output.extend(
1065 env_val
1066 .split_whitespace()
1067 .map(|s| (s.to_string(), def.clone())),
1068 );
1069 }
1070 output.sort_by(|a, b| a.1.cmp(&b.1));
1071 Ok(())
1072 }
1073
1074 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1078 match self.get_cv(key)? {
1079 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1080 Some(val) => self.expected("table", key, &val),
1081 None => Ok(None),
1082 }
1083 }
1084
1085 get_value_typed! {get_integer, i64, Integer, "an integer"}
1086 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1087 get_value_typed! {get_string_priv, String, String, "a string"}
1088
1089 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1091 val.expected(ty, &key.to_string())
1092 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1093 }
1094
1095 pub fn configure(
1101 &mut self,
1102 verbose: u32,
1103 quiet: bool,
1104 color: Option<&str>,
1105 frozen: bool,
1106 locked: bool,
1107 offline: bool,
1108 target_dir: &Option<PathBuf>,
1109 unstable_flags: &[String],
1110 cli_config: &[String],
1111 ) -> CargoResult<()> {
1112 for warning in self
1113 .unstable_flags
1114 .parse(unstable_flags, self.nightly_features_allowed)?
1115 {
1116 self.shell().warn(warning)?;
1117 }
1118 if !unstable_flags.is_empty() {
1119 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1122 }
1123 if !cli_config.is_empty() {
1124 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1125 self.merge_cli_args()?;
1126 }
1127
1128 self.load_unstable_flags_from_config()?;
1132 if self.unstable_flags.config_include {
1133 self.reload_rooted_at(self.cwd.clone())?;
1140 }
1141
1142 let term = self.get::<TermConfig>("term").unwrap_or_default();
1146
1147 let extra_verbose = verbose >= 2;
1149 let verbose = verbose != 0;
1150 let verbosity = match (verbose, quiet) {
1151 (true, true) => bail!("cannot set both --verbose and --quiet"),
1152 (true, false) => Verbosity::Verbose,
1153 (false, true) => Verbosity::Quiet,
1154 (false, false) => match (term.verbose, term.quiet) {
1155 (Some(true), Some(true)) => {
1156 bail!("cannot set both `term.verbose` and `term.quiet`")
1157 }
1158 (Some(true), _) => Verbosity::Verbose,
1159 (_, Some(true)) => Verbosity::Quiet,
1160 _ => Verbosity::Normal,
1161 },
1162 };
1163 self.shell().set_verbosity(verbosity);
1164 self.extra_verbose = extra_verbose;
1165
1166 let color = color.or_else(|| term.color.as_deref());
1167 self.shell().set_color_choice(color)?;
1168 if let Some(hyperlinks) = term.hyperlinks {
1169 self.shell().set_hyperlinks(hyperlinks)?;
1170 }
1171 if let Some(unicode) = term.unicode {
1172 self.shell().set_unicode(unicode)?;
1173 }
1174
1175 self.progress_config = term.progress.unwrap_or_default();
1176
1177 self.frozen = frozen;
1178 self.locked = locked;
1179 self.offline = offline
1180 || self
1181 .net_config()
1182 .ok()
1183 .and_then(|n| n.offline)
1184 .unwrap_or(false);
1185 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1186 self.target_dir = cli_target_dir;
1187
1188 Ok(())
1189 }
1190
1191 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1192 if self.nightly_features_allowed {
1195 self.unstable_flags = self
1196 .get::<Option<CliUnstable>>("unstable")?
1197 .unwrap_or_default();
1198 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1199 self.unstable_flags.parse(unstable_flags_cli, true)?;
1204 }
1205 }
1206
1207 Ok(())
1208 }
1209
1210 pub fn cli_unstable(&self) -> &CliUnstable {
1211 &self.unstable_flags
1212 }
1213
1214 pub fn extra_verbose(&self) -> bool {
1215 self.extra_verbose
1216 }
1217
1218 pub fn network_allowed(&self) -> bool {
1219 !self.offline_flag().is_some()
1220 }
1221
1222 pub fn offline_flag(&self) -> Option<&'static str> {
1223 if self.frozen {
1224 Some("--frozen")
1225 } else if self.offline {
1226 Some("--offline")
1227 } else {
1228 None
1229 }
1230 }
1231
1232 pub fn set_locked(&mut self, locked: bool) {
1233 self.locked = locked;
1234 }
1235
1236 pub fn lock_update_allowed(&self) -> bool {
1237 !self.locked_flag().is_some()
1238 }
1239
1240 pub fn locked_flag(&self) -> Option<&'static str> {
1241 if self.frozen {
1242 Some("--frozen")
1243 } else if self.locked {
1244 Some("--locked")
1245 } else {
1246 None
1247 }
1248 }
1249
1250 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1252 self.load_values_from(&self.cwd)
1253 }
1254
1255 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1259 let mut result = Vec::new();
1260 let mut seen = HashSet::new();
1261 let home = self.home_path.clone().into_path_unlocked();
1262 self.walk_tree(&self.cwd, &home, |path| {
1263 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1264 if self.cli_unstable().config_include {
1265 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1266 }
1267 result.push(cv);
1268 Ok(())
1269 })
1270 .context("could not load Cargo configuration")?;
1271 Ok(result)
1272 }
1273
1274 fn load_unmerged_include(
1278 &self,
1279 cv: &mut CV,
1280 seen: &mut HashSet<PathBuf>,
1281 output: &mut Vec<CV>,
1282 ) -> CargoResult<()> {
1283 let includes = self.include_paths(cv, false)?;
1284 for (path, abs_path, def) in includes {
1285 let mut cv = self
1286 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1287 .with_context(|| {
1288 format!("failed to load config include `{}` from `{}`", path, def)
1289 })?;
1290 self.load_unmerged_include(&mut cv, seen, output)?;
1291 output.push(cv);
1292 }
1293 Ok(())
1294 }
1295
1296 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1298 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1301 let home = self.home_path.clone().into_path_unlocked();
1302
1303 self.walk_tree(path, &home, |path| {
1304 let value = self.load_file(path)?;
1305 cfg.merge(value, false).with_context(|| {
1306 format!("failed to merge configuration at `{}`", path.display())
1307 })?;
1308 Ok(())
1309 })
1310 .context("could not load Cargo configuration")?;
1311
1312 match cfg {
1313 CV::Table(map, _) => Ok(map),
1314 _ => unreachable!(),
1315 }
1316 }
1317
1318 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1322 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1323 }
1324
1325 fn _load_file(
1335 &self,
1336 path: &Path,
1337 seen: &mut HashSet<PathBuf>,
1338 includes: bool,
1339 why_load: WhyLoad,
1340 ) -> CargoResult<ConfigValue> {
1341 if !seen.insert(path.to_path_buf()) {
1342 bail!(
1343 "config `include` cycle detected with path `{}`",
1344 path.display()
1345 );
1346 }
1347 tracing::debug!(?path, ?why_load, includes, "load config from file");
1348
1349 let contents = fs::read_to_string(path)
1350 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1351 let toml = parse_document(&contents, path, self).with_context(|| {
1352 format!("could not parse TOML configuration in `{}`", path.display())
1353 })?;
1354 let def = match why_load {
1355 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1356 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1357 };
1358 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1359 format!(
1360 "failed to load TOML configuration from `{}`",
1361 path.display()
1362 )
1363 })?;
1364 if includes {
1365 self.load_includes(value, seen, why_load)
1366 } else {
1367 Ok(value)
1368 }
1369 }
1370
1371 fn load_includes(
1378 &self,
1379 mut value: CV,
1380 seen: &mut HashSet<PathBuf>,
1381 why_load: WhyLoad,
1382 ) -> CargoResult<CV> {
1383 let includes = self.include_paths(&mut value, true)?;
1385 if !self.cli_unstable().config_include {
1387 return Ok(value);
1388 }
1389 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1391 for (path, abs_path, def) in includes {
1392 self._load_file(&abs_path, seen, true, why_load)
1393 .and_then(|include| root.merge(include, true))
1394 .with_context(|| {
1395 format!("failed to load config include `{}` from `{}`", path, def)
1396 })?;
1397 }
1398 root.merge(value, true)?;
1399 Ok(root)
1400 }
1401
1402 fn include_paths(
1404 &self,
1405 cv: &mut CV,
1406 remove: bool,
1407 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1408 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1409 let abs_path = match def {
1410 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1411 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1412 };
1413 (path.to_string(), abs_path, def.clone())
1414 };
1415 let CV::Table(table, _def) = cv else {
1416 unreachable!()
1417 };
1418 let owned;
1419 let include = if remove {
1420 owned = table.remove("include");
1421 owned.as_ref()
1422 } else {
1423 table.get("include")
1424 };
1425 let includes = match include {
1426 Some(CV::String(s, def)) => {
1427 vec![abs(s, def)]
1428 }
1429 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1430 Some(other) => bail!(
1431 "`include` expected a string or list, but found {} in `{}`",
1432 other.desc(),
1433 other.definition()
1434 ),
1435 None => {
1436 return Ok(Vec::new());
1437 }
1438 };
1439
1440 for (path, abs_path, def) in &includes {
1441 if abs_path.extension() != Some(OsStr::new("toml")) {
1442 bail!(
1443 "expected a config include path ending with `.toml`, \
1444 but found `{path}` from `{def}`",
1445 )
1446 }
1447 }
1448
1449 Ok(includes)
1450 }
1451
1452 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1454 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1455 let Some(cli_args) = &self.cli_config else {
1456 return Ok(loaded_args);
1457 };
1458 let mut seen = HashSet::new();
1459 for arg in cli_args {
1460 let arg_as_path = self.cwd.join(arg);
1461 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1462 let str_path = arg_as_path
1464 .to_str()
1465 .ok_or_else(|| {
1466 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1467 })?
1468 .to_string();
1469 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1470 .with_context(|| format!("failed to load config from `{}`", str_path))?
1471 } else {
1472 let doc = toml_dotted_keys(arg)?;
1473 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1474 .with_context(|| {
1475 format!("failed to parse value from --config argument `{arg}`")
1476 })?;
1477
1478 if doc
1479 .get("registry")
1480 .and_then(|v| v.as_table())
1481 .and_then(|t| t.get("token"))
1482 .is_some()
1483 {
1484 bail!("registry.token cannot be set through --config for security reasons");
1485 } else if let Some((k, _)) = doc
1486 .get("registries")
1487 .and_then(|v| v.as_table())
1488 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1489 {
1490 bail!(
1491 "registries.{}.token cannot be set through --config for security reasons",
1492 k
1493 );
1494 }
1495
1496 if doc
1497 .get("registry")
1498 .and_then(|v| v.as_table())
1499 .and_then(|t| t.get("secret-key"))
1500 .is_some()
1501 {
1502 bail!(
1503 "registry.secret-key cannot be set through --config for security reasons"
1504 );
1505 } else if let Some((k, _)) = doc
1506 .get("registries")
1507 .and_then(|v| v.as_table())
1508 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1509 {
1510 bail!(
1511 "registries.{}.secret-key cannot be set through --config for security reasons",
1512 k
1513 );
1514 }
1515
1516 CV::from_toml(Definition::Cli(None), doc)
1517 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1518 };
1519 let tmp_table = self
1520 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1521 .context("failed to load --config include".to_string())?;
1522 loaded_args
1523 .merge(tmp_table, true)
1524 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1525 }
1526 Ok(loaded_args)
1527 }
1528
1529 fn merge_cli_args(&mut self) -> CargoResult<()> {
1531 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1532 unreachable!()
1533 };
1534 let values = self.values_mut()?;
1535 for (key, value) in loaded_map.into_iter() {
1536 match values.entry(key) {
1537 Vacant(entry) => {
1538 entry.insert(value);
1539 }
1540 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1541 format!(
1542 "failed to merge --config key `{}` into `{}`",
1543 entry.key(),
1544 entry.get().definition(),
1545 )
1546 })?,
1547 };
1548 }
1549 Ok(())
1550 }
1551
1552 fn get_file_path(
1558 &self,
1559 dir: &Path,
1560 filename_without_extension: &str,
1561 warn: bool,
1562 ) -> CargoResult<Option<PathBuf>> {
1563 let possible = dir.join(filename_without_extension);
1564 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1565
1566 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1567 if warn {
1568 if let Ok(possible_with_extension_handle) =
1569 same_file::Handle::from_path(&possible_with_extension)
1570 {
1571 if possible_handle != possible_with_extension_handle {
1577 self.shell().warn(format!(
1578 "both `{}` and `{}` exist. Using `{}`",
1579 possible.display(),
1580 possible_with_extension.display(),
1581 possible.display()
1582 ))?;
1583 }
1584 } else {
1585 self.shell().warn(format!(
1586 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1587 possible.display(),
1588 ))?;
1589 self.shell().note(
1590 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1591 )?;
1592 }
1593 }
1594
1595 Ok(Some(possible))
1596 } else if possible_with_extension.exists() {
1597 Ok(Some(possible_with_extension))
1598 } else {
1599 Ok(None)
1600 }
1601 }
1602
1603 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1604 where
1605 F: FnMut(&Path) -> CargoResult<()>,
1606 {
1607 let mut seen_dir = HashSet::new();
1608
1609 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1610 let config_root = current.join(".cargo");
1611 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1612 walk(&path)?;
1613 }
1614 seen_dir.insert(config_root);
1615 }
1616
1617 if !seen_dir.contains(home) {
1621 if let Some(path) = self.get_file_path(home, "config", true)? {
1622 walk(&path)?;
1623 }
1624 }
1625
1626 Ok(())
1627 }
1628
1629 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1631 RegistryName::new(registry)?;
1632 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1633 self.resolve_registry_index(&index).with_context(|| {
1634 format!(
1635 "invalid index URL for registry `{}` defined in {}",
1636 registry, index.definition
1637 )
1638 })
1639 } else {
1640 bail!(
1641 "registry index was not found in any configuration: `{}`",
1642 registry
1643 );
1644 }
1645 }
1646
1647 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1649 if self.get_string("registry.index")?.is_some() {
1650 bail!(
1651 "the `registry.index` config value is no longer supported\n\
1652 Use `[source]` replacement to alter the default index for crates.io."
1653 );
1654 }
1655 Ok(())
1656 }
1657
1658 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1659 let base = index
1661 .definition
1662 .root(self)
1663 .join("truncated-by-url_with_base");
1664 let _parsed = index.val.into_url()?;
1666 let url = index.val.into_url_with_base(Some(&*base))?;
1667 if url.password().is_some() {
1668 bail!("registry URLs may not contain passwords");
1669 }
1670 Ok(url)
1671 }
1672
1673 pub fn load_credentials(&self) -> CargoResult<()> {
1681 if self.credential_values.filled() {
1682 return Ok(());
1683 }
1684
1685 let home_path = self.home_path.clone().into_path_unlocked();
1686 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1687 return Ok(());
1688 };
1689
1690 let mut value = self.load_file(&credentials)?;
1691 {
1693 let CV::Table(ref mut value_map, ref def) = value else {
1694 unreachable!();
1695 };
1696
1697 if let Some(token) = value_map.remove("token") {
1698 if let Vacant(entry) = value_map.entry("registry".into()) {
1699 let map = HashMap::from([("token".into(), token)]);
1700 let table = CV::Table(map, def.clone());
1701 entry.insert(table);
1702 }
1703 }
1704 }
1705
1706 let mut credential_values = HashMap::new();
1707 if let CV::Table(map, _) = value {
1708 let base_map = self.values()?;
1709 for (k, v) in map {
1710 let entry = match base_map.get(&k) {
1711 Some(base_entry) => {
1712 let mut entry = base_entry.clone();
1713 entry.merge(v, true)?;
1714 entry
1715 }
1716 None => v,
1717 };
1718 credential_values.insert(k, entry);
1719 }
1720 }
1721 self.credential_values
1722 .set(credential_values)
1723 .expect("was not filled at beginning of the function");
1724 Ok(())
1725 }
1726
1727 fn maybe_get_tool(
1730 &self,
1731 tool: &str,
1732 from_config: &Option<ConfigRelativePath>,
1733 ) -> Option<PathBuf> {
1734 let var = tool.to_uppercase();
1735
1736 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1737 Some(tool_path) => {
1738 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1739 let path = if maybe_relative {
1740 self.cwd.join(tool_path)
1741 } else {
1742 PathBuf::from(tool_path)
1743 };
1744 Some(path)
1745 }
1746
1747 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1748 }
1749 }
1750
1751 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1762 let tool_str = tool.as_str();
1763 self.maybe_get_tool(tool_str, from_config)
1764 .or_else(|| {
1765 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1779 if toolchain.to_str()?.contains(&['/', '\\']) {
1782 return None;
1783 }
1784 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1787 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1788 let tool_meta = tool_resolved.metadata().ok()?;
1789 let rustup_meta = rustup_resolved.metadata().ok()?;
1790 if tool_meta.len() != rustup_meta.len() {
1795 return None;
1796 }
1797 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1799 let toolchain_exe = home::rustup_home()
1800 .ok()?
1801 .join("toolchains")
1802 .join(&toolchain)
1803 .join("bin")
1804 .join(&tool_exe);
1805 toolchain_exe.exists().then_some(toolchain_exe)
1806 })
1807 .unwrap_or_else(|| PathBuf::from(tool_str))
1808 }
1809
1810 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1811 self.jobserver.as_ref()
1812 }
1813
1814 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1815 let http = self
1816 .easy
1817 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1818 {
1819 let mut http = http.lock().unwrap();
1820 http.reset();
1821 let timeout = configure_http_handle(self, &mut http)?;
1822 timeout.configure(&mut http)?;
1823 }
1824 Ok(http)
1825 }
1826
1827 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1828 self.http_config.try_borrow_with(|| {
1829 let mut http = self.get::<CargoHttpConfig>("http")?;
1830 let curl_v = curl::Version::get();
1831 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1832 Ok(http)
1833 })
1834 }
1835
1836 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1837 self.future_incompat_config
1838 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1839 }
1840
1841 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1842 self.net_config
1843 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1844 }
1845
1846 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1847 self.build_config
1848 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1849 }
1850
1851 pub fn progress_config(&self) -> &ProgressConfig {
1852 &self.progress_config
1853 }
1854
1855 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1858 let env_config = self.env_config.try_borrow_with(|| {
1859 CargoResult::Ok(Arc::new({
1860 let env_config = self.get::<EnvConfig>("env")?;
1861 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1877 if env_config.contains_key(*disallowed) {
1878 bail!(
1879 "setting the `{disallowed}` environment variable is not supported \
1880 in the `[env]` configuration table"
1881 );
1882 }
1883 }
1884 env_config
1885 .into_iter()
1886 .filter_map(|(k, v)| {
1887 if v.is_force() || self.get_env_os(&k).is_none() {
1888 Some((k, v.resolve(self).to_os_string()))
1889 } else {
1890 None
1891 }
1892 })
1893 .collect()
1894 }))
1895 })?;
1896
1897 Ok(env_config)
1898 }
1899
1900 pub fn validate_term_config(&self) -> CargoResult<()> {
1906 drop(self.get::<TermConfig>("term")?);
1907 Ok(())
1908 }
1909
1910 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1914 self.target_cfgs
1915 .try_borrow_with(|| target::load_target_cfgs(self))
1916 }
1917
1918 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1919 self.doc_extern_map
1923 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1924 }
1925
1926 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1928 target::get_target_applies_to_host(self)
1929 }
1930
1931 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1933 target::load_host_triple(self, target)
1934 }
1935
1936 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1938 target::load_target_triple(self, target)
1939 }
1940
1941 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1946 let source_id = self.crates_io_source_id.try_borrow_with(|| {
1947 self.check_registry_index_not_set()?;
1948 let url = CRATES_IO_INDEX.into_url().unwrap();
1949 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1950 })?;
1951 Ok(*source_id)
1952 }
1953
1954 pub fn creation_time(&self) -> Instant {
1955 self.creation_time
1956 }
1957
1958 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1973 let d = Deserializer {
1974 gctx: self,
1975 key: ConfigKey::from_str(key),
1976 env_prefix_ok: true,
1977 };
1978 T::deserialize(d).map_err(|e| e.into())
1979 }
1980
1981 #[track_caller]
1987 #[tracing::instrument(skip_all)]
1988 pub fn assert_package_cache_locked<'a>(
1989 &self,
1990 mode: CacheLockMode,
1991 f: &'a Filesystem,
1992 ) -> &'a Path {
1993 let ret = f.as_path_unlocked();
1994 assert!(
1995 self.package_cache_lock.is_locked(mode),
1996 "package cache lock is not currently held, Cargo forgot to call \
1997 `acquire_package_cache_lock` before we got to this stack frame",
1998 );
1999 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2000 ret
2001 }
2002
2003 #[tracing::instrument(skip_all)]
2009 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2010 self.package_cache_lock.lock(self, mode)
2011 }
2012
2013 #[tracing::instrument(skip_all)]
2019 pub fn try_acquire_package_cache_lock(
2020 &self,
2021 mode: CacheLockMode,
2022 ) -> CargoResult<Option<CacheLock<'_>>> {
2023 self.package_cache_lock.try_lock(self, mode)
2024 }
2025
2026 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2031 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2032 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2033 })?;
2034 Ok(tracker.lock().unwrap())
2035 }
2036
2037 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2039 let deferred = self
2040 .deferred_global_last_use
2041 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2042 Ok(deferred.lock().unwrap())
2043 }
2044
2045 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2047 if self.unstable_flags.warnings {
2048 Ok(self.build_config()?.warnings.unwrap_or_default())
2049 } else {
2050 Ok(WarningHandling::default())
2051 }
2052 }
2053
2054 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2055 self.ws_roots.lock().unwrap()
2056 }
2057}
2058
2059#[derive(Debug)]
2061pub struct ConfigError {
2062 error: anyhow::Error,
2063 definition: Option<Definition>,
2064}
2065
2066impl ConfigError {
2067 fn new(message: String, definition: Definition) -> ConfigError {
2068 ConfigError {
2069 error: anyhow::Error::msg(message),
2070 definition: Some(definition),
2071 }
2072 }
2073
2074 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2075 ConfigError {
2076 error: anyhow!(
2077 "`{}` expected {}, but found a {}",
2078 key,
2079 expected,
2080 found.desc()
2081 ),
2082 definition: Some(found.definition().clone()),
2083 }
2084 }
2085
2086 fn is_missing_field(&self) -> bool {
2087 self.error.downcast_ref::<MissingFieldError>().is_some()
2088 }
2089
2090 fn missing(key: &ConfigKey) -> ConfigError {
2091 ConfigError {
2092 error: anyhow!("missing config key `{}`", key),
2093 definition: None,
2094 }
2095 }
2096
2097 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2098 ConfigError {
2099 error: anyhow::Error::from(self)
2100 .context(format!("could not load config key `{}`", key)),
2101 definition: definition,
2102 }
2103 }
2104}
2105
2106impl std::error::Error for ConfigError {
2107 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2108 self.error.source()
2109 }
2110}
2111
2112impl fmt::Display for ConfigError {
2113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2114 if let Some(definition) = &self.definition {
2115 write!(f, "error in {}: {}", definition, self.error)
2116 } else {
2117 self.error.fmt(f)
2118 }
2119 }
2120}
2121
2122#[derive(Debug)]
2123struct MissingFieldError(String);
2124
2125impl fmt::Display for MissingFieldError {
2126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2127 write!(f, "missing field `{}`", self.0)
2128 }
2129}
2130
2131impl std::error::Error for MissingFieldError {}
2132
2133impl serde::de::Error for ConfigError {
2134 fn custom<T: fmt::Display>(msg: T) -> Self {
2135 ConfigError {
2136 error: anyhow::Error::msg(msg.to_string()),
2137 definition: None,
2138 }
2139 }
2140
2141 fn missing_field(field: &'static str) -> Self {
2142 ConfigError {
2143 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2144 definition: None,
2145 }
2146 }
2147}
2148
2149impl From<anyhow::Error> for ConfigError {
2150 fn from(error: anyhow::Error) -> Self {
2151 ConfigError {
2152 error,
2153 definition: None,
2154 }
2155 }
2156}
2157
2158#[derive(Debug)]
2159enum KeyOrIdx {
2160 Key(String),
2161 Idx(usize),
2162}
2163
2164#[derive(Eq, PartialEq, Clone)]
2165pub enum ConfigValue {
2166 Integer(i64, Definition),
2167 String(String, Definition),
2168 List(Vec<(String, Definition)>, Definition),
2169 Table(HashMap<String, ConfigValue>, Definition),
2170 Boolean(bool, Definition),
2171}
2172
2173impl fmt::Debug for ConfigValue {
2174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2175 match self {
2176 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2177 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2178 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2179 CV::List(list, def) => {
2180 write!(f, "[")?;
2181 for (i, (s, def)) in list.iter().enumerate() {
2182 if i > 0 {
2183 write!(f, ", ")?;
2184 }
2185 write!(f, "{} (from {})", s, def)?;
2186 }
2187 write!(f, "] (from {})", def)
2188 }
2189 CV::Table(table, _) => write!(f, "{:?}", table),
2190 }
2191 }
2192}
2193
2194impl ConfigValue {
2195 fn get_definition(&self) -> &Definition {
2196 match self {
2197 CV::Boolean(_, def)
2198 | CV::Integer(_, def)
2199 | CV::String(_, def)
2200 | CV::List(_, def)
2201 | CV::Table(_, def) => def,
2202 }
2203 }
2204
2205 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2206 let mut error_path = Vec::new();
2207 Self::from_toml_inner(def, toml, &mut error_path).with_context(|| {
2208 let mut it = error_path.iter().rev().peekable();
2209 let mut key_path = String::with_capacity(error_path.len() * 3);
2210 while let Some(k) = it.next() {
2211 match k {
2212 KeyOrIdx::Key(s) => key_path.push_str(&key::escape_key_part(&s)),
2213 KeyOrIdx::Idx(i) => key_path.push_str(&format!("[{i}]")),
2214 }
2215 if matches!(it.peek(), Some(KeyOrIdx::Key(_))) {
2216 key_path.push('.');
2217 }
2218 }
2219 format!("failed to parse config at `{key_path}`")
2220 })
2221 }
2222
2223 fn from_toml_inner(
2224 def: Definition,
2225 toml: toml::Value,
2226 path: &mut Vec<KeyOrIdx>,
2227 ) -> CargoResult<ConfigValue> {
2228 match toml {
2229 toml::Value::String(val) => Ok(CV::String(val, def)),
2230 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2231 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2232 toml::Value::Array(val) => Ok(CV::List(
2233 val.into_iter()
2234 .enumerate()
2235 .map(|(i, toml)| match toml {
2236 toml::Value::String(val) => Ok((val, def.clone())),
2237 v => {
2238 path.push(KeyOrIdx::Idx(i));
2239 bail!("expected string but found {} at index {i}", v.type_str())
2240 }
2241 })
2242 .collect::<CargoResult<_>>()?,
2243 def,
2244 )),
2245 toml::Value::Table(val) => Ok(CV::Table(
2246 val.into_iter()
2247 .map(
2248 |(key, value)| match CV::from_toml_inner(def.clone(), value, path) {
2249 Ok(value) => Ok((key, value)),
2250 Err(e) => {
2251 path.push(KeyOrIdx::Key(key));
2252 Err(e)
2253 }
2254 },
2255 )
2256 .collect::<CargoResult<_>>()?,
2257 def,
2258 )),
2259 v => bail!(
2260 "found TOML configuration value of unknown type `{}`",
2261 v.type_str()
2262 ),
2263 }
2264 }
2265
2266 fn into_toml(self) -> toml::Value {
2267 match self {
2268 CV::Boolean(s, _) => toml::Value::Boolean(s),
2269 CV::String(s, _) => toml::Value::String(s),
2270 CV::Integer(i, _) => toml::Value::Integer(i),
2271 CV::List(l, _) => {
2272 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2273 }
2274 CV::Table(l, _) => {
2275 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2276 }
2277 }
2278 }
2279
2280 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2289 self.merge_helper(from, force, &mut ConfigKey::new())
2290 }
2291
2292 fn merge_helper(
2293 &mut self,
2294 from: ConfigValue,
2295 force: bool,
2296 parts: &mut ConfigKey,
2297 ) -> CargoResult<()> {
2298 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2299 match (self, from) {
2300 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2301 if is_nonmergable_list(&parts) {
2302 if force || is_higher_priority {
2304 mem::swap(new, old);
2305 }
2306 } else {
2307 if force {
2309 old.append(new);
2310 } else {
2311 new.append(old);
2312 mem::swap(new, old);
2313 }
2314 }
2315 old.sort_by(|a, b| a.1.cmp(&b.1));
2316 }
2317 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2318 for (key, value) in mem::take(new) {
2319 match old.entry(key.clone()) {
2320 Occupied(mut entry) => {
2321 let new_def = value.definition().clone();
2322 let entry = entry.get_mut();
2323 parts.push(&key);
2324 entry.merge_helper(value, force, parts).with_context(|| {
2325 format!(
2326 "failed to merge key `{}` between \
2327 {} and {}",
2328 key,
2329 entry.definition(),
2330 new_def,
2331 )
2332 })?;
2333 }
2334 Vacant(entry) => {
2335 entry.insert(value);
2336 }
2337 };
2338 }
2339 }
2340 (expected @ &mut CV::List(_, _), found)
2342 | (expected @ &mut CV::Table(_, _), found)
2343 | (expected, found @ CV::List(_, _))
2344 | (expected, found @ CV::Table(_, _)) => {
2345 return Err(anyhow!(
2346 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2347 found.definition(),
2348 expected.definition(),
2349 expected.desc(),
2350 found.desc()
2351 ));
2352 }
2353 (old, mut new) => {
2354 if force || is_higher_priority {
2355 mem::swap(old, &mut new);
2356 }
2357 }
2358 }
2359
2360 Ok(())
2361 }
2362
2363 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2364 match self {
2365 CV::Integer(i, def) => Ok((*i, def)),
2366 _ => self.expected("integer", key),
2367 }
2368 }
2369
2370 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2371 match self {
2372 CV::String(s, def) => Ok((s, def)),
2373 _ => self.expected("string", key),
2374 }
2375 }
2376
2377 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2378 match self {
2379 CV::Table(table, def) => Ok((table, def)),
2380 _ => self.expected("table", key),
2381 }
2382 }
2383
2384 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2385 match self {
2386 CV::List(list, _) => Ok(list),
2387 _ => self.expected("list", key),
2388 }
2389 }
2390
2391 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2392 match self {
2393 CV::Boolean(b, def) => Ok((*b, def)),
2394 _ => self.expected("bool", key),
2395 }
2396 }
2397
2398 pub fn desc(&self) -> &'static str {
2399 match *self {
2400 CV::Table(..) => "table",
2401 CV::List(..) => "array",
2402 CV::String(..) => "string",
2403 CV::Boolean(..) => "boolean",
2404 CV::Integer(..) => "integer",
2405 }
2406 }
2407
2408 pub fn definition(&self) -> &Definition {
2409 match self {
2410 CV::Boolean(_, def)
2411 | CV::Integer(_, def)
2412 | CV::String(_, def)
2413 | CV::List(_, def)
2414 | CV::Table(_, def) => def,
2415 }
2416 }
2417
2418 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2419 bail!(
2420 "expected a {}, but found a {} for `{}` in {}",
2421 wanted,
2422 self.desc(),
2423 key,
2424 self.definition()
2425 )
2426 }
2427}
2428
2429fn is_nonmergable_list(key: &ConfigKey) -> bool {
2432 key.matches("registry.credential-provider")
2433 || key.matches("registries.*.credential-provider")
2434 || key.matches("target.*.runner")
2435 || key.matches("host.runner")
2436 || key.matches("credential-alias.*")
2437 || key.matches("doc.browser")
2438}
2439
2440pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2441 ::home::cargo_home_with_cwd(cwd).ok()
2442}
2443
2444pub fn save_credentials(
2445 gctx: &GlobalContext,
2446 token: Option<RegistryCredentialConfig>,
2447 registry: &SourceId,
2448) -> CargoResult<()> {
2449 let registry = if registry.is_crates_io() {
2450 None
2451 } else {
2452 let name = registry
2453 .alt_registry_key()
2454 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2455 Some(name)
2456 };
2457
2458 let home_path = gctx.home_path.clone().into_path_unlocked();
2462 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2463 Some(path) => match path.file_name() {
2464 Some(filename) => Path::new(filename).to_owned(),
2465 None => Path::new("credentials.toml").to_owned(),
2466 },
2467 None => Path::new("credentials.toml").to_owned(),
2468 };
2469
2470 let mut file = {
2471 gctx.home_path.create_dir()?;
2472 gctx.home_path
2473 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2474 };
2475
2476 let mut contents = String::new();
2477 file.read_to_string(&mut contents).with_context(|| {
2478 format!(
2479 "failed to read configuration file `{}`",
2480 file.path().display()
2481 )
2482 })?;
2483
2484 let mut toml = parse_document(&contents, file.path(), gctx)?;
2485
2486 if let Some(token) = toml.remove("token") {
2488 let map = HashMap::from([("token".to_string(), token)]);
2489 toml.insert("registry".into(), map.into());
2490 }
2491
2492 if let Some(token) = token {
2493 let path_def = Definition::Path(file.path().to_path_buf());
2496 let (key, mut value) = match token {
2497 RegistryCredentialConfig::Token(token) => {
2498 let key = "token".to_string();
2501 let value = ConfigValue::String(token.expose(), path_def.clone());
2502 let map = HashMap::from([(key, value)]);
2503 let table = CV::Table(map, path_def.clone());
2504
2505 if let Some(registry) = registry {
2506 let map = HashMap::from([(registry.to_string(), table)]);
2507 ("registries".into(), CV::Table(map, path_def.clone()))
2508 } else {
2509 ("registry".into(), table)
2510 }
2511 }
2512 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2513 let key = "secret-key".to_string();
2516 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2517 let mut map = HashMap::from([(key, value)]);
2518 if let Some(key_subject) = key_subject {
2519 let key = "secret-key-subject".to_string();
2520 let value = ConfigValue::String(key_subject, path_def.clone());
2521 map.insert(key, value);
2522 }
2523 let table = CV::Table(map, path_def.clone());
2524
2525 if let Some(registry) = registry {
2526 let map = HashMap::from([(registry.to_string(), table)]);
2527 ("registries".into(), CV::Table(map, path_def.clone()))
2528 } else {
2529 ("registry".into(), table)
2530 }
2531 }
2532 _ => unreachable!(),
2533 };
2534
2535 if registry.is_some() {
2536 if let Some(table) = toml.remove("registries") {
2537 let v = CV::from_toml(path_def, table)?;
2538 value.merge(v, false)?;
2539 }
2540 }
2541 toml.insert(key, value.into_toml());
2542 } else {
2543 if let Some(registry) = registry {
2545 if let Some(registries) = toml.get_mut("registries") {
2546 if let Some(reg) = registries.get_mut(registry) {
2547 let rtable = reg.as_table_mut().ok_or_else(|| {
2548 format_err!("expected `[registries.{}]` to be a table", registry)
2549 })?;
2550 rtable.remove("token");
2551 rtable.remove("secret-key");
2552 rtable.remove("secret-key-subject");
2553 }
2554 }
2555 } else if let Some(registry) = toml.get_mut("registry") {
2556 let reg_table = registry
2557 .as_table_mut()
2558 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2559 reg_table.remove("token");
2560 reg_table.remove("secret-key");
2561 reg_table.remove("secret-key-subject");
2562 }
2563 }
2564
2565 let contents = toml.to_string();
2566 file.seek(SeekFrom::Start(0))?;
2567 file.write_all(contents.as_bytes())
2568 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2569 file.file().set_len(contents.len() as u64)?;
2570 set_permissions(file.file(), 0o600)
2571 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2572
2573 return Ok(());
2574
2575 #[cfg(unix)]
2576 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2577 use std::os::unix::fs::PermissionsExt;
2578
2579 let mut perms = file.metadata()?.permissions();
2580 perms.set_mode(mode);
2581 file.set_permissions(perms)?;
2582 Ok(())
2583 }
2584
2585 #[cfg(not(unix))]
2586 #[allow(unused)]
2587 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2588 Ok(())
2589 }
2590}
2591
2592#[derive(Debug, Default, Deserialize, PartialEq)]
2593#[serde(rename_all = "kebab-case")]
2594pub struct CargoHttpConfig {
2595 pub proxy: Option<String>,
2596 pub low_speed_limit: Option<u32>,
2597 pub timeout: Option<u64>,
2598 pub cainfo: Option<ConfigRelativePath>,
2599 pub proxy_cainfo: Option<ConfigRelativePath>,
2600 pub check_revoke: Option<bool>,
2601 pub user_agent: Option<String>,
2602 pub debug: Option<bool>,
2603 pub multiplexing: Option<bool>,
2604 pub ssl_version: Option<SslVersionConfig>,
2605}
2606
2607#[derive(Debug, Default, Deserialize, PartialEq)]
2608#[serde(rename_all = "kebab-case")]
2609pub struct CargoFutureIncompatConfig {
2610 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2611}
2612
2613#[derive(Debug, Default, Deserialize, PartialEq)]
2614#[serde(rename_all = "kebab-case")]
2615pub enum CargoFutureIncompatFrequencyConfig {
2616 #[default]
2617 Always,
2618 Never,
2619}
2620
2621impl CargoFutureIncompatConfig {
2622 pub fn should_display_message(&self) -> bool {
2623 use CargoFutureIncompatFrequencyConfig::*;
2624
2625 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2626 match frequency {
2627 Always => true,
2628 Never => false,
2629 }
2630 }
2631}
2632
2633#[derive(Clone, Debug, PartialEq)]
2647pub enum SslVersionConfig {
2648 Single(String),
2649 Range(SslVersionConfigRange),
2650}
2651
2652impl<'de> Deserialize<'de> for SslVersionConfig {
2653 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2654 where
2655 D: serde::Deserializer<'de>,
2656 {
2657 UntaggedEnumVisitor::new()
2658 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2659 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2660 .deserialize(deserializer)
2661 }
2662}
2663
2664#[derive(Clone, Debug, Deserialize, PartialEq)]
2665#[serde(rename_all = "kebab-case")]
2666pub struct SslVersionConfigRange {
2667 pub min: Option<String>,
2668 pub max: Option<String>,
2669}
2670
2671#[derive(Debug, Deserialize)]
2672#[serde(rename_all = "kebab-case")]
2673pub struct CargoNetConfig {
2674 pub retry: Option<u32>,
2675 pub offline: Option<bool>,
2676 pub git_fetch_with_cli: Option<bool>,
2677 pub ssh: Option<CargoSshConfig>,
2678}
2679
2680#[derive(Debug, Deserialize)]
2681#[serde(rename_all = "kebab-case")]
2682pub struct CargoSshConfig {
2683 pub known_hosts: Option<Vec<Value<String>>>,
2684}
2685
2686#[derive(Debug, Clone)]
2699pub enum JobsConfig {
2700 Integer(i32),
2701 String(String),
2702}
2703
2704impl<'de> Deserialize<'de> for JobsConfig {
2705 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2706 where
2707 D: serde::Deserializer<'de>,
2708 {
2709 UntaggedEnumVisitor::new()
2710 .i32(|int| Ok(JobsConfig::Integer(int)))
2711 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2712 .deserialize(deserializer)
2713 }
2714}
2715
2716#[derive(Debug, Deserialize)]
2717#[serde(rename_all = "kebab-case")]
2718pub struct CargoBuildConfig {
2719 pub pipelining: Option<bool>,
2721 pub dep_info_basedir: Option<ConfigRelativePath>,
2722 pub target_dir: Option<ConfigRelativePath>,
2723 pub build_dir: Option<ConfigRelativePath>,
2724 pub incremental: Option<bool>,
2725 pub target: Option<BuildTargetConfig>,
2726 pub jobs: Option<JobsConfig>,
2727 pub rustflags: Option<StringList>,
2728 pub rustdocflags: Option<StringList>,
2729 pub rustc_wrapper: Option<ConfigRelativePath>,
2730 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2731 pub rustc: Option<ConfigRelativePath>,
2732 pub rustdoc: Option<ConfigRelativePath>,
2733 pub out_dir: Option<ConfigRelativePath>,
2735 pub artifact_dir: Option<ConfigRelativePath>,
2736 pub warnings: Option<WarningHandling>,
2737 pub sbom: Option<bool>,
2739 pub analysis: Option<CargoBuildAnalysis>,
2741}
2742
2743#[derive(Debug, Deserialize, Default)]
2745#[serde(rename_all = "kebab-case")]
2746pub struct CargoBuildAnalysis {
2747 pub enabled: bool,
2748}
2749
2750#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2752#[serde(rename_all = "kebab-case")]
2753pub enum WarningHandling {
2754 #[default]
2755 Warn,
2757 Allow,
2759 Deny,
2761}
2762
2763#[derive(Debug, Deserialize)]
2773#[serde(transparent)]
2774pub struct BuildTargetConfig {
2775 inner: Value<BuildTargetConfigInner>,
2776}
2777
2778#[derive(Debug)]
2779enum BuildTargetConfigInner {
2780 One(String),
2781 Many(Vec<String>),
2782}
2783
2784impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2785 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2786 where
2787 D: serde::Deserializer<'de>,
2788 {
2789 UntaggedEnumVisitor::new()
2790 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2791 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2792 .deserialize(deserializer)
2793 }
2794}
2795
2796impl BuildTargetConfig {
2797 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2799 let map = |s: &String| {
2800 if s.ends_with(".json") {
2801 self.inner
2804 .definition
2805 .root(gctx)
2806 .join(s)
2807 .to_str()
2808 .expect("must be utf-8 in toml")
2809 .to_string()
2810 } else {
2811 s.to_string()
2813 }
2814 };
2815 let values = match &self.inner.val {
2816 BuildTargetConfigInner::One(s) => vec![map(s)],
2817 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2818 };
2819 Ok(values)
2820 }
2821}
2822
2823#[derive(Debug, Deserialize)]
2824#[serde(rename_all = "kebab-case")]
2825pub struct CargoResolverConfig {
2826 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2827 pub feature_unification: Option<FeatureUnification>,
2828}
2829
2830#[derive(Debug, Deserialize, PartialEq, Eq)]
2831#[serde(rename_all = "kebab-case")]
2832pub enum IncompatibleRustVersions {
2833 Allow,
2834 Fallback,
2835}
2836
2837#[derive(Copy, Clone, Debug, Deserialize)]
2838#[serde(rename_all = "kebab-case")]
2839pub enum FeatureUnification {
2840 Package,
2841 Selected,
2842 Workspace,
2843}
2844
2845#[derive(Deserialize, Default)]
2846#[serde(rename_all = "kebab-case")]
2847pub struct TermConfig {
2848 pub verbose: Option<bool>,
2849 pub quiet: Option<bool>,
2850 pub color: Option<String>,
2851 pub hyperlinks: Option<bool>,
2852 pub unicode: Option<bool>,
2853 #[serde(default)]
2854 #[serde(deserialize_with = "progress_or_string")]
2855 pub progress: Option<ProgressConfig>,
2856}
2857
2858#[derive(Debug, Default, Deserialize)]
2859#[serde(rename_all = "kebab-case")]
2860pub struct ProgressConfig {
2861 #[serde(default)]
2862 pub when: ProgressWhen,
2863 pub width: Option<usize>,
2864 pub term_integration: Option<bool>,
2866}
2867
2868#[derive(Debug, Default, Deserialize)]
2869#[serde(rename_all = "kebab-case")]
2870pub enum ProgressWhen {
2871 #[default]
2872 Auto,
2873 Never,
2874 Always,
2875}
2876
2877fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2878where
2879 D: serde::de::Deserializer<'de>,
2880{
2881 struct ProgressVisitor;
2882
2883 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2884 type Value = Option<ProgressConfig>;
2885
2886 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2887 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2888 }
2889
2890 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2891 where
2892 E: serde::de::Error,
2893 {
2894 match s {
2895 "auto" => Ok(Some(ProgressConfig {
2896 when: ProgressWhen::Auto,
2897 width: None,
2898 term_integration: None,
2899 })),
2900 "never" => Ok(Some(ProgressConfig {
2901 when: ProgressWhen::Never,
2902 width: None,
2903 term_integration: None,
2904 })),
2905 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2906 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2907 }
2908 }
2909
2910 fn visit_none<E>(self) -> Result<Self::Value, E>
2911 where
2912 E: serde::de::Error,
2913 {
2914 Ok(None)
2915 }
2916
2917 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2918 where
2919 D: serde::de::Deserializer<'de>,
2920 {
2921 let pc = ProgressConfig::deserialize(deserializer)?;
2922 if let ProgressConfig {
2923 when: ProgressWhen::Always,
2924 width: None,
2925 ..
2926 } = pc
2927 {
2928 return Err(serde::de::Error::custom(
2929 "\"always\" progress requires a `width` key",
2930 ));
2931 }
2932 Ok(Some(pc))
2933 }
2934 }
2935
2936 deserializer.deserialize_option(ProgressVisitor)
2937}
2938
2939#[derive(Debug)]
2940enum EnvConfigValueInner {
2941 Simple(String),
2942 WithOptions {
2943 value: String,
2944 force: bool,
2945 relative: bool,
2946 },
2947}
2948
2949impl<'de> Deserialize<'de> for EnvConfigValueInner {
2950 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2951 where
2952 D: serde::Deserializer<'de>,
2953 {
2954 #[derive(Deserialize)]
2955 struct WithOptions {
2956 value: String,
2957 #[serde(default)]
2958 force: bool,
2959 #[serde(default)]
2960 relative: bool,
2961 }
2962
2963 UntaggedEnumVisitor::new()
2964 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2965 .map(|map| {
2966 let with_options: WithOptions = map.deserialize()?;
2967 Ok(EnvConfigValueInner::WithOptions {
2968 value: with_options.value,
2969 force: with_options.force,
2970 relative: with_options.relative,
2971 })
2972 })
2973 .deserialize(deserializer)
2974 }
2975}
2976
2977#[derive(Debug, Deserialize)]
2978#[serde(transparent)]
2979pub struct EnvConfigValue {
2980 inner: Value<EnvConfigValueInner>,
2981}
2982
2983impl EnvConfigValue {
2984 pub fn is_force(&self) -> bool {
2985 match self.inner.val {
2986 EnvConfigValueInner::Simple(_) => false,
2987 EnvConfigValueInner::WithOptions { force, .. } => force,
2988 }
2989 }
2990
2991 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2992 match self.inner.val {
2993 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2994 EnvConfigValueInner::WithOptions {
2995 ref value,
2996 relative,
2997 ..
2998 } => {
2999 if relative {
3000 let p = self.inner.definition.root(gctx).join(&value);
3001 Cow::Owned(p.into_os_string())
3002 } else {
3003 Cow::Borrowed(OsStr::new(value.as_str()))
3004 }
3005 }
3006 }
3007 }
3008}
3009
3010pub type EnvConfig = HashMap<String, EnvConfigValue>;
3011
3012fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3013 toml.parse().map_err(Into::into)
3015}
3016
3017fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
3018 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
3024 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
3025 })?;
3026 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
3027 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
3028 }
3029 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
3030 non_empty(d.prefix()) || non_empty(d.suffix())
3031 }
3032 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
3033 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
3034 }
3035 let ok = {
3036 let mut got_to_value = false;
3037 let mut table = doc.as_table();
3038 let mut is_root = true;
3039 while table.is_dotted() || is_root {
3040 is_root = false;
3041 if table.len() != 1 {
3042 break;
3043 }
3044 let (k, n) = table.iter().next().expect("len() == 1 above");
3045 match n {
3046 Item::Table(nt) => {
3047 if table.key(k).map_or(false, non_empty_key_decor)
3048 || non_empty_decor(nt.decor())
3049 {
3050 bail!(
3051 "--config argument `{arg}` \
3052 includes non-whitespace decoration"
3053 )
3054 }
3055 table = nt;
3056 }
3057 Item::Value(v) if v.is_inline_table() => {
3058 bail!(
3059 "--config argument `{arg}` \
3060 sets a value to an inline table, which is not accepted"
3061 );
3062 }
3063 Item::Value(v) => {
3064 if table
3065 .key(k)
3066 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
3067 || non_empty_decor(v.decor())
3068 {
3069 bail!(
3070 "--config argument `{arg}` \
3071 includes non-whitespace decoration"
3072 )
3073 }
3074 got_to_value = true;
3075 break;
3076 }
3077 Item::ArrayOfTables(_) => {
3078 bail!(
3079 "--config argument `{arg}` \
3080 sets a value to an array of tables, which is not accepted"
3081 );
3082 }
3083
3084 Item::None => {
3085 bail!("--config argument `{arg}` doesn't provide a value")
3086 }
3087 }
3088 }
3089 got_to_value
3090 };
3091 if !ok {
3092 bail!(
3093 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
3094 );
3095 }
3096 Ok(doc)
3097}
3098
3099#[derive(Debug, Deserialize, Clone)]
3110pub struct StringList(Vec<String>);
3111
3112impl StringList {
3113 pub fn as_slice(&self) -> &[String] {
3114 &self.0
3115 }
3116}
3117
3118#[macro_export]
3119macro_rules! __shell_print {
3120 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3121 let mut shell = $config.shell();
3122 let out = shell.$which();
3123 drop(out.write_fmt(format_args!($($arg)*)));
3124 if $newline {
3125 drop(out.write_all(b"\n"));
3126 }
3127 });
3128}
3129
3130#[macro_export]
3131macro_rules! drop_println {
3132 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3133 ($config:expr, $($arg:tt)*) => (
3134 $crate::__shell_print!($config, out, true, $($arg)*)
3135 );
3136}
3137
3138#[macro_export]
3139macro_rules! drop_eprintln {
3140 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3141 ($config:expr, $($arg:tt)*) => (
3142 $crate::__shell_print!($config, err, true, $($arg)*)
3143 );
3144}
3145
3146#[macro_export]
3147macro_rules! drop_print {
3148 ($config:expr, $($arg:tt)*) => (
3149 $crate::__shell_print!($config, out, false, $($arg)*)
3150 );
3151}
3152
3153#[macro_export]
3154macro_rules! drop_eprint {
3155 ($config:expr, $($arg:tt)*) => (
3156 $crate::__shell_print!($config, err, false, $($arg)*)
3157 );
3158}
3159
3160enum Tool {
3161 Rustc,
3162 Rustdoc,
3163}
3164
3165impl Tool {
3166 fn as_str(&self) -> &str {
3167 match self {
3168 Tool::Rustc => "rustc",
3169 Tool::Rustdoc => "rustdoc",
3170 }
3171 }
3172}
3173
3174fn disables_multiplexing_for_bad_curl(
3184 curl_version: &str,
3185 http: &mut CargoHttpConfig,
3186 gctx: &GlobalContext,
3187) {
3188 use crate::util::network;
3189
3190 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3191 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3192 if bad_curl_versions
3193 .iter()
3194 .any(|v| curl_version.starts_with(v))
3195 {
3196 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3197 http.multiplexing = Some(false);
3198 }
3199 }
3200}
3201
3202#[cfg(test)]
3203mod tests {
3204 use super::CargoHttpConfig;
3205 use super::GlobalContext;
3206 use super::Shell;
3207 use super::disables_multiplexing_for_bad_curl;
3208
3209 #[test]
3210 fn disables_multiplexing() {
3211 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3212 gctx.set_search_stop_path(std::path::PathBuf::new());
3213 gctx.set_env(Default::default());
3214
3215 let mut http = CargoHttpConfig::default();
3216 http.proxy = Some("127.0.0.1:3128".into());
3217 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3218 assert_eq!(http.multiplexing, Some(false));
3219
3220 let cases = [
3221 (None, None, "7.87.0", None),
3222 (None, None, "7.88.0", None),
3223 (None, None, "7.88.1", None),
3224 (None, None, "8.0.0", None),
3225 (Some("".into()), None, "7.87.0", Some(false)),
3226 (Some("".into()), None, "7.88.0", Some(false)),
3227 (Some("".into()), None, "7.88.1", Some(false)),
3228 (Some("".into()), None, "8.0.0", None),
3229 (Some("".into()), Some(false), "7.87.0", Some(false)),
3230 (Some("".into()), Some(false), "7.88.0", Some(false)),
3231 (Some("".into()), Some(false), "7.88.1", Some(false)),
3232 (Some("".into()), Some(false), "8.0.0", Some(false)),
3233 ];
3234
3235 for (proxy, multiplexing, curl_v, result) in cases {
3236 let mut http = CargoHttpConfig {
3237 multiplexing,
3238 proxy,
3239 ..Default::default()
3240 };
3241 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3242 assert_eq!(http.multiplexing, result);
3243 }
3244 }
3245
3246 #[test]
3247 fn sync_context() {
3248 fn assert_sync<S: Sync>() {}
3249 assert_sync::<GlobalContext>();
3250 }
3251}