1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
53use std::borrow::Cow;
54use std::cell::{RefCell, RefMut};
55use std::collections::hash_map::Entry::{Occupied, Vacant};
56use std::collections::{HashMap, HashSet};
57use std::env;
58use std::ffi::{OsStr, OsString};
59use std::fmt;
60use std::fs::{self, File};
61use std::io::prelude::*;
62use std::io::SeekFrom;
63use std::mem;
64use std::path::{Path, PathBuf};
65use std::str::FromStr;
66use std::sync::{Arc, Once};
67use std::time::Instant;
68
69use self::ConfigValue as CV;
70use crate::core::compiler::rustdoc::RustdocExternMap;
71use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
72use crate::core::shell::Verbosity;
73use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig};
74use crate::ops::RegistryCredentialConfig;
75use crate::sources::CRATES_IO_INDEX;
76use crate::sources::CRATES_IO_REGISTRY;
77use crate::util::errors::CargoResult;
78use crate::util::network::http::configure_http_handle;
79use crate::util::network::http::http_handle;
80use crate::util::{closest_msg, internal, CanonicalUrl};
81use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
82use anyhow::{anyhow, bail, format_err, Context as _};
83use cargo_credential::Secret;
84use cargo_util::paths;
85use cargo_util_schemas::manifest::RegistryName;
86use curl::easy::Easy;
87use itertools::Itertools;
88use lazycell::LazyCell;
89use serde::de::IntoDeserializer as _;
90use serde::Deserialize;
91use serde_untagged::UntaggedEnumVisitor;
92use time::OffsetDateTime;
93use toml_edit::Item;
94use url::Url;
95
96mod de;
97use de::Deserializer;
98
99mod value;
100pub use value::{Definition, OptValue, Value};
101
102mod key;
103pub use key::ConfigKey;
104
105mod path;
106pub use path::{ConfigRelativePath, PathAndArgs};
107
108mod target;
109pub use target::{TargetCfgConfig, TargetConfig};
110
111mod environment;
112use environment::Env;
113
114use super::auth::RegistryConfig;
115
116macro_rules! get_value_typed {
118 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
119 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
121 let cv = self.get_cv(key)?;
122 let env = self.get_config_env::<$ty>(key)?;
123 match (cv, env) {
124 (Some(CV::$variant(val, definition)), Some(env)) => {
125 if definition.is_higher_priority(&env.definition) {
126 Ok(Some(Value { val, definition }))
127 } else {
128 Ok(Some(env))
129 }
130 }
131 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
132 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
133 (None, Some(env)) => Ok(Some(env)),
134 (None, None) => Ok(None),
135 }
136 }
137 };
138}
139
140#[derive(Clone, Copy, Debug)]
142enum WhyLoad {
143 Cli,
150 FileDiscovery,
152}
153
154#[derive(Debug)]
156pub struct CredentialCacheValue {
157 pub token_value: Secret<String>,
158 pub expiration: Option<OffsetDateTime>,
159 pub operation_independent: bool,
160}
161
162#[derive(Debug)]
165pub struct GlobalContext {
166 home_path: Filesystem,
168 shell: RefCell<Shell>,
170 values: LazyCell<HashMap<String, ConfigValue>>,
172 credential_values: LazyCell<HashMap<String, ConfigValue>>,
174 cli_config: Option<Vec<String>>,
176 cwd: PathBuf,
178 search_stop_path: Option<PathBuf>,
180 cargo_exe: LazyCell<PathBuf>,
182 rustdoc: LazyCell<PathBuf>,
184 extra_verbose: bool,
186 frozen: bool,
189 locked: bool,
192 offline: bool,
195 jobserver: Option<jobserver::Client>,
197 unstable_flags: CliUnstable,
199 unstable_flags_cli: Option<Vec<String>>,
201 easy: LazyCell<RefCell<Easy>>,
203 crates_io_source_id: LazyCell<SourceId>,
205 cache_rustc_info: bool,
207 creation_time: Instant,
209 target_dir: Option<Filesystem>,
211 env: Env,
213 updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
215 credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, CredentialCacheValue>>>,
218 registry_config: LazyCell<RefCell<HashMap<SourceId, Option<RegistryConfig>>>>,
220 package_cache_lock: CacheLocker,
222 http_config: LazyCell<CargoHttpConfig>,
224 future_incompat_config: LazyCell<CargoFutureIncompatConfig>,
225 net_config: LazyCell<CargoNetConfig>,
226 build_config: LazyCell<CargoBuildConfig>,
227 target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
228 doc_extern_map: LazyCell<RustdocExternMap>,
229 progress_config: ProgressConfig,
230 env_config: LazyCell<Arc<HashMap<String, OsString>>>,
231 pub nightly_features_allowed: bool,
247 pub ws_roots: RefCell<HashMap<PathBuf, WorkspaceRootConfig>>,
249 global_cache_tracker: LazyCell<RefCell<GlobalCacheTracker>>,
251 deferred_global_last_use: LazyCell<RefCell<DeferredGlobalLastUse>>,
254}
255
256impl GlobalContext {
257 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
265 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
266 static INIT: Once = Once::new();
267
268 INIT.call_once(|| unsafe {
271 if let Some(client) = jobserver::Client::from_env() {
272 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
273 }
274 });
275
276 let env = Env::new();
277
278 let cache_key = "CARGO_CACHE_RUSTC_INFO";
279 let cache_rustc_info = match env.get_env_os(cache_key) {
280 Some(cache) => cache != "0",
281 _ => true,
282 };
283
284 GlobalContext {
285 home_path: Filesystem::new(homedir),
286 shell: RefCell::new(shell),
287 cwd,
288 search_stop_path: None,
289 values: LazyCell::new(),
290 credential_values: LazyCell::new(),
291 cli_config: None,
292 cargo_exe: LazyCell::new(),
293 rustdoc: LazyCell::new(),
294 extra_verbose: false,
295 frozen: false,
296 locked: false,
297 offline: false,
298 jobserver: unsafe {
299 if GLOBAL_JOBSERVER.is_null() {
300 None
301 } else {
302 Some((*GLOBAL_JOBSERVER).clone())
303 }
304 },
305 unstable_flags: CliUnstable::default(),
306 unstable_flags_cli: None,
307 easy: LazyCell::new(),
308 crates_io_source_id: LazyCell::new(),
309 cache_rustc_info,
310 creation_time: Instant::now(),
311 target_dir: None,
312 env,
313 updated_sources: LazyCell::new(),
314 credential_cache: LazyCell::new(),
315 registry_config: LazyCell::new(),
316 package_cache_lock: CacheLocker::new(),
317 http_config: LazyCell::new(),
318 future_incompat_config: LazyCell::new(),
319 net_config: LazyCell::new(),
320 build_config: LazyCell::new(),
321 target_cfgs: LazyCell::new(),
322 doc_extern_map: LazyCell::new(),
323 progress_config: ProgressConfig::default(),
324 env_config: LazyCell::new(),
325 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
326 ws_roots: RefCell::new(HashMap::new()),
327 global_cache_tracker: LazyCell::new(),
328 deferred_global_last_use: LazyCell::new(),
329 }
330 }
331
332 pub fn default() -> CargoResult<GlobalContext> {
337 let shell = Shell::new();
338 let cwd =
339 env::current_dir().context("couldn't get the current directory of the process")?;
340 let homedir = homedir(&cwd).ok_or_else(|| {
341 anyhow!(
342 "Cargo couldn't find your home directory. \
343 This probably means that $HOME was not set."
344 )
345 })?;
346 Ok(GlobalContext::new(shell, cwd, homedir))
347 }
348
349 pub fn home(&self) -> &Filesystem {
351 &self.home_path
352 }
353
354 pub fn diagnostic_home_config(&self) -> String {
358 let home = self.home_path.as_path_unlocked();
359 let path = match self.get_file_path(home, "config", false) {
360 Ok(Some(existing_path)) => existing_path,
361 _ => home.join("config.toml"),
362 };
363 path.to_string_lossy().to_string()
364 }
365
366 pub fn git_path(&self) -> Filesystem {
368 self.home_path.join("git")
369 }
370
371 pub fn git_checkouts_path(&self) -> Filesystem {
374 self.git_path().join("checkouts")
375 }
376
377 pub fn git_db_path(&self) -> Filesystem {
380 self.git_path().join("db")
381 }
382
383 pub fn registry_base_path(&self) -> Filesystem {
385 self.home_path.join("registry")
386 }
387
388 pub fn registry_index_path(&self) -> Filesystem {
390 self.registry_base_path().join("index")
391 }
392
393 pub fn registry_cache_path(&self) -> Filesystem {
395 self.registry_base_path().join("cache")
396 }
397
398 pub fn registry_source_path(&self) -> Filesystem {
400 self.registry_base_path().join("src")
401 }
402
403 pub fn default_registry(&self) -> CargoResult<Option<String>> {
405 Ok(self
406 .get_string("registry.default")?
407 .map(|registry| registry.val))
408 }
409
410 pub fn shell(&self) -> RefMut<'_, Shell> {
412 self.shell.borrow_mut()
413 }
414
415 pub fn rustdoc(&self) -> CargoResult<&Path> {
417 self.rustdoc
418 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
419 .map(AsRef::as_ref)
420 }
421
422 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
424 let cache_location =
425 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
426 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
427 let rustc_workspace_wrapper = self.maybe_get_tool(
428 "rustc_workspace_wrapper",
429 &self.build_config()?.rustc_workspace_wrapper,
430 );
431
432 Rustc::new(
433 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
434 wrapper,
435 rustc_workspace_wrapper,
436 &self
437 .home()
438 .join("bin")
439 .join("rustc")
440 .into_path_unlocked()
441 .with_extension(env::consts::EXE_EXTENSION),
442 if self.cache_rustc_info {
443 cache_location
444 } else {
445 None
446 },
447 self,
448 )
449 }
450
451 pub fn cargo_exe(&self) -> CargoResult<&Path> {
453 self.cargo_exe
454 .try_borrow_with(|| {
455 let from_env = || -> CargoResult<PathBuf> {
456 let exe = self
461 .get_env_os(crate::CARGO_ENV)
462 .map(PathBuf::from)
463 .ok_or_else(|| anyhow!("$CARGO not set"))?;
464 Ok(exe)
465 };
466
467 fn from_current_exe() -> CargoResult<PathBuf> {
468 let exe = env::current_exe()?;
473 Ok(exe)
474 }
475
476 fn from_argv() -> CargoResult<PathBuf> {
477 let argv0 = env::args_os()
484 .map(PathBuf::from)
485 .next()
486 .ok_or_else(|| anyhow!("no argv[0]"))?;
487 paths::resolve_executable(&argv0)
488 }
489
490 fn is_cargo(path: &Path) -> bool {
493 path.file_stem() == Some(OsStr::new("cargo"))
494 }
495
496 let from_current_exe = from_current_exe();
497 if from_current_exe.as_deref().is_ok_and(is_cargo) {
498 return from_current_exe;
499 }
500
501 let from_argv = from_argv();
502 if from_argv.as_deref().is_ok_and(is_cargo) {
503 return from_argv;
504 }
505
506 let exe = from_env()
507 .or(from_current_exe)
508 .or(from_argv)
509 .context("couldn't get the path to cargo executable")?;
510 Ok(exe)
511 })
512 .map(AsRef::as_ref)
513 }
514
515 pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
517 self.updated_sources
518 .borrow_with(|| RefCell::new(HashSet::new()))
519 .borrow_mut()
520 }
521
522 pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
524 self.credential_cache
525 .borrow_with(|| RefCell::new(HashMap::new()))
526 .borrow_mut()
527 }
528
529 pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
531 self.registry_config
532 .borrow_with(|| RefCell::new(HashMap::new()))
533 .borrow_mut()
534 }
535
536 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
542 self.values.try_borrow_with(|| self.load_values())
543 }
544
545 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
552 let _ = self.values()?;
553 Ok(self
554 .values
555 .borrow_mut()
556 .expect("already loaded config values"))
557 }
558
559 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
561 if self.values.borrow().is_some() {
562 bail!("config values already found")
563 }
564 match self.values.fill(values) {
565 Ok(()) => Ok(()),
566 Err(_) => bail!("could not fill values"),
567 }
568 }
569
570 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
573 let path = path.into();
574 debug_assert!(self.cwd.starts_with(&path));
575 self.search_stop_path = Some(path);
576 }
577
578 pub fn reload_cwd(&mut self) -> CargoResult<()> {
582 let cwd =
583 env::current_dir().context("couldn't get the current directory of the process")?;
584 let homedir = homedir(&cwd).ok_or_else(|| {
585 anyhow!(
586 "Cargo couldn't find your home directory. \
587 This probably means that $HOME was not set."
588 )
589 })?;
590
591 self.cwd = cwd;
592 self.home_path = Filesystem::new(homedir);
593 self.reload_rooted_at(self.cwd.clone())?;
594 Ok(())
595 }
596
597 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
600 let values = self.load_values_from(path.as_ref())?;
601 self.values.replace(values);
602 self.merge_cli_args()?;
603 self.load_unstable_flags_from_config()?;
604 Ok(())
605 }
606
607 pub fn cwd(&self) -> &Path {
609 &self.cwd
610 }
611
612 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
618 if let Some(dir) = &self.target_dir {
619 Ok(Some(dir.clone()))
620 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
621 if dir.is_empty() {
623 bail!(
624 "the target directory is set to an empty string in the \
625 `CARGO_TARGET_DIR` environment variable"
626 )
627 }
628
629 Ok(Some(Filesystem::new(self.cwd.join(dir))))
630 } else if let Some(val) = &self.build_config()?.target_dir {
631 let path = val.resolve_path(self);
632
633 if val.raw_value().is_empty() {
635 bail!(
636 "the target directory is set to an empty string in {}",
637 val.value().definition
638 )
639 }
640
641 Ok(Some(Filesystem::new(path)))
642 } else {
643 Ok(None)
644 }
645 }
646
647 pub fn build_dir(&self, workspace_manifest_path: &PathBuf) -> CargoResult<Option<Filesystem>> {
653 if !self.cli_unstable().build_dir {
654 return self.target_dir();
655 }
656 if let Some(val) = &self.build_config()?.build_dir {
657 let replacements = vec![
658 (
659 "{workspace-root}",
660 workspace_manifest_path
661 .parent()
662 .unwrap()
663 .to_str()
664 .context("workspace root was not valid utf-8")?
665 .to_string(),
666 ),
667 (
668 "{cargo-cache-home}",
669 self.home()
670 .as_path_unlocked()
671 .to_str()
672 .context("cargo home was not valid utf-8")?
673 .to_string(),
674 ),
675 ("{workspace-path-hash}", {
676 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
677 let hash = crate::util::hex::short_hash(&real_path);
678 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
679 }),
680 ];
681
682 let template_variables = replacements
683 .iter()
684 .map(|(key, _)| key[1..key.len() - 1].to_string())
685 .collect_vec();
686
687 let path = val
688 .resolve_templated_path(self, replacements)
689 .map_err(|e| match e {
690 path::ResolveTemplateError::UnexpectedVariable {
691 variable,
692 raw_template,
693 } => {
694 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
695 if suggestion == "" {
696 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
697 suggestion = format!("\n\nhelp: available template variables are {variables}");
698 }
699 anyhow!(
700 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
701 )
702 },
703 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
704 let (btype, literal) = match bracket_type {
705 path::BracketType::Opening => ("opening", "{"),
706 path::BracketType::Closing => ("closing", "}"),
707 };
708
709 anyhow!(
710 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
711 )
712 }
713 })?;
714
715 if val.raw_value().is_empty() {
717 bail!(
718 "the build directory is set to an empty string in {}",
719 val.value().definition
720 )
721 }
722
723 Ok(Some(Filesystem::new(path)))
724 } else {
725 return self.target_dir();
728 }
729 }
730
731 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
736 if let Some(vals) = self.credential_values.borrow() {
737 let val = self.get_cv_helper(key, vals)?;
738 if val.is_some() {
739 return Ok(val);
740 }
741 }
742 self.get_cv_helper(key, self.values()?)
743 }
744
745 fn get_cv_helper(
746 &self,
747 key: &ConfigKey,
748 vals: &HashMap<String, ConfigValue>,
749 ) -> CargoResult<Option<ConfigValue>> {
750 tracing::trace!("get cv {:?}", key);
751 if key.is_root() {
752 return Ok(Some(CV::Table(
755 vals.clone(),
756 Definition::Path(PathBuf::new()),
757 )));
758 }
759 let mut parts = key.parts().enumerate();
760 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
761 return Ok(None);
762 };
763 for (i, part) in parts {
764 match val {
765 CV::Table(map, _) => {
766 val = match map.get(part) {
767 Some(val) => val,
768 None => return Ok(None),
769 }
770 }
771 CV::Integer(_, def)
772 | CV::String(_, def)
773 | CV::List(_, def)
774 | CV::Boolean(_, def) => {
775 let mut key_so_far = ConfigKey::new();
776 for part in key.parts().take(i) {
777 key_so_far.push(part);
778 }
779 bail!(
780 "expected table for configuration key `{}`, \
781 but found {} in {}",
782 key_so_far,
783 val.desc(),
784 def
785 )
786 }
787 }
788 }
789 Ok(Some(val.clone()))
790 }
791
792 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
794 let cv = self.get_cv(key)?;
797 if key.is_root() {
798 return Ok(cv);
800 }
801 let env = self.env.get_str(key.as_env_key());
802 let env_def = Definition::Environment(key.as_env_key().to_string());
803 let use_env = match (&cv, env) {
804 (Some(CV::List(..)), Some(_)) => true,
806 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
807 (None, Some(_)) => true,
808 _ => false,
809 };
810
811 if !use_env {
812 return Ok(cv);
813 }
814
815 let env = env.unwrap();
819 if env == "true" {
820 Ok(Some(CV::Boolean(true, env_def)))
821 } else if env == "false" {
822 Ok(Some(CV::Boolean(false, env_def)))
823 } else if let Ok(i) = env.parse::<i64>() {
824 Ok(Some(CV::Integer(i, env_def)))
825 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
826 match cv {
827 Some(CV::List(mut cv_list, cv_def)) => {
828 self.get_env_list(key, &mut cv_list)?;
830 Ok(Some(CV::List(cv_list, cv_def)))
831 }
832 Some(cv) => {
833 bail!(
837 "unable to merge array env for config `{}`\n\
838 file: {:?}\n\
839 env: {}",
840 key,
841 cv,
842 env
843 );
844 }
845 None => {
846 let mut cv_list = Vec::new();
847 self.get_env_list(key, &mut cv_list)?;
848 Ok(Some(CV::List(cv_list, env_def)))
849 }
850 }
851 } else {
852 match cv {
854 Some(CV::List(mut cv_list, cv_def)) => {
855 self.get_env_list(key, &mut cv_list)?;
857 Ok(Some(CV::List(cv_list, cv_def)))
858 }
859 _ => {
860 Ok(Some(CV::String(env.to_string(), env_def)))
865 }
866 }
867 }
868 }
869
870 pub fn set_env(&mut self, env: HashMap<String, String>) {
872 self.env = Env::from_map(env);
873 }
874
875 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
878 self.env.iter_str()
879 }
880
881 fn env_keys(&self) -> impl Iterator<Item = &str> {
883 self.env.keys_str()
884 }
885
886 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
887 where
888 T: FromStr,
889 <T as FromStr>::Err: fmt::Display,
890 {
891 match self.env.get_str(key.as_env_key()) {
892 Some(value) => {
893 let definition = Definition::Environment(key.as_env_key().to_string());
894 Ok(Some(Value {
895 val: value
896 .parse()
897 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
898 definition,
899 }))
900 }
901 None => {
902 self.check_environment_key_case_mismatch(key);
903 Ok(None)
904 }
905 }
906 }
907
908 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
913 self.env.get_env(key)
914 }
915
916 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
921 self.env.get_env_os(key)
922 }
923
924 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
928 if self.env.contains_key(key.as_env_key()) {
929 return Ok(true);
930 }
931 if env_prefix_ok {
932 let env_prefix = format!("{}_", key.as_env_key());
933 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
934 return Ok(true);
935 }
936 }
937 if self.get_cv(key)?.is_some() {
938 return Ok(true);
939 }
940 self.check_environment_key_case_mismatch(key);
941
942 Ok(false)
943 }
944
945 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
946 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
947 let _ = self.shell().warn(format!(
948 "environment variables are expected to use uppercase letters and underscores, \
949 the variable `{}` will be ignored and have no effect",
950 env_key
951 ));
952 }
953 }
954
955 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
959 self.get::<OptValue<String>>(key)
960 }
961
962 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
968 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
969 v.map(|v| Value {
970 val: v.val.resolve_program(self),
971 definition: v.definition,
972 })
973 })
974 }
975
976 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
977 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
978 if is_path {
979 definition.root(self).join(value)
980 } else {
981 PathBuf::from(value)
983 }
984 }
985
986 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
994 let key = ConfigKey::from_str(key);
995 self._get_list(&key)
996 }
997
998 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
999 match self.get_cv(key)? {
1000 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
1001 Some(val) => self.expected("list", key, &val),
1002 None => Ok(None),
1003 }
1004 }
1005
1006 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
1008 let mut res = Vec::new();
1009
1010 match self.get_cv(key)? {
1011 Some(CV::List(val, _def)) => res.extend(val),
1012 Some(CV::String(val, def)) => {
1013 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
1014 res.extend(split_vs);
1015 }
1016 Some(val) => {
1017 return self.expected("string or array of strings", key, &val);
1018 }
1019 None => {}
1020 }
1021
1022 self.get_env_list(key, &mut res)?;
1023
1024 Ok(res)
1025 }
1026
1027 fn get_env_list(
1030 &self,
1031 key: &ConfigKey,
1032 output: &mut Vec<(String, Definition)>,
1033 ) -> CargoResult<()> {
1034 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1035 self.check_environment_key_case_mismatch(key);
1036 return Ok(());
1037 };
1038
1039 if is_nonmergable_list(&key) {
1040 output.clear();
1041 }
1042
1043 let def = Definition::Environment(key.as_env_key().to_string());
1044 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1045 let toml_v = toml::Value::deserialize(toml::de::ValueDeserializer::new(&env_val))
1047 .map_err(|e| {
1048 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1049 })?;
1050 let values = toml_v.as_array().expect("env var was not array");
1051 for value in values {
1052 let s = value.as_str().ok_or_else(|| {
1054 ConfigError::new(
1055 format!("expected string, found {}", value.type_str()),
1056 def.clone(),
1057 )
1058 })?;
1059 output.push((s.to_string(), def.clone()));
1060 }
1061 } else {
1062 output.extend(
1063 env_val
1064 .split_whitespace()
1065 .map(|s| (s.to_string(), def.clone())),
1066 );
1067 }
1068 output.sort_by(|a, b| a.1.cmp(&b.1));
1069 Ok(())
1070 }
1071
1072 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1076 match self.get_cv(key)? {
1077 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1078 Some(val) => self.expected("table", key, &val),
1079 None => Ok(None),
1080 }
1081 }
1082
1083 get_value_typed! {get_integer, i64, Integer, "an integer"}
1084 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1085 get_value_typed! {get_string_priv, String, String, "a string"}
1086
1087 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1089 val.expected(ty, &key.to_string())
1090 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1091 }
1092
1093 pub fn configure(
1099 &mut self,
1100 verbose: u32,
1101 quiet: bool,
1102 color: Option<&str>,
1103 frozen: bool,
1104 locked: bool,
1105 offline: bool,
1106 target_dir: &Option<PathBuf>,
1107 unstable_flags: &[String],
1108 cli_config: &[String],
1109 ) -> CargoResult<()> {
1110 for warning in self
1111 .unstable_flags
1112 .parse(unstable_flags, self.nightly_features_allowed)?
1113 {
1114 self.shell().warn(warning)?;
1115 }
1116 if !unstable_flags.is_empty() {
1117 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1120 }
1121 if !cli_config.is_empty() {
1122 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1123 self.merge_cli_args()?;
1124 }
1125
1126 self.load_unstable_flags_from_config()?;
1130 if self.unstable_flags.config_include {
1131 self.reload_rooted_at(self.cwd.clone())?;
1138 }
1139
1140 let term = self.get::<TermConfig>("term").unwrap_or_default();
1144
1145 let extra_verbose = verbose >= 2;
1147 let verbose = verbose != 0;
1148 let verbosity = match (verbose, quiet) {
1149 (true, true) => bail!("cannot set both --verbose and --quiet"),
1150 (true, false) => Verbosity::Verbose,
1151 (false, true) => Verbosity::Quiet,
1152 (false, false) => match (term.verbose, term.quiet) {
1153 (Some(true), Some(true)) => {
1154 bail!("cannot set both `term.verbose` and `term.quiet`")
1155 }
1156 (Some(true), _) => Verbosity::Verbose,
1157 (_, Some(true)) => Verbosity::Quiet,
1158 _ => Verbosity::Normal,
1159 },
1160 };
1161 self.shell().set_verbosity(verbosity);
1162 self.extra_verbose = extra_verbose;
1163
1164 let color = color.or_else(|| term.color.as_deref());
1165 self.shell().set_color_choice(color)?;
1166 if let Some(hyperlinks) = term.hyperlinks {
1167 self.shell().set_hyperlinks(hyperlinks)?;
1168 }
1169 if let Some(unicode) = term.unicode {
1170 self.shell().set_unicode(unicode)?;
1171 }
1172
1173 self.progress_config = term.progress.unwrap_or_default();
1174
1175 self.frozen = frozen;
1176 self.locked = locked;
1177 self.offline = offline
1178 || self
1179 .net_config()
1180 .ok()
1181 .and_then(|n| n.offline)
1182 .unwrap_or(false);
1183 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1184 self.target_dir = cli_target_dir;
1185
1186 Ok(())
1187 }
1188
1189 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1190 if self.nightly_features_allowed {
1193 self.unstable_flags = self
1194 .get::<Option<CliUnstable>>("unstable")?
1195 .unwrap_or_default();
1196 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1197 self.unstable_flags.parse(unstable_flags_cli, true)?;
1202 }
1203 }
1204
1205 Ok(())
1206 }
1207
1208 pub fn cli_unstable(&self) -> &CliUnstable {
1209 &self.unstable_flags
1210 }
1211
1212 pub fn extra_verbose(&self) -> bool {
1213 self.extra_verbose
1214 }
1215
1216 pub fn network_allowed(&self) -> bool {
1217 !self.offline_flag().is_some()
1218 }
1219
1220 pub fn offline_flag(&self) -> Option<&'static str> {
1221 if self.frozen {
1222 Some("--frozen")
1223 } else if self.offline {
1224 Some("--offline")
1225 } else {
1226 None
1227 }
1228 }
1229
1230 pub fn set_locked(&mut self, locked: bool) {
1231 self.locked = locked;
1232 }
1233
1234 pub fn lock_update_allowed(&self) -> bool {
1235 !self.locked_flag().is_some()
1236 }
1237
1238 pub fn locked_flag(&self) -> Option<&'static str> {
1239 if self.frozen {
1240 Some("--frozen")
1241 } else if self.locked {
1242 Some("--locked")
1243 } else {
1244 None
1245 }
1246 }
1247
1248 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1250 self.load_values_from(&self.cwd)
1251 }
1252
1253 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1257 let mut result = Vec::new();
1258 let mut seen = HashSet::new();
1259 let home = self.home_path.clone().into_path_unlocked();
1260 self.walk_tree(&self.cwd, &home, |path| {
1261 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1262 if self.cli_unstable().config_include {
1263 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1264 }
1265 result.push(cv);
1266 Ok(())
1267 })
1268 .context("could not load Cargo configuration")?;
1269 Ok(result)
1270 }
1271
1272 fn load_unmerged_include(
1276 &self,
1277 cv: &mut CV,
1278 seen: &mut HashSet<PathBuf>,
1279 output: &mut Vec<CV>,
1280 ) -> CargoResult<()> {
1281 let includes = self.include_paths(cv, false)?;
1282 for (path, abs_path, def) in includes {
1283 let mut cv = self
1284 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1285 .with_context(|| {
1286 format!("failed to load config include `{}` from `{}`", path, def)
1287 })?;
1288 self.load_unmerged_include(&mut cv, seen, output)?;
1289 output.push(cv);
1290 }
1291 Ok(())
1292 }
1293
1294 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1296 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1299 let home = self.home_path.clone().into_path_unlocked();
1300
1301 self.walk_tree(path, &home, |path| {
1302 let value = self.load_file(path)?;
1303 cfg.merge(value, false).with_context(|| {
1304 format!("failed to merge configuration at `{}`", path.display())
1305 })?;
1306 Ok(())
1307 })
1308 .context("could not load Cargo configuration")?;
1309
1310 match cfg {
1311 CV::Table(map, _) => Ok(map),
1312 _ => unreachable!(),
1313 }
1314 }
1315
1316 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1320 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1321 }
1322
1323 fn _load_file(
1333 &self,
1334 path: &Path,
1335 seen: &mut HashSet<PathBuf>,
1336 includes: bool,
1337 why_load: WhyLoad,
1338 ) -> CargoResult<ConfigValue> {
1339 if !seen.insert(path.to_path_buf()) {
1340 bail!(
1341 "config `include` cycle detected with path `{}`",
1342 path.display()
1343 );
1344 }
1345 tracing::debug!(?path, ?why_load, includes, "load config from file");
1346
1347 let contents = fs::read_to_string(path)
1348 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1349 let toml = parse_document(&contents, path, self).with_context(|| {
1350 format!("could not parse TOML configuration in `{}`", path.display())
1351 })?;
1352 let def = match why_load {
1353 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1354 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1355 };
1356 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1357 format!(
1358 "failed to load TOML configuration from `{}`",
1359 path.display()
1360 )
1361 })?;
1362 if includes {
1363 self.load_includes(value, seen, why_load)
1364 } else {
1365 Ok(value)
1366 }
1367 }
1368
1369 fn load_includes(
1376 &self,
1377 mut value: CV,
1378 seen: &mut HashSet<PathBuf>,
1379 why_load: WhyLoad,
1380 ) -> CargoResult<CV> {
1381 let includes = self.include_paths(&mut value, true)?;
1383 if !self.cli_unstable().config_include {
1385 return Ok(value);
1386 }
1387 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1389 for (path, abs_path, def) in includes {
1390 self._load_file(&abs_path, seen, true, why_load)
1391 .and_then(|include| root.merge(include, true))
1392 .with_context(|| {
1393 format!("failed to load config include `{}` from `{}`", path, def)
1394 })?;
1395 }
1396 root.merge(value, true)?;
1397 Ok(root)
1398 }
1399
1400 fn include_paths(
1402 &self,
1403 cv: &mut CV,
1404 remove: bool,
1405 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1406 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1407 let abs_path = match def {
1408 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1409 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1410 };
1411 (path.to_string(), abs_path, def.clone())
1412 };
1413 let CV::Table(table, _def) = cv else {
1414 unreachable!()
1415 };
1416 let owned;
1417 let include = if remove {
1418 owned = table.remove("include");
1419 owned.as_ref()
1420 } else {
1421 table.get("include")
1422 };
1423 let includes = match include {
1424 Some(CV::String(s, def)) => {
1425 vec![abs(s, def)]
1426 }
1427 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1428 Some(other) => bail!(
1429 "`include` expected a string or list, but found {} in `{}`",
1430 other.desc(),
1431 other.definition()
1432 ),
1433 None => {
1434 return Ok(Vec::new());
1435 }
1436 };
1437
1438 for (path, abs_path, def) in &includes {
1439 if abs_path.extension() != Some(OsStr::new("toml")) {
1440 bail!(
1441 "expected a config include path ending with `.toml`, \
1442 but found `{path}` from `{def}`",
1443 )
1444 }
1445 }
1446
1447 Ok(includes)
1448 }
1449
1450 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1452 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1453 let Some(cli_args) = &self.cli_config else {
1454 return Ok(loaded_args);
1455 };
1456 let mut seen = HashSet::new();
1457 for arg in cli_args {
1458 let arg_as_path = self.cwd.join(arg);
1459 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1460 let str_path = arg_as_path
1462 .to_str()
1463 .ok_or_else(|| {
1464 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1465 })?
1466 .to_string();
1467 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1468 .with_context(|| format!("failed to load config from `{}`", str_path))?
1469 } else {
1470 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1476 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1477 })?;
1478 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1479 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1480 }
1481 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1482 non_empty(d.prefix()) || non_empty(d.suffix())
1483 }
1484 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1485 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1486 }
1487 let ok = {
1488 let mut got_to_value = false;
1489 let mut table = doc.as_table();
1490 let mut is_root = true;
1491 while table.is_dotted() || is_root {
1492 is_root = false;
1493 if table.len() != 1 {
1494 break;
1495 }
1496 let (k, n) = table.iter().next().expect("len() == 1 above");
1497 match n {
1498 Item::Table(nt) => {
1499 if table.key(k).map_or(false, non_empty_key_decor)
1500 || non_empty_decor(nt.decor())
1501 {
1502 bail!(
1503 "--config argument `{arg}` \
1504 includes non-whitespace decoration"
1505 )
1506 }
1507 table = nt;
1508 }
1509 Item::Value(v) if v.is_inline_table() => {
1510 bail!(
1511 "--config argument `{arg}` \
1512 sets a value to an inline table, which is not accepted"
1513 );
1514 }
1515 Item::Value(v) => {
1516 if table
1517 .key(k)
1518 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1519 || non_empty_decor(v.decor())
1520 {
1521 bail!(
1522 "--config argument `{arg}` \
1523 includes non-whitespace decoration"
1524 )
1525 }
1526 got_to_value = true;
1527 break;
1528 }
1529 Item::ArrayOfTables(_) => {
1530 bail!(
1531 "--config argument `{arg}` \
1532 sets a value to an array of tables, which is not accepted"
1533 );
1534 }
1535
1536 Item::None => {
1537 bail!("--config argument `{arg}` doesn't provide a value")
1538 }
1539 }
1540 }
1541 got_to_value
1542 };
1543 if !ok {
1544 bail!(
1545 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1546 );
1547 }
1548
1549 let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1550 .with_context(|| {
1551 format!("failed to parse value from --config argument `{arg}`")
1552 })?;
1553
1554 if toml_v
1555 .get("registry")
1556 .and_then(|v| v.as_table())
1557 .and_then(|t| t.get("token"))
1558 .is_some()
1559 {
1560 bail!("registry.token cannot be set through --config for security reasons");
1561 } else if let Some((k, _)) = toml_v
1562 .get("registries")
1563 .and_then(|v| v.as_table())
1564 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1565 {
1566 bail!(
1567 "registries.{}.token cannot be set through --config for security reasons",
1568 k
1569 );
1570 }
1571
1572 if toml_v
1573 .get("registry")
1574 .and_then(|v| v.as_table())
1575 .and_then(|t| t.get("secret-key"))
1576 .is_some()
1577 {
1578 bail!(
1579 "registry.secret-key cannot be set through --config for security reasons"
1580 );
1581 } else if let Some((k, _)) = toml_v
1582 .get("registries")
1583 .and_then(|v| v.as_table())
1584 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1585 {
1586 bail!(
1587 "registries.{}.secret-key cannot be set through --config for security reasons",
1588 k
1589 );
1590 }
1591
1592 CV::from_toml(Definition::Cli(None), toml_v)
1593 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1594 };
1595 let tmp_table = self
1596 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1597 .context("failed to load --config include".to_string())?;
1598 loaded_args
1599 .merge(tmp_table, true)
1600 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1601 }
1602 Ok(loaded_args)
1603 }
1604
1605 fn merge_cli_args(&mut self) -> CargoResult<()> {
1607 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1608 unreachable!()
1609 };
1610 let values = self.values_mut()?;
1611 for (key, value) in loaded_map.into_iter() {
1612 match values.entry(key) {
1613 Vacant(entry) => {
1614 entry.insert(value);
1615 }
1616 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1617 format!(
1618 "failed to merge --config key `{}` into `{}`",
1619 entry.key(),
1620 entry.get().definition(),
1621 )
1622 })?,
1623 };
1624 }
1625 Ok(())
1626 }
1627
1628 fn get_file_path(
1634 &self,
1635 dir: &Path,
1636 filename_without_extension: &str,
1637 warn: bool,
1638 ) -> CargoResult<Option<PathBuf>> {
1639 let possible = dir.join(filename_without_extension);
1640 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1641
1642 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1643 if warn {
1644 if let Ok(possible_with_extension_handle) =
1645 same_file::Handle::from_path(&possible_with_extension)
1646 {
1647 if possible_handle != possible_with_extension_handle {
1653 self.shell().warn(format!(
1654 "both `{}` and `{}` exist. Using `{}`",
1655 possible.display(),
1656 possible_with_extension.display(),
1657 possible.display()
1658 ))?;
1659 }
1660 } else {
1661 self.shell().warn(format!(
1662 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1663 possible.display(),
1664 ))?;
1665 self.shell().note(
1666 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1667 )?;
1668 }
1669 }
1670
1671 Ok(Some(possible))
1672 } else if possible_with_extension.exists() {
1673 Ok(Some(possible_with_extension))
1674 } else {
1675 Ok(None)
1676 }
1677 }
1678
1679 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1680 where
1681 F: FnMut(&Path) -> CargoResult<()>,
1682 {
1683 let mut seen_dir = HashSet::new();
1684
1685 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1686 let config_root = current.join(".cargo");
1687 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1688 walk(&path)?;
1689 }
1690 seen_dir.insert(config_root);
1691 }
1692
1693 if !seen_dir.contains(home) {
1697 if let Some(path) = self.get_file_path(home, "config", true)? {
1698 walk(&path)?;
1699 }
1700 }
1701
1702 Ok(())
1703 }
1704
1705 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1707 RegistryName::new(registry)?;
1708 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1709 self.resolve_registry_index(&index).with_context(|| {
1710 format!(
1711 "invalid index URL for registry `{}` defined in {}",
1712 registry, index.definition
1713 )
1714 })
1715 } else {
1716 bail!(
1717 "registry index was not found in any configuration: `{}`",
1718 registry
1719 );
1720 }
1721 }
1722
1723 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1725 if self.get_string("registry.index")?.is_some() {
1726 bail!(
1727 "the `registry.index` config value is no longer supported\n\
1728 Use `[source]` replacement to alter the default index for crates.io."
1729 );
1730 }
1731 Ok(())
1732 }
1733
1734 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1735 let base = index
1737 .definition
1738 .root(self)
1739 .join("truncated-by-url_with_base");
1740 let _parsed = index.val.into_url()?;
1742 let url = index.val.into_url_with_base(Some(&*base))?;
1743 if url.password().is_some() {
1744 bail!("registry URLs may not contain passwords");
1745 }
1746 Ok(url)
1747 }
1748
1749 pub fn load_credentials(&self) -> CargoResult<()> {
1757 if self.credential_values.filled() {
1758 return Ok(());
1759 }
1760
1761 let home_path = self.home_path.clone().into_path_unlocked();
1762 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1763 return Ok(());
1764 };
1765
1766 let mut value = self.load_file(&credentials)?;
1767 {
1769 let CV::Table(ref mut value_map, ref def) = value else {
1770 unreachable!();
1771 };
1772
1773 if let Some(token) = value_map.remove("token") {
1774 if let Vacant(entry) = value_map.entry("registry".into()) {
1775 let map = HashMap::from([("token".into(), token)]);
1776 let table = CV::Table(map, def.clone());
1777 entry.insert(table);
1778 }
1779 }
1780 }
1781
1782 let mut credential_values = HashMap::new();
1783 if let CV::Table(map, _) = value {
1784 let base_map = self.values()?;
1785 for (k, v) in map {
1786 let entry = match base_map.get(&k) {
1787 Some(base_entry) => {
1788 let mut entry = base_entry.clone();
1789 entry.merge(v, true)?;
1790 entry
1791 }
1792 None => v,
1793 };
1794 credential_values.insert(k, entry);
1795 }
1796 }
1797 self.credential_values
1798 .fill(credential_values)
1799 .expect("was not filled at beginning of the function");
1800 Ok(())
1801 }
1802
1803 fn maybe_get_tool(
1806 &self,
1807 tool: &str,
1808 from_config: &Option<ConfigRelativePath>,
1809 ) -> Option<PathBuf> {
1810 let var = tool.to_uppercase();
1811
1812 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1813 Some(tool_path) => {
1814 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1815 let path = if maybe_relative {
1816 self.cwd.join(tool_path)
1817 } else {
1818 PathBuf::from(tool_path)
1819 };
1820 Some(path)
1821 }
1822
1823 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1824 }
1825 }
1826
1827 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1838 let tool_str = tool.as_str();
1839 self.maybe_get_tool(tool_str, from_config)
1840 .or_else(|| {
1841 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1855 if toolchain.to_str()?.contains(&['/', '\\']) {
1858 return None;
1859 }
1860 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1863 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1864 let tool_meta = tool_resolved.metadata().ok()?;
1865 let rustup_meta = rustup_resolved.metadata().ok()?;
1866 if tool_meta.len() != rustup_meta.len() {
1871 return None;
1872 }
1873 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1875 let toolchain_exe = home::rustup_home()
1876 .ok()?
1877 .join("toolchains")
1878 .join(&toolchain)
1879 .join("bin")
1880 .join(&tool_exe);
1881 toolchain_exe.exists().then_some(toolchain_exe)
1882 })
1883 .unwrap_or_else(|| PathBuf::from(tool_str))
1884 }
1885
1886 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1887 self.jobserver.as_ref()
1888 }
1889
1890 pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1891 let http = self
1892 .easy
1893 .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
1894 {
1895 let mut http = http.borrow_mut();
1896 http.reset();
1897 let timeout = configure_http_handle(self, &mut http)?;
1898 timeout.configure(&mut http)?;
1899 }
1900 Ok(http)
1901 }
1902
1903 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1904 self.http_config.try_borrow_with(|| {
1905 let mut http = self.get::<CargoHttpConfig>("http")?;
1906 let curl_v = curl::Version::get();
1907 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1908 Ok(http)
1909 })
1910 }
1911
1912 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1913 self.future_incompat_config
1914 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1915 }
1916
1917 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1918 self.net_config
1919 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1920 }
1921
1922 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1923 self.build_config
1924 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1925 }
1926
1927 pub fn progress_config(&self) -> &ProgressConfig {
1928 &self.progress_config
1929 }
1930
1931 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1934 let env_config = self.env_config.try_borrow_with(|| {
1935 CargoResult::Ok(Arc::new({
1936 let env_config = self.get::<EnvConfig>("env")?;
1937 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1953 if env_config.contains_key(*disallowed) {
1954 bail!(
1955 "setting the `{disallowed}` environment variable is not supported \
1956 in the `[env]` configuration table"
1957 );
1958 }
1959 }
1960 env_config
1961 .into_iter()
1962 .filter_map(|(k, v)| {
1963 if v.is_force() || self.get_env_os(&k).is_none() {
1964 Some((k, v.resolve(self).to_os_string()))
1965 } else {
1966 None
1967 }
1968 })
1969 .collect()
1970 }))
1971 })?;
1972
1973 Ok(env_config)
1974 }
1975
1976 pub fn validate_term_config(&self) -> CargoResult<()> {
1982 drop(self.get::<TermConfig>("term")?);
1983 Ok(())
1984 }
1985
1986 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1990 self.target_cfgs
1991 .try_borrow_with(|| target::load_target_cfgs(self))
1992 }
1993
1994 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1995 self.doc_extern_map
1999 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
2000 }
2001
2002 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2004 target::get_target_applies_to_host(self)
2005 }
2006
2007 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2009 target::load_host_triple(self, target)
2010 }
2011
2012 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2014 target::load_target_triple(self, target)
2015 }
2016
2017 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2022 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2023 self.check_registry_index_not_set()?;
2024 let url = CRATES_IO_INDEX.into_url().unwrap();
2025 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2026 })?;
2027 Ok(*source_id)
2028 }
2029
2030 pub fn creation_time(&self) -> Instant {
2031 self.creation_time
2032 }
2033
2034 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2049 let d = Deserializer {
2050 gctx: self,
2051 key: ConfigKey::from_str(key),
2052 env_prefix_ok: true,
2053 };
2054 T::deserialize(d).map_err(|e| e.into())
2055 }
2056
2057 #[track_caller]
2063 #[tracing::instrument(skip_all)]
2064 pub fn assert_package_cache_locked<'a>(
2065 &self,
2066 mode: CacheLockMode,
2067 f: &'a Filesystem,
2068 ) -> &'a Path {
2069 let ret = f.as_path_unlocked();
2070 assert!(
2071 self.package_cache_lock.is_locked(mode),
2072 "package cache lock is not currently held, Cargo forgot to call \
2073 `acquire_package_cache_lock` before we got to this stack frame",
2074 );
2075 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2076 ret
2077 }
2078
2079 #[tracing::instrument(skip_all)]
2085 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2086 self.package_cache_lock.lock(self, mode)
2087 }
2088
2089 #[tracing::instrument(skip_all)]
2095 pub fn try_acquire_package_cache_lock(
2096 &self,
2097 mode: CacheLockMode,
2098 ) -> CargoResult<Option<CacheLock<'_>>> {
2099 self.package_cache_lock.try_lock(self, mode)
2100 }
2101
2102 pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
2107 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2108 Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
2109 })?;
2110 Ok(tracker.borrow_mut())
2111 }
2112
2113 pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
2115 let deferred = self.deferred_global_last_use.try_borrow_with(|| {
2116 Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
2117 })?;
2118 Ok(deferred.borrow_mut())
2119 }
2120
2121 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2123 if self.unstable_flags.warnings {
2124 Ok(self.build_config()?.warnings.unwrap_or_default())
2125 } else {
2126 Ok(WarningHandling::default())
2127 }
2128 }
2129}
2130
2131#[derive(Debug)]
2133pub struct ConfigError {
2134 error: anyhow::Error,
2135 definition: Option<Definition>,
2136}
2137
2138impl ConfigError {
2139 fn new(message: String, definition: Definition) -> ConfigError {
2140 ConfigError {
2141 error: anyhow::Error::msg(message),
2142 definition: Some(definition),
2143 }
2144 }
2145
2146 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2147 ConfigError {
2148 error: anyhow!(
2149 "`{}` expected {}, but found a {}",
2150 key,
2151 expected,
2152 found.desc()
2153 ),
2154 definition: Some(found.definition().clone()),
2155 }
2156 }
2157
2158 fn is_missing_field(&self) -> bool {
2159 self.error.downcast_ref::<MissingFieldError>().is_some()
2160 }
2161
2162 fn missing(key: &ConfigKey) -> ConfigError {
2163 ConfigError {
2164 error: anyhow!("missing config key `{}`", key),
2165 definition: None,
2166 }
2167 }
2168
2169 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2170 ConfigError {
2171 error: anyhow::Error::from(self)
2172 .context(format!("could not load config key `{}`", key)),
2173 definition: definition,
2174 }
2175 }
2176}
2177
2178impl std::error::Error for ConfigError {
2179 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2180 self.error.source()
2181 }
2182}
2183
2184impl fmt::Display for ConfigError {
2185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2186 if let Some(definition) = &self.definition {
2187 write!(f, "error in {}: {}", definition, self.error)
2188 } else {
2189 self.error.fmt(f)
2190 }
2191 }
2192}
2193
2194#[derive(Debug)]
2195struct MissingFieldError(String);
2196
2197impl fmt::Display for MissingFieldError {
2198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2199 write!(f, "missing field `{}`", self.0)
2200 }
2201}
2202
2203impl std::error::Error for MissingFieldError {}
2204
2205impl serde::de::Error for ConfigError {
2206 fn custom<T: fmt::Display>(msg: T) -> Self {
2207 ConfigError {
2208 error: anyhow::Error::msg(msg.to_string()),
2209 definition: None,
2210 }
2211 }
2212
2213 fn missing_field(field: &'static str) -> Self {
2214 ConfigError {
2215 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2216 definition: None,
2217 }
2218 }
2219}
2220
2221impl From<anyhow::Error> for ConfigError {
2222 fn from(error: anyhow::Error) -> Self {
2223 ConfigError {
2224 error,
2225 definition: None,
2226 }
2227 }
2228}
2229
2230#[derive(Eq, PartialEq, Clone)]
2231pub enum ConfigValue {
2232 Integer(i64, Definition),
2233 String(String, Definition),
2234 List(Vec<(String, Definition)>, Definition),
2235 Table(HashMap<String, ConfigValue>, Definition),
2236 Boolean(bool, Definition),
2237}
2238
2239impl fmt::Debug for ConfigValue {
2240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2241 match self {
2242 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2243 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2244 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2245 CV::List(list, def) => {
2246 write!(f, "[")?;
2247 for (i, (s, def)) in list.iter().enumerate() {
2248 if i > 0 {
2249 write!(f, ", ")?;
2250 }
2251 write!(f, "{} (from {})", s, def)?;
2252 }
2253 write!(f, "] (from {})", def)
2254 }
2255 CV::Table(table, _) => write!(f, "{:?}", table),
2256 }
2257 }
2258}
2259
2260impl ConfigValue {
2261 fn get_definition(&self) -> &Definition {
2262 match self {
2263 CV::Boolean(_, def)
2264 | CV::Integer(_, def)
2265 | CV::String(_, def)
2266 | CV::List(_, def)
2267 | CV::Table(_, def) => def,
2268 }
2269 }
2270
2271 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2272 match toml {
2273 toml::Value::String(val) => Ok(CV::String(val, def)),
2274 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2275 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2276 toml::Value::Array(val) => Ok(CV::List(
2277 val.into_iter()
2278 .map(|toml| match toml {
2279 toml::Value::String(val) => Ok((val, def.clone())),
2280 v => bail!("expected string but found {} in list", v.type_str()),
2281 })
2282 .collect::<CargoResult<_>>()?,
2283 def,
2284 )),
2285 toml::Value::Table(val) => Ok(CV::Table(
2286 val.into_iter()
2287 .map(|(key, value)| {
2288 let value = CV::from_toml(def.clone(), value)
2289 .with_context(|| format!("failed to parse key `{}`", key))?;
2290 Ok((key, value))
2291 })
2292 .collect::<CargoResult<_>>()?,
2293 def,
2294 )),
2295 v => bail!(
2296 "found TOML configuration value of unknown type `{}`",
2297 v.type_str()
2298 ),
2299 }
2300 }
2301
2302 fn into_toml(self) -> toml::Value {
2303 match self {
2304 CV::Boolean(s, _) => toml::Value::Boolean(s),
2305 CV::String(s, _) => toml::Value::String(s),
2306 CV::Integer(i, _) => toml::Value::Integer(i),
2307 CV::List(l, _) => {
2308 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2309 }
2310 CV::Table(l, _) => {
2311 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2312 }
2313 }
2314 }
2315
2316 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2325 self.merge_helper(from, force, &mut ConfigKey::new())
2326 }
2327
2328 fn merge_helper(
2329 &mut self,
2330 from: ConfigValue,
2331 force: bool,
2332 parts: &mut ConfigKey,
2333 ) -> CargoResult<()> {
2334 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2335 match (self, from) {
2336 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2337 if is_nonmergable_list(&parts) {
2338 if force || is_higher_priority {
2340 mem::swap(new, old);
2341 }
2342 } else {
2343 if force {
2345 old.append(new);
2346 } else {
2347 new.append(old);
2348 mem::swap(new, old);
2349 }
2350 }
2351 old.sort_by(|a, b| a.1.cmp(&b.1));
2352 }
2353 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2354 for (key, value) in mem::take(new) {
2355 match old.entry(key.clone()) {
2356 Occupied(mut entry) => {
2357 let new_def = value.definition().clone();
2358 let entry = entry.get_mut();
2359 parts.push(&key);
2360 entry.merge_helper(value, force, parts).with_context(|| {
2361 format!(
2362 "failed to merge key `{}` between \
2363 {} and {}",
2364 key,
2365 entry.definition(),
2366 new_def,
2367 )
2368 })?;
2369 }
2370 Vacant(entry) => {
2371 entry.insert(value);
2372 }
2373 };
2374 }
2375 }
2376 (expected @ &mut CV::List(_, _), found)
2378 | (expected @ &mut CV::Table(_, _), found)
2379 | (expected, found @ CV::List(_, _))
2380 | (expected, found @ CV::Table(_, _)) => {
2381 return Err(anyhow!(
2382 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2383 found.definition(),
2384 expected.definition(),
2385 expected.desc(),
2386 found.desc()
2387 ));
2388 }
2389 (old, mut new) => {
2390 if force || is_higher_priority {
2391 mem::swap(old, &mut new);
2392 }
2393 }
2394 }
2395
2396 Ok(())
2397 }
2398
2399 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2400 match self {
2401 CV::Integer(i, def) => Ok((*i, def)),
2402 _ => self.expected("integer", key),
2403 }
2404 }
2405
2406 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2407 match self {
2408 CV::String(s, def) => Ok((s, def)),
2409 _ => self.expected("string", key),
2410 }
2411 }
2412
2413 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2414 match self {
2415 CV::Table(table, def) => Ok((table, def)),
2416 _ => self.expected("table", key),
2417 }
2418 }
2419
2420 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2421 match self {
2422 CV::List(list, _) => Ok(list),
2423 _ => self.expected("list", key),
2424 }
2425 }
2426
2427 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2428 match self {
2429 CV::Boolean(b, def) => Ok((*b, def)),
2430 _ => self.expected("bool", key),
2431 }
2432 }
2433
2434 pub fn desc(&self) -> &'static str {
2435 match *self {
2436 CV::Table(..) => "table",
2437 CV::List(..) => "array",
2438 CV::String(..) => "string",
2439 CV::Boolean(..) => "boolean",
2440 CV::Integer(..) => "integer",
2441 }
2442 }
2443
2444 pub fn definition(&self) -> &Definition {
2445 match self {
2446 CV::Boolean(_, def)
2447 | CV::Integer(_, def)
2448 | CV::String(_, def)
2449 | CV::List(_, def)
2450 | CV::Table(_, def) => def,
2451 }
2452 }
2453
2454 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2455 bail!(
2456 "expected a {}, but found a {} for `{}` in {}",
2457 wanted,
2458 self.desc(),
2459 key,
2460 self.definition()
2461 )
2462 }
2463}
2464
2465fn is_nonmergable_list(key: &ConfigKey) -> bool {
2468 key.matches("registry.credential-provider")
2469 || key.matches("registries.*.credential-provider")
2470 || key.matches("target.*.runner")
2471 || key.matches("host.runner")
2472 || key.matches("credential-alias.*")
2473 || key.matches("doc.browser")
2474}
2475
2476pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2477 ::home::cargo_home_with_cwd(cwd).ok()
2478}
2479
2480pub fn save_credentials(
2481 gctx: &GlobalContext,
2482 token: Option<RegistryCredentialConfig>,
2483 registry: &SourceId,
2484) -> CargoResult<()> {
2485 let registry = if registry.is_crates_io() {
2486 None
2487 } else {
2488 let name = registry
2489 .alt_registry_key()
2490 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2491 Some(name)
2492 };
2493
2494 let home_path = gctx.home_path.clone().into_path_unlocked();
2498 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2499 Some(path) => match path.file_name() {
2500 Some(filename) => Path::new(filename).to_owned(),
2501 None => Path::new("credentials.toml").to_owned(),
2502 },
2503 None => Path::new("credentials.toml").to_owned(),
2504 };
2505
2506 let mut file = {
2507 gctx.home_path.create_dir()?;
2508 gctx.home_path
2509 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2510 };
2511
2512 let mut contents = String::new();
2513 file.read_to_string(&mut contents).with_context(|| {
2514 format!(
2515 "failed to read configuration file `{}`",
2516 file.path().display()
2517 )
2518 })?;
2519
2520 let mut toml = parse_document(&contents, file.path(), gctx)?;
2521
2522 if let Some(token) = toml.remove("token") {
2524 let map = HashMap::from([("token".to_string(), token)]);
2525 toml.insert("registry".into(), map.into());
2526 }
2527
2528 if let Some(token) = token {
2529 let path_def = Definition::Path(file.path().to_path_buf());
2532 let (key, mut value) = match token {
2533 RegistryCredentialConfig::Token(token) => {
2534 let key = "token".to_string();
2537 let value = ConfigValue::String(token.expose(), path_def.clone());
2538 let map = HashMap::from([(key, value)]);
2539 let table = CV::Table(map, path_def.clone());
2540
2541 if let Some(registry) = registry {
2542 let map = HashMap::from([(registry.to_string(), table)]);
2543 ("registries".into(), CV::Table(map, path_def.clone()))
2544 } else {
2545 ("registry".into(), table)
2546 }
2547 }
2548 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2549 let key = "secret-key".to_string();
2552 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2553 let mut map = HashMap::from([(key, value)]);
2554 if let Some(key_subject) = key_subject {
2555 let key = "secret-key-subject".to_string();
2556 let value = ConfigValue::String(key_subject, path_def.clone());
2557 map.insert(key, value);
2558 }
2559 let table = CV::Table(map, path_def.clone());
2560
2561 if let Some(registry) = registry {
2562 let map = HashMap::from([(registry.to_string(), table)]);
2563 ("registries".into(), CV::Table(map, path_def.clone()))
2564 } else {
2565 ("registry".into(), table)
2566 }
2567 }
2568 _ => unreachable!(),
2569 };
2570
2571 if registry.is_some() {
2572 if let Some(table) = toml.remove("registries") {
2573 let v = CV::from_toml(path_def, table)?;
2574 value.merge(v, false)?;
2575 }
2576 }
2577 toml.insert(key, value.into_toml());
2578 } else {
2579 if let Some(registry) = registry {
2581 if let Some(registries) = toml.get_mut("registries") {
2582 if let Some(reg) = registries.get_mut(registry) {
2583 let rtable = reg.as_table_mut().ok_or_else(|| {
2584 format_err!("expected `[registries.{}]` to be a table", registry)
2585 })?;
2586 rtable.remove("token");
2587 rtable.remove("secret-key");
2588 rtable.remove("secret-key-subject");
2589 }
2590 }
2591 } else if let Some(registry) = toml.get_mut("registry") {
2592 let reg_table = registry
2593 .as_table_mut()
2594 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2595 reg_table.remove("token");
2596 reg_table.remove("secret-key");
2597 reg_table.remove("secret-key-subject");
2598 }
2599 }
2600
2601 let contents = toml.to_string();
2602 file.seek(SeekFrom::Start(0))?;
2603 file.write_all(contents.as_bytes())
2604 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2605 file.file().set_len(contents.len() as u64)?;
2606 set_permissions(file.file(), 0o600)
2607 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2608
2609 return Ok(());
2610
2611 #[cfg(unix)]
2612 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2613 use std::os::unix::fs::PermissionsExt;
2614
2615 let mut perms = file.metadata()?.permissions();
2616 perms.set_mode(mode);
2617 file.set_permissions(perms)?;
2618 Ok(())
2619 }
2620
2621 #[cfg(not(unix))]
2622 #[allow(unused)]
2623 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2624 Ok(())
2625 }
2626}
2627
2628#[derive(Debug, Default, Deserialize, PartialEq)]
2629#[serde(rename_all = "kebab-case")]
2630pub struct CargoHttpConfig {
2631 pub proxy: Option<String>,
2632 pub low_speed_limit: Option<u32>,
2633 pub timeout: Option<u64>,
2634 pub cainfo: Option<ConfigRelativePath>,
2635 pub check_revoke: Option<bool>,
2636 pub user_agent: Option<String>,
2637 pub debug: Option<bool>,
2638 pub multiplexing: Option<bool>,
2639 pub ssl_version: Option<SslVersionConfig>,
2640}
2641
2642#[derive(Debug, Default, Deserialize, PartialEq)]
2643#[serde(rename_all = "kebab-case")]
2644pub struct CargoFutureIncompatConfig {
2645 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2646}
2647
2648#[derive(Debug, Default, Deserialize, PartialEq)]
2649#[serde(rename_all = "kebab-case")]
2650pub enum CargoFutureIncompatFrequencyConfig {
2651 #[default]
2652 Always,
2653 Never,
2654}
2655
2656impl CargoFutureIncompatConfig {
2657 pub fn should_display_message(&self) -> bool {
2658 use CargoFutureIncompatFrequencyConfig::*;
2659
2660 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2661 match frequency {
2662 Always => true,
2663 Never => false,
2664 }
2665 }
2666}
2667
2668#[derive(Clone, Debug, PartialEq)]
2682pub enum SslVersionConfig {
2683 Single(String),
2684 Range(SslVersionConfigRange),
2685}
2686
2687impl<'de> Deserialize<'de> for SslVersionConfig {
2688 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2689 where
2690 D: serde::Deserializer<'de>,
2691 {
2692 UntaggedEnumVisitor::new()
2693 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2694 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2695 .deserialize(deserializer)
2696 }
2697}
2698
2699#[derive(Clone, Debug, Deserialize, PartialEq)]
2700#[serde(rename_all = "kebab-case")]
2701pub struct SslVersionConfigRange {
2702 pub min: Option<String>,
2703 pub max: Option<String>,
2704}
2705
2706#[derive(Debug, Deserialize)]
2707#[serde(rename_all = "kebab-case")]
2708pub struct CargoNetConfig {
2709 pub retry: Option<u32>,
2710 pub offline: Option<bool>,
2711 pub git_fetch_with_cli: Option<bool>,
2712 pub ssh: Option<CargoSshConfig>,
2713}
2714
2715#[derive(Debug, Deserialize)]
2716#[serde(rename_all = "kebab-case")]
2717pub struct CargoSshConfig {
2718 pub known_hosts: Option<Vec<Value<String>>>,
2719}
2720
2721#[derive(Debug, Clone)]
2734pub enum JobsConfig {
2735 Integer(i32),
2736 String(String),
2737}
2738
2739impl<'de> Deserialize<'de> for JobsConfig {
2740 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2741 where
2742 D: serde::Deserializer<'de>,
2743 {
2744 UntaggedEnumVisitor::new()
2745 .i32(|int| Ok(JobsConfig::Integer(int)))
2746 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2747 .deserialize(deserializer)
2748 }
2749}
2750
2751#[derive(Debug, Deserialize)]
2752#[serde(rename_all = "kebab-case")]
2753pub struct CargoBuildConfig {
2754 pub pipelining: Option<bool>,
2756 pub dep_info_basedir: Option<ConfigRelativePath>,
2757 pub target_dir: Option<ConfigRelativePath>,
2758 pub build_dir: Option<ConfigRelativePath>,
2759 pub incremental: Option<bool>,
2760 pub target: Option<BuildTargetConfig>,
2761 pub jobs: Option<JobsConfig>,
2762 pub rustflags: Option<StringList>,
2763 pub rustdocflags: Option<StringList>,
2764 pub rustc_wrapper: Option<ConfigRelativePath>,
2765 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2766 pub rustc: Option<ConfigRelativePath>,
2767 pub rustdoc: Option<ConfigRelativePath>,
2768 pub out_dir: Option<ConfigRelativePath>,
2770 pub artifact_dir: Option<ConfigRelativePath>,
2771 pub warnings: Option<WarningHandling>,
2772 pub sbom: Option<bool>,
2774}
2775
2776#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2778#[serde(rename_all = "kebab-case")]
2779pub enum WarningHandling {
2780 #[default]
2781 Warn,
2783 Allow,
2785 Deny,
2787}
2788
2789#[derive(Debug, Deserialize)]
2799#[serde(transparent)]
2800pub struct BuildTargetConfig {
2801 inner: Value<BuildTargetConfigInner>,
2802}
2803
2804#[derive(Debug)]
2805enum BuildTargetConfigInner {
2806 One(String),
2807 Many(Vec<String>),
2808}
2809
2810impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2811 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2812 where
2813 D: serde::Deserializer<'de>,
2814 {
2815 UntaggedEnumVisitor::new()
2816 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2817 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2818 .deserialize(deserializer)
2819 }
2820}
2821
2822impl BuildTargetConfig {
2823 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2825 let map = |s: &String| {
2826 if s.ends_with(".json") {
2827 self.inner
2830 .definition
2831 .root(gctx)
2832 .join(s)
2833 .to_str()
2834 .expect("must be utf-8 in toml")
2835 .to_string()
2836 } else {
2837 s.to_string()
2839 }
2840 };
2841 let values = match &self.inner.val {
2842 BuildTargetConfigInner::One(s) => vec![map(s)],
2843 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2844 };
2845 Ok(values)
2846 }
2847}
2848
2849#[derive(Debug, Deserialize)]
2850#[serde(rename_all = "kebab-case")]
2851pub struct CargoResolverConfig {
2852 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2853 pub feature_unification: Option<FeatureUnification>,
2854}
2855
2856#[derive(Debug, Deserialize, PartialEq, Eq)]
2857#[serde(rename_all = "kebab-case")]
2858pub enum IncompatibleRustVersions {
2859 Allow,
2860 Fallback,
2861}
2862
2863#[derive(Copy, Clone, Debug, Deserialize)]
2864#[serde(rename_all = "kebab-case")]
2865pub enum FeatureUnification {
2866 Selected,
2867 Workspace,
2868}
2869
2870#[derive(Deserialize, Default)]
2871#[serde(rename_all = "kebab-case")]
2872pub struct TermConfig {
2873 pub verbose: Option<bool>,
2874 pub quiet: Option<bool>,
2875 pub color: Option<String>,
2876 pub hyperlinks: Option<bool>,
2877 pub unicode: Option<bool>,
2878 #[serde(default)]
2879 #[serde(deserialize_with = "progress_or_string")]
2880 pub progress: Option<ProgressConfig>,
2881}
2882
2883#[derive(Debug, Default, Deserialize)]
2884#[serde(rename_all = "kebab-case")]
2885pub struct ProgressConfig {
2886 #[serde(default)]
2887 pub when: ProgressWhen,
2888 pub width: Option<usize>,
2889 pub term_integration: Option<bool>,
2891}
2892
2893#[derive(Debug, Default, Deserialize)]
2894#[serde(rename_all = "kebab-case")]
2895pub enum ProgressWhen {
2896 #[default]
2897 Auto,
2898 Never,
2899 Always,
2900}
2901
2902fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2903where
2904 D: serde::de::Deserializer<'de>,
2905{
2906 struct ProgressVisitor;
2907
2908 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2909 type Value = Option<ProgressConfig>;
2910
2911 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2912 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2913 }
2914
2915 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2916 where
2917 E: serde::de::Error,
2918 {
2919 match s {
2920 "auto" => Ok(Some(ProgressConfig {
2921 when: ProgressWhen::Auto,
2922 width: None,
2923 term_integration: None,
2924 })),
2925 "never" => Ok(Some(ProgressConfig {
2926 when: ProgressWhen::Never,
2927 width: None,
2928 term_integration: None,
2929 })),
2930 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2931 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2932 }
2933 }
2934
2935 fn visit_none<E>(self) -> Result<Self::Value, E>
2936 where
2937 E: serde::de::Error,
2938 {
2939 Ok(None)
2940 }
2941
2942 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2943 where
2944 D: serde::de::Deserializer<'de>,
2945 {
2946 let pc = ProgressConfig::deserialize(deserializer)?;
2947 if let ProgressConfig {
2948 when: ProgressWhen::Always,
2949 width: None,
2950 ..
2951 } = pc
2952 {
2953 return Err(serde::de::Error::custom(
2954 "\"always\" progress requires a `width` key",
2955 ));
2956 }
2957 Ok(Some(pc))
2958 }
2959 }
2960
2961 deserializer.deserialize_option(ProgressVisitor)
2962}
2963
2964#[derive(Debug)]
2965enum EnvConfigValueInner {
2966 Simple(String),
2967 WithOptions {
2968 value: String,
2969 force: bool,
2970 relative: bool,
2971 },
2972}
2973
2974impl<'de> Deserialize<'de> for EnvConfigValueInner {
2975 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2976 where
2977 D: serde::Deserializer<'de>,
2978 {
2979 #[derive(Deserialize)]
2980 struct WithOptions {
2981 value: String,
2982 #[serde(default)]
2983 force: bool,
2984 #[serde(default)]
2985 relative: bool,
2986 }
2987
2988 UntaggedEnumVisitor::new()
2989 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2990 .map(|map| {
2991 let with_options: WithOptions = map.deserialize()?;
2992 Ok(EnvConfigValueInner::WithOptions {
2993 value: with_options.value,
2994 force: with_options.force,
2995 relative: with_options.relative,
2996 })
2997 })
2998 .deserialize(deserializer)
2999 }
3000}
3001
3002#[derive(Debug, Deserialize)]
3003#[serde(transparent)]
3004pub struct EnvConfigValue {
3005 inner: Value<EnvConfigValueInner>,
3006}
3007
3008impl EnvConfigValue {
3009 pub fn is_force(&self) -> bool {
3010 match self.inner.val {
3011 EnvConfigValueInner::Simple(_) => false,
3012 EnvConfigValueInner::WithOptions { force, .. } => force,
3013 }
3014 }
3015
3016 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
3017 match self.inner.val {
3018 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
3019 EnvConfigValueInner::WithOptions {
3020 ref value,
3021 relative,
3022 ..
3023 } => {
3024 if relative {
3025 let p = self.inner.definition.root(gctx).join(&value);
3026 Cow::Owned(p.into_os_string())
3027 } else {
3028 Cow::Borrowed(OsStr::new(value.as_str()))
3029 }
3030 }
3031 }
3032 }
3033}
3034
3035pub type EnvConfig = HashMap<String, EnvConfigValue>;
3036
3037fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3038 toml.parse().map_err(Into::into)
3040}
3041
3042#[derive(Debug, Deserialize, Clone)]
3053pub struct StringList(Vec<String>);
3054
3055impl StringList {
3056 pub fn as_slice(&self) -> &[String] {
3057 &self.0
3058 }
3059}
3060
3061#[macro_export]
3062macro_rules! __shell_print {
3063 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3064 let mut shell = $config.shell();
3065 let out = shell.$which();
3066 drop(out.write_fmt(format_args!($($arg)*)));
3067 if $newline {
3068 drop(out.write_all(b"\n"));
3069 }
3070 });
3071}
3072
3073#[macro_export]
3074macro_rules! drop_println {
3075 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3076 ($config:expr, $($arg:tt)*) => (
3077 $crate::__shell_print!($config, out, true, $($arg)*)
3078 );
3079}
3080
3081#[macro_export]
3082macro_rules! drop_eprintln {
3083 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3084 ($config:expr, $($arg:tt)*) => (
3085 $crate::__shell_print!($config, err, true, $($arg)*)
3086 );
3087}
3088
3089#[macro_export]
3090macro_rules! drop_print {
3091 ($config:expr, $($arg:tt)*) => (
3092 $crate::__shell_print!($config, out, false, $($arg)*)
3093 );
3094}
3095
3096#[macro_export]
3097macro_rules! drop_eprint {
3098 ($config:expr, $($arg:tt)*) => (
3099 $crate::__shell_print!($config, err, false, $($arg)*)
3100 );
3101}
3102
3103enum Tool {
3104 Rustc,
3105 Rustdoc,
3106}
3107
3108impl Tool {
3109 fn as_str(&self) -> &str {
3110 match self {
3111 Tool::Rustc => "rustc",
3112 Tool::Rustdoc => "rustdoc",
3113 }
3114 }
3115}
3116
3117fn disables_multiplexing_for_bad_curl(
3127 curl_version: &str,
3128 http: &mut CargoHttpConfig,
3129 gctx: &GlobalContext,
3130) {
3131 use crate::util::network;
3132
3133 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3134 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3135 if bad_curl_versions
3136 .iter()
3137 .any(|v| curl_version.starts_with(v))
3138 {
3139 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3140 http.multiplexing = Some(false);
3141 }
3142 }
3143}
3144
3145#[cfg(test)]
3146mod tests {
3147 use super::disables_multiplexing_for_bad_curl;
3148 use super::CargoHttpConfig;
3149 use super::GlobalContext;
3150 use super::Shell;
3151
3152 #[test]
3153 fn disables_multiplexing() {
3154 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3155 gctx.set_search_stop_path(std::path::PathBuf::new());
3156 gctx.set_env(Default::default());
3157
3158 let mut http = CargoHttpConfig::default();
3159 http.proxy = Some("127.0.0.1:3128".into());
3160 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3161 assert_eq!(http.multiplexing, Some(false));
3162
3163 let cases = [
3164 (None, None, "7.87.0", None),
3165 (None, None, "7.88.0", None),
3166 (None, None, "7.88.1", None),
3167 (None, None, "8.0.0", None),
3168 (Some("".into()), None, "7.87.0", Some(false)),
3169 (Some("".into()), None, "7.88.0", Some(false)),
3170 (Some("".into()), None, "7.88.1", Some(false)),
3171 (Some("".into()), None, "8.0.0", None),
3172 (Some("".into()), Some(false), "7.87.0", Some(false)),
3173 (Some("".into()), Some(false), "7.88.0", Some(false)),
3174 (Some("".into()), Some(false), "7.88.1", Some(false)),
3175 (Some("".into()), Some(false), "8.0.0", Some(false)),
3176 ];
3177
3178 for (proxy, multiplexing, curl_v, result) in cases {
3179 let mut http = CargoHttpConfig {
3180 multiplexing,
3181 proxy,
3182 ..Default::default()
3183 };
3184 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3185 assert_eq!(http.multiplexing, result);
3186 }
3187 }
3188}