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::SeekFrom;
62use std::io::prelude::*;
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::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
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::{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 lazycell::LazyCell;
89use serde::Deserialize;
90use serde::de::IntoDeserializer as _;
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 let Some(val) = &self.build_config()?.build_dir {
654 let replacements = vec![
655 (
656 "{workspace-root}",
657 workspace_manifest_path
658 .parent()
659 .unwrap()
660 .to_str()
661 .context("workspace root was not valid utf-8")?
662 .to_string(),
663 ),
664 (
665 "{cargo-cache-home}",
666 self.home()
667 .as_path_unlocked()
668 .to_str()
669 .context("cargo home was not valid utf-8")?
670 .to_string(),
671 ),
672 ("{workspace-path-hash}", {
673 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
674 let hash = crate::util::hex::short_hash(&real_path);
675 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
676 }),
677 ];
678
679 let template_variables = replacements
680 .iter()
681 .map(|(key, _)| key[1..key.len() - 1].to_string())
682 .collect_vec();
683
684 let path = val
685 .resolve_templated_path(self, replacements)
686 .map_err(|e| match e {
687 path::ResolveTemplateError::UnexpectedVariable {
688 variable,
689 raw_template,
690 } => {
691 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
692 if suggestion == "" {
693 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
694 suggestion = format!("\n\nhelp: available template variables are {variables}");
695 }
696 anyhow!(
697 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
698 )
699 },
700 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
701 let (btype, literal) = match bracket_type {
702 path::BracketType::Opening => ("opening", "{"),
703 path::BracketType::Closing => ("closing", "}"),
704 };
705
706 anyhow!(
707 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
708 )
709 }
710 })?;
711
712 if val.raw_value().is_empty() {
714 bail!(
715 "the build directory is set to an empty string in {}",
716 val.value().definition
717 )
718 }
719
720 Ok(Some(Filesystem::new(path)))
721 } else {
722 return self.target_dir();
725 }
726 }
727
728 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
733 if let Some(vals) = self.credential_values.borrow() {
734 let val = self.get_cv_helper(key, vals)?;
735 if val.is_some() {
736 return Ok(val);
737 }
738 }
739 self.get_cv_helper(key, self.values()?)
740 }
741
742 fn get_cv_helper(
743 &self,
744 key: &ConfigKey,
745 vals: &HashMap<String, ConfigValue>,
746 ) -> CargoResult<Option<ConfigValue>> {
747 tracing::trace!("get cv {:?}", key);
748 if key.is_root() {
749 return Ok(Some(CV::Table(
752 vals.clone(),
753 Definition::Path(PathBuf::new()),
754 )));
755 }
756 let mut parts = key.parts().enumerate();
757 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
758 return Ok(None);
759 };
760 for (i, part) in parts {
761 match val {
762 CV::Table(map, _) => {
763 val = match map.get(part) {
764 Some(val) => val,
765 None => return Ok(None),
766 }
767 }
768 CV::Integer(_, def)
769 | CV::String(_, def)
770 | CV::List(_, def)
771 | CV::Boolean(_, def) => {
772 let mut key_so_far = ConfigKey::new();
773 for part in key.parts().take(i) {
774 key_so_far.push(part);
775 }
776 bail!(
777 "expected table for configuration key `{}`, \
778 but found {} in {}",
779 key_so_far,
780 val.desc(),
781 def
782 )
783 }
784 }
785 }
786 Ok(Some(val.clone()))
787 }
788
789 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
791 let cv = self.get_cv(key)?;
794 if key.is_root() {
795 return Ok(cv);
797 }
798 let env = self.env.get_str(key.as_env_key());
799 let env_def = Definition::Environment(key.as_env_key().to_string());
800 let use_env = match (&cv, env) {
801 (Some(CV::List(..)), Some(_)) => true,
803 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
804 (None, Some(_)) => true,
805 _ => false,
806 };
807
808 if !use_env {
809 return Ok(cv);
810 }
811
812 let env = env.unwrap();
816 if env == "true" {
817 Ok(Some(CV::Boolean(true, env_def)))
818 } else if env == "false" {
819 Ok(Some(CV::Boolean(false, env_def)))
820 } else if let Ok(i) = env.parse::<i64>() {
821 Ok(Some(CV::Integer(i, env_def)))
822 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
823 match cv {
824 Some(CV::List(mut cv_list, cv_def)) => {
825 self.get_env_list(key, &mut cv_list)?;
827 Ok(Some(CV::List(cv_list, cv_def)))
828 }
829 Some(cv) => {
830 bail!(
834 "unable to merge array env for config `{}`\n\
835 file: {:?}\n\
836 env: {}",
837 key,
838 cv,
839 env
840 );
841 }
842 None => {
843 let mut cv_list = Vec::new();
844 self.get_env_list(key, &mut cv_list)?;
845 Ok(Some(CV::List(cv_list, env_def)))
846 }
847 }
848 } else {
849 match cv {
851 Some(CV::List(mut cv_list, cv_def)) => {
852 self.get_env_list(key, &mut cv_list)?;
854 Ok(Some(CV::List(cv_list, cv_def)))
855 }
856 _ => {
857 Ok(Some(CV::String(env.to_string(), env_def)))
862 }
863 }
864 }
865 }
866
867 pub fn set_env(&mut self, env: HashMap<String, String>) {
869 self.env = Env::from_map(env);
870 }
871
872 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
875 self.env.iter_str()
876 }
877
878 fn env_keys(&self) -> impl Iterator<Item = &str> {
880 self.env.keys_str()
881 }
882
883 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
884 where
885 T: FromStr,
886 <T as FromStr>::Err: fmt::Display,
887 {
888 match self.env.get_str(key.as_env_key()) {
889 Some(value) => {
890 let definition = Definition::Environment(key.as_env_key().to_string());
891 Ok(Some(Value {
892 val: value
893 .parse()
894 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
895 definition,
896 }))
897 }
898 None => {
899 self.check_environment_key_case_mismatch(key);
900 Ok(None)
901 }
902 }
903 }
904
905 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
910 self.env.get_env(key)
911 }
912
913 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
918 self.env.get_env_os(key)
919 }
920
921 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
925 if self.env.contains_key(key.as_env_key()) {
926 return Ok(true);
927 }
928 if env_prefix_ok {
929 let env_prefix = format!("{}_", key.as_env_key());
930 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
931 return Ok(true);
932 }
933 }
934 if self.get_cv(key)?.is_some() {
935 return Ok(true);
936 }
937 self.check_environment_key_case_mismatch(key);
938
939 Ok(false)
940 }
941
942 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
943 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
944 let _ = self.shell().warn(format!(
945 "environment variables are expected to use uppercase letters and underscores, \
946 the variable `{}` will be ignored and have no effect",
947 env_key
948 ));
949 }
950 }
951
952 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
956 self.get::<OptValue<String>>(key)
957 }
958
959 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
965 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
966 v.map(|v| Value {
967 val: v.val.resolve_program(self),
968 definition: v.definition,
969 })
970 })
971 }
972
973 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
974 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
975 if is_path {
976 definition.root(self).join(value)
977 } else {
978 PathBuf::from(value)
980 }
981 }
982
983 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
991 let key = ConfigKey::from_str(key);
992 self._get_list(&key)
993 }
994
995 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
996 match self.get_cv(key)? {
997 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
998 Some(val) => self.expected("list", key, &val),
999 None => Ok(None),
1000 }
1001 }
1002
1003 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
1005 let mut res = Vec::new();
1006
1007 match self.get_cv(key)? {
1008 Some(CV::List(val, _def)) => res.extend(val),
1009 Some(CV::String(val, def)) => {
1010 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
1011 res.extend(split_vs);
1012 }
1013 Some(val) => {
1014 return self.expected("string or array of strings", key, &val);
1015 }
1016 None => {}
1017 }
1018
1019 self.get_env_list(key, &mut res)?;
1020
1021 Ok(res)
1022 }
1023
1024 fn get_env_list(
1027 &self,
1028 key: &ConfigKey,
1029 output: &mut Vec<(String, Definition)>,
1030 ) -> CargoResult<()> {
1031 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1032 self.check_environment_key_case_mismatch(key);
1033 return Ok(());
1034 };
1035
1036 if is_nonmergable_list(&key) {
1037 output.clear();
1038 }
1039
1040 let def = Definition::Environment(key.as_env_key().to_string());
1041 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1042 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1044 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1045 })?;
1046 let values = toml_v.as_array().expect("env var was not array");
1047 for value in values {
1048 let s = value.as_str().ok_or_else(|| {
1050 ConfigError::new(
1051 format!("expected string, found {}", value.type_str()),
1052 def.clone(),
1053 )
1054 })?;
1055 output.push((s.to_string(), def.clone()));
1056 }
1057 } else {
1058 output.extend(
1059 env_val
1060 .split_whitespace()
1061 .map(|s| (s.to_string(), def.clone())),
1062 );
1063 }
1064 output.sort_by(|a, b| a.1.cmp(&b.1));
1065 Ok(())
1066 }
1067
1068 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1072 match self.get_cv(key)? {
1073 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1074 Some(val) => self.expected("table", key, &val),
1075 None => Ok(None),
1076 }
1077 }
1078
1079 get_value_typed! {get_integer, i64, Integer, "an integer"}
1080 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1081 get_value_typed! {get_string_priv, String, String, "a string"}
1082
1083 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1085 val.expected(ty, &key.to_string())
1086 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1087 }
1088
1089 pub fn configure(
1095 &mut self,
1096 verbose: u32,
1097 quiet: bool,
1098 color: Option<&str>,
1099 frozen: bool,
1100 locked: bool,
1101 offline: bool,
1102 target_dir: &Option<PathBuf>,
1103 unstable_flags: &[String],
1104 cli_config: &[String],
1105 ) -> CargoResult<()> {
1106 for warning in self
1107 .unstable_flags
1108 .parse(unstable_flags, self.nightly_features_allowed)?
1109 {
1110 self.shell().warn(warning)?;
1111 }
1112 if !unstable_flags.is_empty() {
1113 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1116 }
1117 if !cli_config.is_empty() {
1118 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1119 self.merge_cli_args()?;
1120 }
1121
1122 self.load_unstable_flags_from_config()?;
1126 if self.unstable_flags.config_include {
1127 self.reload_rooted_at(self.cwd.clone())?;
1134 }
1135
1136 let term = self.get::<TermConfig>("term").unwrap_or_default();
1140
1141 let extra_verbose = verbose >= 2;
1143 let verbose = verbose != 0;
1144 let verbosity = match (verbose, quiet) {
1145 (true, true) => bail!("cannot set both --verbose and --quiet"),
1146 (true, false) => Verbosity::Verbose,
1147 (false, true) => Verbosity::Quiet,
1148 (false, false) => match (term.verbose, term.quiet) {
1149 (Some(true), Some(true)) => {
1150 bail!("cannot set both `term.verbose` and `term.quiet`")
1151 }
1152 (Some(true), _) => Verbosity::Verbose,
1153 (_, Some(true)) => Verbosity::Quiet,
1154 _ => Verbosity::Normal,
1155 },
1156 };
1157 self.shell().set_verbosity(verbosity);
1158 self.extra_verbose = extra_verbose;
1159
1160 let color = color.or_else(|| term.color.as_deref());
1161 self.shell().set_color_choice(color)?;
1162 if let Some(hyperlinks) = term.hyperlinks {
1163 self.shell().set_hyperlinks(hyperlinks)?;
1164 }
1165 if let Some(unicode) = term.unicode {
1166 self.shell().set_unicode(unicode)?;
1167 }
1168
1169 self.progress_config = term.progress.unwrap_or_default();
1170
1171 self.frozen = frozen;
1172 self.locked = locked;
1173 self.offline = offline
1174 || self
1175 .net_config()
1176 .ok()
1177 .and_then(|n| n.offline)
1178 .unwrap_or(false);
1179 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1180 self.target_dir = cli_target_dir;
1181
1182 Ok(())
1183 }
1184
1185 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1186 if self.nightly_features_allowed {
1189 self.unstable_flags = self
1190 .get::<Option<CliUnstable>>("unstable")?
1191 .unwrap_or_default();
1192 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1193 self.unstable_flags.parse(unstable_flags_cli, true)?;
1198 }
1199 }
1200
1201 Ok(())
1202 }
1203
1204 pub fn cli_unstable(&self) -> &CliUnstable {
1205 &self.unstable_flags
1206 }
1207
1208 pub fn extra_verbose(&self) -> bool {
1209 self.extra_verbose
1210 }
1211
1212 pub fn network_allowed(&self) -> bool {
1213 !self.offline_flag().is_some()
1214 }
1215
1216 pub fn offline_flag(&self) -> Option<&'static str> {
1217 if self.frozen {
1218 Some("--frozen")
1219 } else if self.offline {
1220 Some("--offline")
1221 } else {
1222 None
1223 }
1224 }
1225
1226 pub fn set_locked(&mut self, locked: bool) {
1227 self.locked = locked;
1228 }
1229
1230 pub fn lock_update_allowed(&self) -> bool {
1231 !self.locked_flag().is_some()
1232 }
1233
1234 pub fn locked_flag(&self) -> Option<&'static str> {
1235 if self.frozen {
1236 Some("--frozen")
1237 } else if self.locked {
1238 Some("--locked")
1239 } else {
1240 None
1241 }
1242 }
1243
1244 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1246 self.load_values_from(&self.cwd)
1247 }
1248
1249 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1253 let mut result = Vec::new();
1254 let mut seen = HashSet::new();
1255 let home = self.home_path.clone().into_path_unlocked();
1256 self.walk_tree(&self.cwd, &home, |path| {
1257 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1258 if self.cli_unstable().config_include {
1259 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1260 }
1261 result.push(cv);
1262 Ok(())
1263 })
1264 .context("could not load Cargo configuration")?;
1265 Ok(result)
1266 }
1267
1268 fn load_unmerged_include(
1272 &self,
1273 cv: &mut CV,
1274 seen: &mut HashSet<PathBuf>,
1275 output: &mut Vec<CV>,
1276 ) -> CargoResult<()> {
1277 let includes = self.include_paths(cv, false)?;
1278 for (path, abs_path, def) in includes {
1279 let mut cv = self
1280 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1281 .with_context(|| {
1282 format!("failed to load config include `{}` from `{}`", path, def)
1283 })?;
1284 self.load_unmerged_include(&mut cv, seen, output)?;
1285 output.push(cv);
1286 }
1287 Ok(())
1288 }
1289
1290 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1292 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1295 let home = self.home_path.clone().into_path_unlocked();
1296
1297 self.walk_tree(path, &home, |path| {
1298 let value = self.load_file(path)?;
1299 cfg.merge(value, false).with_context(|| {
1300 format!("failed to merge configuration at `{}`", path.display())
1301 })?;
1302 Ok(())
1303 })
1304 .context("could not load Cargo configuration")?;
1305
1306 match cfg {
1307 CV::Table(map, _) => Ok(map),
1308 _ => unreachable!(),
1309 }
1310 }
1311
1312 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1316 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1317 }
1318
1319 fn _load_file(
1329 &self,
1330 path: &Path,
1331 seen: &mut HashSet<PathBuf>,
1332 includes: bool,
1333 why_load: WhyLoad,
1334 ) -> CargoResult<ConfigValue> {
1335 if !seen.insert(path.to_path_buf()) {
1336 bail!(
1337 "config `include` cycle detected with path `{}`",
1338 path.display()
1339 );
1340 }
1341 tracing::debug!(?path, ?why_load, includes, "load config from file");
1342
1343 let contents = fs::read_to_string(path)
1344 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1345 let toml = parse_document(&contents, path, self).with_context(|| {
1346 format!("could not parse TOML configuration in `{}`", path.display())
1347 })?;
1348 let def = match why_load {
1349 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1350 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1351 };
1352 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1353 format!(
1354 "failed to load TOML configuration from `{}`",
1355 path.display()
1356 )
1357 })?;
1358 if includes {
1359 self.load_includes(value, seen, why_load)
1360 } else {
1361 Ok(value)
1362 }
1363 }
1364
1365 fn load_includes(
1372 &self,
1373 mut value: CV,
1374 seen: &mut HashSet<PathBuf>,
1375 why_load: WhyLoad,
1376 ) -> CargoResult<CV> {
1377 let includes = self.include_paths(&mut value, true)?;
1379 if !self.cli_unstable().config_include {
1381 return Ok(value);
1382 }
1383 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1385 for (path, abs_path, def) in includes {
1386 self._load_file(&abs_path, seen, true, why_load)
1387 .and_then(|include| root.merge(include, true))
1388 .with_context(|| {
1389 format!("failed to load config include `{}` from `{}`", path, def)
1390 })?;
1391 }
1392 root.merge(value, true)?;
1393 Ok(root)
1394 }
1395
1396 fn include_paths(
1398 &self,
1399 cv: &mut CV,
1400 remove: bool,
1401 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1402 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1403 let abs_path = match def {
1404 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1405 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1406 };
1407 (path.to_string(), abs_path, def.clone())
1408 };
1409 let CV::Table(table, _def) = cv else {
1410 unreachable!()
1411 };
1412 let owned;
1413 let include = if remove {
1414 owned = table.remove("include");
1415 owned.as_ref()
1416 } else {
1417 table.get("include")
1418 };
1419 let includes = match include {
1420 Some(CV::String(s, def)) => {
1421 vec![abs(s, def)]
1422 }
1423 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1424 Some(other) => bail!(
1425 "`include` expected a string or list, but found {} in `{}`",
1426 other.desc(),
1427 other.definition()
1428 ),
1429 None => {
1430 return Ok(Vec::new());
1431 }
1432 };
1433
1434 for (path, abs_path, def) in &includes {
1435 if abs_path.extension() != Some(OsStr::new("toml")) {
1436 bail!(
1437 "expected a config include path ending with `.toml`, \
1438 but found `{path}` from `{def}`",
1439 )
1440 }
1441 }
1442
1443 Ok(includes)
1444 }
1445
1446 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1448 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1449 let Some(cli_args) = &self.cli_config else {
1450 return Ok(loaded_args);
1451 };
1452 let mut seen = HashSet::new();
1453 for arg in cli_args {
1454 let arg_as_path = self.cwd.join(arg);
1455 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1456 let str_path = arg_as_path
1458 .to_str()
1459 .ok_or_else(|| {
1460 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1461 })?
1462 .to_string();
1463 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1464 .with_context(|| format!("failed to load config from `{}`", str_path))?
1465 } else {
1466 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1472 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1473 })?;
1474 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1475 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1476 }
1477 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1478 non_empty(d.prefix()) || non_empty(d.suffix())
1479 }
1480 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1481 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1482 }
1483 let ok = {
1484 let mut got_to_value = false;
1485 let mut table = doc.as_table();
1486 let mut is_root = true;
1487 while table.is_dotted() || is_root {
1488 is_root = false;
1489 if table.len() != 1 {
1490 break;
1491 }
1492 let (k, n) = table.iter().next().expect("len() == 1 above");
1493 match n {
1494 Item::Table(nt) => {
1495 if table.key(k).map_or(false, non_empty_key_decor)
1496 || non_empty_decor(nt.decor())
1497 {
1498 bail!(
1499 "--config argument `{arg}` \
1500 includes non-whitespace decoration"
1501 )
1502 }
1503 table = nt;
1504 }
1505 Item::Value(v) if v.is_inline_table() => {
1506 bail!(
1507 "--config argument `{arg}` \
1508 sets a value to an inline table, which is not accepted"
1509 );
1510 }
1511 Item::Value(v) => {
1512 if table
1513 .key(k)
1514 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1515 || non_empty_decor(v.decor())
1516 {
1517 bail!(
1518 "--config argument `{arg}` \
1519 includes non-whitespace decoration"
1520 )
1521 }
1522 got_to_value = true;
1523 break;
1524 }
1525 Item::ArrayOfTables(_) => {
1526 bail!(
1527 "--config argument `{arg}` \
1528 sets a value to an array of tables, which is not accepted"
1529 );
1530 }
1531
1532 Item::None => {
1533 bail!("--config argument `{arg}` doesn't provide a value")
1534 }
1535 }
1536 }
1537 got_to_value
1538 };
1539 if !ok {
1540 bail!(
1541 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1542 );
1543 }
1544
1545 let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1546 .with_context(|| {
1547 format!("failed to parse value from --config argument `{arg}`")
1548 })?;
1549
1550 if toml_v
1551 .get("registry")
1552 .and_then(|v| v.as_table())
1553 .and_then(|t| t.get("token"))
1554 .is_some()
1555 {
1556 bail!("registry.token cannot be set through --config for security reasons");
1557 } else if let Some((k, _)) = toml_v
1558 .get("registries")
1559 .and_then(|v| v.as_table())
1560 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1561 {
1562 bail!(
1563 "registries.{}.token cannot be set through --config for security reasons",
1564 k
1565 );
1566 }
1567
1568 if toml_v
1569 .get("registry")
1570 .and_then(|v| v.as_table())
1571 .and_then(|t| t.get("secret-key"))
1572 .is_some()
1573 {
1574 bail!(
1575 "registry.secret-key cannot be set through --config for security reasons"
1576 );
1577 } else if let Some((k, _)) = toml_v
1578 .get("registries")
1579 .and_then(|v| v.as_table())
1580 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1581 {
1582 bail!(
1583 "registries.{}.secret-key cannot be set through --config for security reasons",
1584 k
1585 );
1586 }
1587
1588 CV::from_toml(Definition::Cli(None), toml_v)
1589 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1590 };
1591 let tmp_table = self
1592 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1593 .context("failed to load --config include".to_string())?;
1594 loaded_args
1595 .merge(tmp_table, true)
1596 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1597 }
1598 Ok(loaded_args)
1599 }
1600
1601 fn merge_cli_args(&mut self) -> CargoResult<()> {
1603 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1604 unreachable!()
1605 };
1606 let values = self.values_mut()?;
1607 for (key, value) in loaded_map.into_iter() {
1608 match values.entry(key) {
1609 Vacant(entry) => {
1610 entry.insert(value);
1611 }
1612 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1613 format!(
1614 "failed to merge --config key `{}` into `{}`",
1615 entry.key(),
1616 entry.get().definition(),
1617 )
1618 })?,
1619 };
1620 }
1621 Ok(())
1622 }
1623
1624 fn get_file_path(
1630 &self,
1631 dir: &Path,
1632 filename_without_extension: &str,
1633 warn: bool,
1634 ) -> CargoResult<Option<PathBuf>> {
1635 let possible = dir.join(filename_without_extension);
1636 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1637
1638 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1639 if warn {
1640 if let Ok(possible_with_extension_handle) =
1641 same_file::Handle::from_path(&possible_with_extension)
1642 {
1643 if possible_handle != possible_with_extension_handle {
1649 self.shell().warn(format!(
1650 "both `{}` and `{}` exist. Using `{}`",
1651 possible.display(),
1652 possible_with_extension.display(),
1653 possible.display()
1654 ))?;
1655 }
1656 } else {
1657 self.shell().warn(format!(
1658 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1659 possible.display(),
1660 ))?;
1661 self.shell().note(
1662 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1663 )?;
1664 }
1665 }
1666
1667 Ok(Some(possible))
1668 } else if possible_with_extension.exists() {
1669 Ok(Some(possible_with_extension))
1670 } else {
1671 Ok(None)
1672 }
1673 }
1674
1675 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1676 where
1677 F: FnMut(&Path) -> CargoResult<()>,
1678 {
1679 let mut seen_dir = HashSet::new();
1680
1681 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1682 let config_root = current.join(".cargo");
1683 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1684 walk(&path)?;
1685 }
1686 seen_dir.insert(config_root);
1687 }
1688
1689 if !seen_dir.contains(home) {
1693 if let Some(path) = self.get_file_path(home, "config", true)? {
1694 walk(&path)?;
1695 }
1696 }
1697
1698 Ok(())
1699 }
1700
1701 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1703 RegistryName::new(registry)?;
1704 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1705 self.resolve_registry_index(&index).with_context(|| {
1706 format!(
1707 "invalid index URL for registry `{}` defined in {}",
1708 registry, index.definition
1709 )
1710 })
1711 } else {
1712 bail!(
1713 "registry index was not found in any configuration: `{}`",
1714 registry
1715 );
1716 }
1717 }
1718
1719 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1721 if self.get_string("registry.index")?.is_some() {
1722 bail!(
1723 "the `registry.index` config value is no longer supported\n\
1724 Use `[source]` replacement to alter the default index for crates.io."
1725 );
1726 }
1727 Ok(())
1728 }
1729
1730 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1731 let base = index
1733 .definition
1734 .root(self)
1735 .join("truncated-by-url_with_base");
1736 let _parsed = index.val.into_url()?;
1738 let url = index.val.into_url_with_base(Some(&*base))?;
1739 if url.password().is_some() {
1740 bail!("registry URLs may not contain passwords");
1741 }
1742 Ok(url)
1743 }
1744
1745 pub fn load_credentials(&self) -> CargoResult<()> {
1753 if self.credential_values.filled() {
1754 return Ok(());
1755 }
1756
1757 let home_path = self.home_path.clone().into_path_unlocked();
1758 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1759 return Ok(());
1760 };
1761
1762 let mut value = self.load_file(&credentials)?;
1763 {
1765 let CV::Table(ref mut value_map, ref def) = value else {
1766 unreachable!();
1767 };
1768
1769 if let Some(token) = value_map.remove("token") {
1770 if let Vacant(entry) = value_map.entry("registry".into()) {
1771 let map = HashMap::from([("token".into(), token)]);
1772 let table = CV::Table(map, def.clone());
1773 entry.insert(table);
1774 }
1775 }
1776 }
1777
1778 let mut credential_values = HashMap::new();
1779 if let CV::Table(map, _) = value {
1780 let base_map = self.values()?;
1781 for (k, v) in map {
1782 let entry = match base_map.get(&k) {
1783 Some(base_entry) => {
1784 let mut entry = base_entry.clone();
1785 entry.merge(v, true)?;
1786 entry
1787 }
1788 None => v,
1789 };
1790 credential_values.insert(k, entry);
1791 }
1792 }
1793 self.credential_values
1794 .fill(credential_values)
1795 .expect("was not filled at beginning of the function");
1796 Ok(())
1797 }
1798
1799 fn maybe_get_tool(
1802 &self,
1803 tool: &str,
1804 from_config: &Option<ConfigRelativePath>,
1805 ) -> Option<PathBuf> {
1806 let var = tool.to_uppercase();
1807
1808 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1809 Some(tool_path) => {
1810 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1811 let path = if maybe_relative {
1812 self.cwd.join(tool_path)
1813 } else {
1814 PathBuf::from(tool_path)
1815 };
1816 Some(path)
1817 }
1818
1819 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1820 }
1821 }
1822
1823 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1834 let tool_str = tool.as_str();
1835 self.maybe_get_tool(tool_str, from_config)
1836 .or_else(|| {
1837 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1851 if toolchain.to_str()?.contains(&['/', '\\']) {
1854 return None;
1855 }
1856 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1859 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1860 let tool_meta = tool_resolved.metadata().ok()?;
1861 let rustup_meta = rustup_resolved.metadata().ok()?;
1862 if tool_meta.len() != rustup_meta.len() {
1867 return None;
1868 }
1869 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1871 let toolchain_exe = home::rustup_home()
1872 .ok()?
1873 .join("toolchains")
1874 .join(&toolchain)
1875 .join("bin")
1876 .join(&tool_exe);
1877 toolchain_exe.exists().then_some(toolchain_exe)
1878 })
1879 .unwrap_or_else(|| PathBuf::from(tool_str))
1880 }
1881
1882 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1883 self.jobserver.as_ref()
1884 }
1885
1886 pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1887 let http = self
1888 .easy
1889 .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
1890 {
1891 let mut http = http.borrow_mut();
1892 http.reset();
1893 let timeout = configure_http_handle(self, &mut http)?;
1894 timeout.configure(&mut http)?;
1895 }
1896 Ok(http)
1897 }
1898
1899 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1900 self.http_config.try_borrow_with(|| {
1901 let mut http = self.get::<CargoHttpConfig>("http")?;
1902 let curl_v = curl::Version::get();
1903 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1904 Ok(http)
1905 })
1906 }
1907
1908 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1909 self.future_incompat_config
1910 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1911 }
1912
1913 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1914 self.net_config
1915 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1916 }
1917
1918 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1919 self.build_config
1920 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1921 }
1922
1923 pub fn progress_config(&self) -> &ProgressConfig {
1924 &self.progress_config
1925 }
1926
1927 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1930 let env_config = self.env_config.try_borrow_with(|| {
1931 CargoResult::Ok(Arc::new({
1932 let env_config = self.get::<EnvConfig>("env")?;
1933 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1949 if env_config.contains_key(*disallowed) {
1950 bail!(
1951 "setting the `{disallowed}` environment variable is not supported \
1952 in the `[env]` configuration table"
1953 );
1954 }
1955 }
1956 env_config
1957 .into_iter()
1958 .filter_map(|(k, v)| {
1959 if v.is_force() || self.get_env_os(&k).is_none() {
1960 Some((k, v.resolve(self).to_os_string()))
1961 } else {
1962 None
1963 }
1964 })
1965 .collect()
1966 }))
1967 })?;
1968
1969 Ok(env_config)
1970 }
1971
1972 pub fn validate_term_config(&self) -> CargoResult<()> {
1978 drop(self.get::<TermConfig>("term")?);
1979 Ok(())
1980 }
1981
1982 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1986 self.target_cfgs
1987 .try_borrow_with(|| target::load_target_cfgs(self))
1988 }
1989
1990 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1991 self.doc_extern_map
1995 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1996 }
1997
1998 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2000 target::get_target_applies_to_host(self)
2001 }
2002
2003 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2005 target::load_host_triple(self, target)
2006 }
2007
2008 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2010 target::load_target_triple(self, target)
2011 }
2012
2013 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2018 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2019 self.check_registry_index_not_set()?;
2020 let url = CRATES_IO_INDEX.into_url().unwrap();
2021 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2022 })?;
2023 Ok(*source_id)
2024 }
2025
2026 pub fn creation_time(&self) -> Instant {
2027 self.creation_time
2028 }
2029
2030 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2045 let d = Deserializer {
2046 gctx: self,
2047 key: ConfigKey::from_str(key),
2048 env_prefix_ok: true,
2049 };
2050 T::deserialize(d).map_err(|e| e.into())
2051 }
2052
2053 #[track_caller]
2059 #[tracing::instrument(skip_all)]
2060 pub fn assert_package_cache_locked<'a>(
2061 &self,
2062 mode: CacheLockMode,
2063 f: &'a Filesystem,
2064 ) -> &'a Path {
2065 let ret = f.as_path_unlocked();
2066 assert!(
2067 self.package_cache_lock.is_locked(mode),
2068 "package cache lock is not currently held, Cargo forgot to call \
2069 `acquire_package_cache_lock` before we got to this stack frame",
2070 );
2071 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2072 ret
2073 }
2074
2075 #[tracing::instrument(skip_all)]
2081 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2082 self.package_cache_lock.lock(self, mode)
2083 }
2084
2085 #[tracing::instrument(skip_all)]
2091 pub fn try_acquire_package_cache_lock(
2092 &self,
2093 mode: CacheLockMode,
2094 ) -> CargoResult<Option<CacheLock<'_>>> {
2095 self.package_cache_lock.try_lock(self, mode)
2096 }
2097
2098 pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
2103 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2104 Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
2105 })?;
2106 Ok(tracker.borrow_mut())
2107 }
2108
2109 pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
2111 let deferred = self.deferred_global_last_use.try_borrow_with(|| {
2112 Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
2113 })?;
2114 Ok(deferred.borrow_mut())
2115 }
2116
2117 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2119 if self.unstable_flags.warnings {
2120 Ok(self.build_config()?.warnings.unwrap_or_default())
2121 } else {
2122 Ok(WarningHandling::default())
2123 }
2124 }
2125}
2126
2127#[derive(Debug)]
2129pub struct ConfigError {
2130 error: anyhow::Error,
2131 definition: Option<Definition>,
2132}
2133
2134impl ConfigError {
2135 fn new(message: String, definition: Definition) -> ConfigError {
2136 ConfigError {
2137 error: anyhow::Error::msg(message),
2138 definition: Some(definition),
2139 }
2140 }
2141
2142 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2143 ConfigError {
2144 error: anyhow!(
2145 "`{}` expected {}, but found a {}",
2146 key,
2147 expected,
2148 found.desc()
2149 ),
2150 definition: Some(found.definition().clone()),
2151 }
2152 }
2153
2154 fn is_missing_field(&self) -> bool {
2155 self.error.downcast_ref::<MissingFieldError>().is_some()
2156 }
2157
2158 fn missing(key: &ConfigKey) -> ConfigError {
2159 ConfigError {
2160 error: anyhow!("missing config key `{}`", key),
2161 definition: None,
2162 }
2163 }
2164
2165 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2166 ConfigError {
2167 error: anyhow::Error::from(self)
2168 .context(format!("could not load config key `{}`", key)),
2169 definition: definition,
2170 }
2171 }
2172}
2173
2174impl std::error::Error for ConfigError {
2175 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2176 self.error.source()
2177 }
2178}
2179
2180impl fmt::Display for ConfigError {
2181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2182 if let Some(definition) = &self.definition {
2183 write!(f, "error in {}: {}", definition, self.error)
2184 } else {
2185 self.error.fmt(f)
2186 }
2187 }
2188}
2189
2190#[derive(Debug)]
2191struct MissingFieldError(String);
2192
2193impl fmt::Display for MissingFieldError {
2194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2195 write!(f, "missing field `{}`", self.0)
2196 }
2197}
2198
2199impl std::error::Error for MissingFieldError {}
2200
2201impl serde::de::Error for ConfigError {
2202 fn custom<T: fmt::Display>(msg: T) -> Self {
2203 ConfigError {
2204 error: anyhow::Error::msg(msg.to_string()),
2205 definition: None,
2206 }
2207 }
2208
2209 fn missing_field(field: &'static str) -> Self {
2210 ConfigError {
2211 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2212 definition: None,
2213 }
2214 }
2215}
2216
2217impl From<anyhow::Error> for ConfigError {
2218 fn from(error: anyhow::Error) -> Self {
2219 ConfigError {
2220 error,
2221 definition: None,
2222 }
2223 }
2224}
2225
2226#[derive(Eq, PartialEq, Clone)]
2227pub enum ConfigValue {
2228 Integer(i64, Definition),
2229 String(String, Definition),
2230 List(Vec<(String, Definition)>, Definition),
2231 Table(HashMap<String, ConfigValue>, Definition),
2232 Boolean(bool, Definition),
2233}
2234
2235impl fmt::Debug for ConfigValue {
2236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2237 match self {
2238 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2239 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2240 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2241 CV::List(list, def) => {
2242 write!(f, "[")?;
2243 for (i, (s, def)) in list.iter().enumerate() {
2244 if i > 0 {
2245 write!(f, ", ")?;
2246 }
2247 write!(f, "{} (from {})", s, def)?;
2248 }
2249 write!(f, "] (from {})", def)
2250 }
2251 CV::Table(table, _) => write!(f, "{:?}", table),
2252 }
2253 }
2254}
2255
2256impl ConfigValue {
2257 fn get_definition(&self) -> &Definition {
2258 match self {
2259 CV::Boolean(_, def)
2260 | CV::Integer(_, def)
2261 | CV::String(_, def)
2262 | CV::List(_, def)
2263 | CV::Table(_, def) => def,
2264 }
2265 }
2266
2267 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2268 match toml {
2269 toml::Value::String(val) => Ok(CV::String(val, def)),
2270 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2271 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2272 toml::Value::Array(val) => Ok(CV::List(
2273 val.into_iter()
2274 .map(|toml| match toml {
2275 toml::Value::String(val) => Ok((val, def.clone())),
2276 v => bail!("expected string but found {} in list", v.type_str()),
2277 })
2278 .collect::<CargoResult<_>>()?,
2279 def,
2280 )),
2281 toml::Value::Table(val) => Ok(CV::Table(
2282 val.into_iter()
2283 .map(|(key, value)| {
2284 let value = CV::from_toml(def.clone(), value)
2285 .with_context(|| format!("failed to parse key `{}`", key))?;
2286 Ok((key, value))
2287 })
2288 .collect::<CargoResult<_>>()?,
2289 def,
2290 )),
2291 v => bail!(
2292 "found TOML configuration value of unknown type `{}`",
2293 v.type_str()
2294 ),
2295 }
2296 }
2297
2298 fn into_toml(self) -> toml::Value {
2299 match self {
2300 CV::Boolean(s, _) => toml::Value::Boolean(s),
2301 CV::String(s, _) => toml::Value::String(s),
2302 CV::Integer(i, _) => toml::Value::Integer(i),
2303 CV::List(l, _) => {
2304 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2305 }
2306 CV::Table(l, _) => {
2307 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2308 }
2309 }
2310 }
2311
2312 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2321 self.merge_helper(from, force, &mut ConfigKey::new())
2322 }
2323
2324 fn merge_helper(
2325 &mut self,
2326 from: ConfigValue,
2327 force: bool,
2328 parts: &mut ConfigKey,
2329 ) -> CargoResult<()> {
2330 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2331 match (self, from) {
2332 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2333 if is_nonmergable_list(&parts) {
2334 if force || is_higher_priority {
2336 mem::swap(new, old);
2337 }
2338 } else {
2339 if force {
2341 old.append(new);
2342 } else {
2343 new.append(old);
2344 mem::swap(new, old);
2345 }
2346 }
2347 old.sort_by(|a, b| a.1.cmp(&b.1));
2348 }
2349 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2350 for (key, value) in mem::take(new) {
2351 match old.entry(key.clone()) {
2352 Occupied(mut entry) => {
2353 let new_def = value.definition().clone();
2354 let entry = entry.get_mut();
2355 parts.push(&key);
2356 entry.merge_helper(value, force, parts).with_context(|| {
2357 format!(
2358 "failed to merge key `{}` between \
2359 {} and {}",
2360 key,
2361 entry.definition(),
2362 new_def,
2363 )
2364 })?;
2365 }
2366 Vacant(entry) => {
2367 entry.insert(value);
2368 }
2369 };
2370 }
2371 }
2372 (expected @ &mut CV::List(_, _), found)
2374 | (expected @ &mut CV::Table(_, _), found)
2375 | (expected, found @ CV::List(_, _))
2376 | (expected, found @ CV::Table(_, _)) => {
2377 return Err(anyhow!(
2378 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2379 found.definition(),
2380 expected.definition(),
2381 expected.desc(),
2382 found.desc()
2383 ));
2384 }
2385 (old, mut new) => {
2386 if force || is_higher_priority {
2387 mem::swap(old, &mut new);
2388 }
2389 }
2390 }
2391
2392 Ok(())
2393 }
2394
2395 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2396 match self {
2397 CV::Integer(i, def) => Ok((*i, def)),
2398 _ => self.expected("integer", key),
2399 }
2400 }
2401
2402 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2403 match self {
2404 CV::String(s, def) => Ok((s, def)),
2405 _ => self.expected("string", key),
2406 }
2407 }
2408
2409 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2410 match self {
2411 CV::Table(table, def) => Ok((table, def)),
2412 _ => self.expected("table", key),
2413 }
2414 }
2415
2416 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2417 match self {
2418 CV::List(list, _) => Ok(list),
2419 _ => self.expected("list", key),
2420 }
2421 }
2422
2423 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2424 match self {
2425 CV::Boolean(b, def) => Ok((*b, def)),
2426 _ => self.expected("bool", key),
2427 }
2428 }
2429
2430 pub fn desc(&self) -> &'static str {
2431 match *self {
2432 CV::Table(..) => "table",
2433 CV::List(..) => "array",
2434 CV::String(..) => "string",
2435 CV::Boolean(..) => "boolean",
2436 CV::Integer(..) => "integer",
2437 }
2438 }
2439
2440 pub fn definition(&self) -> &Definition {
2441 match self {
2442 CV::Boolean(_, def)
2443 | CV::Integer(_, def)
2444 | CV::String(_, def)
2445 | CV::List(_, def)
2446 | CV::Table(_, def) => def,
2447 }
2448 }
2449
2450 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2451 bail!(
2452 "expected a {}, but found a {} for `{}` in {}",
2453 wanted,
2454 self.desc(),
2455 key,
2456 self.definition()
2457 )
2458 }
2459}
2460
2461fn is_nonmergable_list(key: &ConfigKey) -> bool {
2464 key.matches("registry.credential-provider")
2465 || key.matches("registries.*.credential-provider")
2466 || key.matches("target.*.runner")
2467 || key.matches("host.runner")
2468 || key.matches("credential-alias.*")
2469 || key.matches("doc.browser")
2470}
2471
2472pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2473 ::home::cargo_home_with_cwd(cwd).ok()
2474}
2475
2476pub fn save_credentials(
2477 gctx: &GlobalContext,
2478 token: Option<RegistryCredentialConfig>,
2479 registry: &SourceId,
2480) -> CargoResult<()> {
2481 let registry = if registry.is_crates_io() {
2482 None
2483 } else {
2484 let name = registry
2485 .alt_registry_key()
2486 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2487 Some(name)
2488 };
2489
2490 let home_path = gctx.home_path.clone().into_path_unlocked();
2494 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2495 Some(path) => match path.file_name() {
2496 Some(filename) => Path::new(filename).to_owned(),
2497 None => Path::new("credentials.toml").to_owned(),
2498 },
2499 None => Path::new("credentials.toml").to_owned(),
2500 };
2501
2502 let mut file = {
2503 gctx.home_path.create_dir()?;
2504 gctx.home_path
2505 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2506 };
2507
2508 let mut contents = String::new();
2509 file.read_to_string(&mut contents).with_context(|| {
2510 format!(
2511 "failed to read configuration file `{}`",
2512 file.path().display()
2513 )
2514 })?;
2515
2516 let mut toml = parse_document(&contents, file.path(), gctx)?;
2517
2518 if let Some(token) = toml.remove("token") {
2520 let map = HashMap::from([("token".to_string(), token)]);
2521 toml.insert("registry".into(), map.into());
2522 }
2523
2524 if let Some(token) = token {
2525 let path_def = Definition::Path(file.path().to_path_buf());
2528 let (key, mut value) = match token {
2529 RegistryCredentialConfig::Token(token) => {
2530 let key = "token".to_string();
2533 let value = ConfigValue::String(token.expose(), path_def.clone());
2534 let map = HashMap::from([(key, value)]);
2535 let table = CV::Table(map, path_def.clone());
2536
2537 if let Some(registry) = registry {
2538 let map = HashMap::from([(registry.to_string(), table)]);
2539 ("registries".into(), CV::Table(map, path_def.clone()))
2540 } else {
2541 ("registry".into(), table)
2542 }
2543 }
2544 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2545 let key = "secret-key".to_string();
2548 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2549 let mut map = HashMap::from([(key, value)]);
2550 if let Some(key_subject) = key_subject {
2551 let key = "secret-key-subject".to_string();
2552 let value = ConfigValue::String(key_subject, path_def.clone());
2553 map.insert(key, value);
2554 }
2555 let table = CV::Table(map, path_def.clone());
2556
2557 if let Some(registry) = registry {
2558 let map = HashMap::from([(registry.to_string(), table)]);
2559 ("registries".into(), CV::Table(map, path_def.clone()))
2560 } else {
2561 ("registry".into(), table)
2562 }
2563 }
2564 _ => unreachable!(),
2565 };
2566
2567 if registry.is_some() {
2568 if let Some(table) = toml.remove("registries") {
2569 let v = CV::from_toml(path_def, table)?;
2570 value.merge(v, false)?;
2571 }
2572 }
2573 toml.insert(key, value.into_toml());
2574 } else {
2575 if let Some(registry) = registry {
2577 if let Some(registries) = toml.get_mut("registries") {
2578 if let Some(reg) = registries.get_mut(registry) {
2579 let rtable = reg.as_table_mut().ok_or_else(|| {
2580 format_err!("expected `[registries.{}]` to be a table", registry)
2581 })?;
2582 rtable.remove("token");
2583 rtable.remove("secret-key");
2584 rtable.remove("secret-key-subject");
2585 }
2586 }
2587 } else if let Some(registry) = toml.get_mut("registry") {
2588 let reg_table = registry
2589 .as_table_mut()
2590 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2591 reg_table.remove("token");
2592 reg_table.remove("secret-key");
2593 reg_table.remove("secret-key-subject");
2594 }
2595 }
2596
2597 let contents = toml.to_string();
2598 file.seek(SeekFrom::Start(0))?;
2599 file.write_all(contents.as_bytes())
2600 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2601 file.file().set_len(contents.len() as u64)?;
2602 set_permissions(file.file(), 0o600)
2603 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2604
2605 return Ok(());
2606
2607 #[cfg(unix)]
2608 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2609 use std::os::unix::fs::PermissionsExt;
2610
2611 let mut perms = file.metadata()?.permissions();
2612 perms.set_mode(mode);
2613 file.set_permissions(perms)?;
2614 Ok(())
2615 }
2616
2617 #[cfg(not(unix))]
2618 #[allow(unused)]
2619 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2620 Ok(())
2621 }
2622}
2623
2624#[derive(Debug, Default, Deserialize, PartialEq)]
2625#[serde(rename_all = "kebab-case")]
2626pub struct CargoHttpConfig {
2627 pub proxy: Option<String>,
2628 pub low_speed_limit: Option<u32>,
2629 pub timeout: Option<u64>,
2630 pub cainfo: Option<ConfigRelativePath>,
2631 pub proxy_cainfo: Option<ConfigRelativePath>,
2632 pub check_revoke: Option<bool>,
2633 pub user_agent: Option<String>,
2634 pub debug: Option<bool>,
2635 pub multiplexing: Option<bool>,
2636 pub ssl_version: Option<SslVersionConfig>,
2637}
2638
2639#[derive(Debug, Default, Deserialize, PartialEq)]
2640#[serde(rename_all = "kebab-case")]
2641pub struct CargoFutureIncompatConfig {
2642 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2643}
2644
2645#[derive(Debug, Default, Deserialize, PartialEq)]
2646#[serde(rename_all = "kebab-case")]
2647pub enum CargoFutureIncompatFrequencyConfig {
2648 #[default]
2649 Always,
2650 Never,
2651}
2652
2653impl CargoFutureIncompatConfig {
2654 pub fn should_display_message(&self) -> bool {
2655 use CargoFutureIncompatFrequencyConfig::*;
2656
2657 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2658 match frequency {
2659 Always => true,
2660 Never => false,
2661 }
2662 }
2663}
2664
2665#[derive(Clone, Debug, PartialEq)]
2679pub enum SslVersionConfig {
2680 Single(String),
2681 Range(SslVersionConfigRange),
2682}
2683
2684impl<'de> Deserialize<'de> for SslVersionConfig {
2685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2686 where
2687 D: serde::Deserializer<'de>,
2688 {
2689 UntaggedEnumVisitor::new()
2690 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2691 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2692 .deserialize(deserializer)
2693 }
2694}
2695
2696#[derive(Clone, Debug, Deserialize, PartialEq)]
2697#[serde(rename_all = "kebab-case")]
2698pub struct SslVersionConfigRange {
2699 pub min: Option<String>,
2700 pub max: Option<String>,
2701}
2702
2703#[derive(Debug, Deserialize)]
2704#[serde(rename_all = "kebab-case")]
2705pub struct CargoNetConfig {
2706 pub retry: Option<u32>,
2707 pub offline: Option<bool>,
2708 pub git_fetch_with_cli: Option<bool>,
2709 pub ssh: Option<CargoSshConfig>,
2710}
2711
2712#[derive(Debug, Deserialize)]
2713#[serde(rename_all = "kebab-case")]
2714pub struct CargoSshConfig {
2715 pub known_hosts: Option<Vec<Value<String>>>,
2716}
2717
2718#[derive(Debug, Clone)]
2731pub enum JobsConfig {
2732 Integer(i32),
2733 String(String),
2734}
2735
2736impl<'de> Deserialize<'de> for JobsConfig {
2737 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2738 where
2739 D: serde::Deserializer<'de>,
2740 {
2741 UntaggedEnumVisitor::new()
2742 .i32(|int| Ok(JobsConfig::Integer(int)))
2743 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2744 .deserialize(deserializer)
2745 }
2746}
2747
2748#[derive(Debug, Deserialize)]
2749#[serde(rename_all = "kebab-case")]
2750pub struct CargoBuildConfig {
2751 pub pipelining: Option<bool>,
2753 pub dep_info_basedir: Option<ConfigRelativePath>,
2754 pub target_dir: Option<ConfigRelativePath>,
2755 pub build_dir: Option<ConfigRelativePath>,
2756 pub incremental: Option<bool>,
2757 pub target: Option<BuildTargetConfig>,
2758 pub jobs: Option<JobsConfig>,
2759 pub rustflags: Option<StringList>,
2760 pub rustdocflags: Option<StringList>,
2761 pub rustc_wrapper: Option<ConfigRelativePath>,
2762 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2763 pub rustc: Option<ConfigRelativePath>,
2764 pub rustdoc: Option<ConfigRelativePath>,
2765 pub out_dir: Option<ConfigRelativePath>,
2767 pub artifact_dir: Option<ConfigRelativePath>,
2768 pub warnings: Option<WarningHandling>,
2769 pub sbom: Option<bool>,
2771 pub analysis: Option<CargoBuildAnalysis>,
2773}
2774
2775#[derive(Debug, Deserialize, Default)]
2777#[serde(rename_all = "kebab-case")]
2778pub struct CargoBuildAnalysis {
2779 pub enabled: bool,
2780}
2781
2782#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2784#[serde(rename_all = "kebab-case")]
2785pub enum WarningHandling {
2786 #[default]
2787 Warn,
2789 Allow,
2791 Deny,
2793}
2794
2795#[derive(Debug, Deserialize)]
2805#[serde(transparent)]
2806pub struct BuildTargetConfig {
2807 inner: Value<BuildTargetConfigInner>,
2808}
2809
2810#[derive(Debug)]
2811enum BuildTargetConfigInner {
2812 One(String),
2813 Many(Vec<String>),
2814}
2815
2816impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2817 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2818 where
2819 D: serde::Deserializer<'de>,
2820 {
2821 UntaggedEnumVisitor::new()
2822 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2823 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2824 .deserialize(deserializer)
2825 }
2826}
2827
2828impl BuildTargetConfig {
2829 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2831 let map = |s: &String| {
2832 if s.ends_with(".json") {
2833 self.inner
2836 .definition
2837 .root(gctx)
2838 .join(s)
2839 .to_str()
2840 .expect("must be utf-8 in toml")
2841 .to_string()
2842 } else {
2843 s.to_string()
2845 }
2846 };
2847 let values = match &self.inner.val {
2848 BuildTargetConfigInner::One(s) => vec![map(s)],
2849 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2850 };
2851 Ok(values)
2852 }
2853}
2854
2855#[derive(Debug, Deserialize)]
2856#[serde(rename_all = "kebab-case")]
2857pub struct CargoResolverConfig {
2858 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2859 pub feature_unification: Option<FeatureUnification>,
2860}
2861
2862#[derive(Debug, Deserialize, PartialEq, Eq)]
2863#[serde(rename_all = "kebab-case")]
2864pub enum IncompatibleRustVersions {
2865 Allow,
2866 Fallback,
2867}
2868
2869#[derive(Copy, Clone, Debug, Deserialize)]
2870#[serde(rename_all = "kebab-case")]
2871pub enum FeatureUnification {
2872 Package,
2873 Selected,
2874 Workspace,
2875}
2876
2877#[derive(Deserialize, Default)]
2878#[serde(rename_all = "kebab-case")]
2879pub struct TermConfig {
2880 pub verbose: Option<bool>,
2881 pub quiet: Option<bool>,
2882 pub color: Option<String>,
2883 pub hyperlinks: Option<bool>,
2884 pub unicode: Option<bool>,
2885 #[serde(default)]
2886 #[serde(deserialize_with = "progress_or_string")]
2887 pub progress: Option<ProgressConfig>,
2888}
2889
2890#[derive(Debug, Default, Deserialize)]
2891#[serde(rename_all = "kebab-case")]
2892pub struct ProgressConfig {
2893 #[serde(default)]
2894 pub when: ProgressWhen,
2895 pub width: Option<usize>,
2896 pub term_integration: Option<bool>,
2898}
2899
2900#[derive(Debug, Default, Deserialize)]
2901#[serde(rename_all = "kebab-case")]
2902pub enum ProgressWhen {
2903 #[default]
2904 Auto,
2905 Never,
2906 Always,
2907}
2908
2909fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2910where
2911 D: serde::de::Deserializer<'de>,
2912{
2913 struct ProgressVisitor;
2914
2915 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2916 type Value = Option<ProgressConfig>;
2917
2918 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2919 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2920 }
2921
2922 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2923 where
2924 E: serde::de::Error,
2925 {
2926 match s {
2927 "auto" => Ok(Some(ProgressConfig {
2928 when: ProgressWhen::Auto,
2929 width: None,
2930 term_integration: None,
2931 })),
2932 "never" => Ok(Some(ProgressConfig {
2933 when: ProgressWhen::Never,
2934 width: None,
2935 term_integration: None,
2936 })),
2937 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2938 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2939 }
2940 }
2941
2942 fn visit_none<E>(self) -> Result<Self::Value, E>
2943 where
2944 E: serde::de::Error,
2945 {
2946 Ok(None)
2947 }
2948
2949 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2950 where
2951 D: serde::de::Deserializer<'de>,
2952 {
2953 let pc = ProgressConfig::deserialize(deserializer)?;
2954 if let ProgressConfig {
2955 when: ProgressWhen::Always,
2956 width: None,
2957 ..
2958 } = pc
2959 {
2960 return Err(serde::de::Error::custom(
2961 "\"always\" progress requires a `width` key",
2962 ));
2963 }
2964 Ok(Some(pc))
2965 }
2966 }
2967
2968 deserializer.deserialize_option(ProgressVisitor)
2969}
2970
2971#[derive(Debug)]
2972enum EnvConfigValueInner {
2973 Simple(String),
2974 WithOptions {
2975 value: String,
2976 force: bool,
2977 relative: bool,
2978 },
2979}
2980
2981impl<'de> Deserialize<'de> for EnvConfigValueInner {
2982 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2983 where
2984 D: serde::Deserializer<'de>,
2985 {
2986 #[derive(Deserialize)]
2987 struct WithOptions {
2988 value: String,
2989 #[serde(default)]
2990 force: bool,
2991 #[serde(default)]
2992 relative: bool,
2993 }
2994
2995 UntaggedEnumVisitor::new()
2996 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2997 .map(|map| {
2998 let with_options: WithOptions = map.deserialize()?;
2999 Ok(EnvConfigValueInner::WithOptions {
3000 value: with_options.value,
3001 force: with_options.force,
3002 relative: with_options.relative,
3003 })
3004 })
3005 .deserialize(deserializer)
3006 }
3007}
3008
3009#[derive(Debug, Deserialize)]
3010#[serde(transparent)]
3011pub struct EnvConfigValue {
3012 inner: Value<EnvConfigValueInner>,
3013}
3014
3015impl EnvConfigValue {
3016 pub fn is_force(&self) -> bool {
3017 match self.inner.val {
3018 EnvConfigValueInner::Simple(_) => false,
3019 EnvConfigValueInner::WithOptions { force, .. } => force,
3020 }
3021 }
3022
3023 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
3024 match self.inner.val {
3025 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
3026 EnvConfigValueInner::WithOptions {
3027 ref value,
3028 relative,
3029 ..
3030 } => {
3031 if relative {
3032 let p = self.inner.definition.root(gctx).join(&value);
3033 Cow::Owned(p.into_os_string())
3034 } else {
3035 Cow::Borrowed(OsStr::new(value.as_str()))
3036 }
3037 }
3038 }
3039 }
3040}
3041
3042pub type EnvConfig = HashMap<String, EnvConfigValue>;
3043
3044fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3045 toml.parse().map_err(Into::into)
3047}
3048
3049#[derive(Debug, Deserialize, Clone)]
3060pub struct StringList(Vec<String>);
3061
3062impl StringList {
3063 pub fn as_slice(&self) -> &[String] {
3064 &self.0
3065 }
3066}
3067
3068#[macro_export]
3069macro_rules! __shell_print {
3070 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3071 let mut shell = $config.shell();
3072 let out = shell.$which();
3073 drop(out.write_fmt(format_args!($($arg)*)));
3074 if $newline {
3075 drop(out.write_all(b"\n"));
3076 }
3077 });
3078}
3079
3080#[macro_export]
3081macro_rules! drop_println {
3082 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3083 ($config:expr, $($arg:tt)*) => (
3084 $crate::__shell_print!($config, out, true, $($arg)*)
3085 );
3086}
3087
3088#[macro_export]
3089macro_rules! drop_eprintln {
3090 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3091 ($config:expr, $($arg:tt)*) => (
3092 $crate::__shell_print!($config, err, true, $($arg)*)
3093 );
3094}
3095
3096#[macro_export]
3097macro_rules! drop_print {
3098 ($config:expr, $($arg:tt)*) => (
3099 $crate::__shell_print!($config, out, false, $($arg)*)
3100 );
3101}
3102
3103#[macro_export]
3104macro_rules! drop_eprint {
3105 ($config:expr, $($arg:tt)*) => (
3106 $crate::__shell_print!($config, err, false, $($arg)*)
3107 );
3108}
3109
3110enum Tool {
3111 Rustc,
3112 Rustdoc,
3113}
3114
3115impl Tool {
3116 fn as_str(&self) -> &str {
3117 match self {
3118 Tool::Rustc => "rustc",
3119 Tool::Rustdoc => "rustdoc",
3120 }
3121 }
3122}
3123
3124fn disables_multiplexing_for_bad_curl(
3134 curl_version: &str,
3135 http: &mut CargoHttpConfig,
3136 gctx: &GlobalContext,
3137) {
3138 use crate::util::network;
3139
3140 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3141 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3142 if bad_curl_versions
3143 .iter()
3144 .any(|v| curl_version.starts_with(v))
3145 {
3146 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3147 http.multiplexing = Some(false);
3148 }
3149 }
3150}
3151
3152#[cfg(test)]
3153mod tests {
3154 use super::CargoHttpConfig;
3155 use super::GlobalContext;
3156 use super::Shell;
3157 use super::disables_multiplexing_for_bad_curl;
3158
3159 #[test]
3160 fn disables_multiplexing() {
3161 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3162 gctx.set_search_stop_path(std::path::PathBuf::new());
3163 gctx.set_env(Default::default());
3164
3165 let mut http = CargoHttpConfig::default();
3166 http.proxy = Some("127.0.0.1:3128".into());
3167 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3168 assert_eq!(http.multiplexing, Some(false));
3169
3170 let cases = [
3171 (None, None, "7.87.0", None),
3172 (None, None, "7.88.0", None),
3173 (None, None, "7.88.1", None),
3174 (None, None, "8.0.0", None),
3175 (Some("".into()), None, "7.87.0", Some(false)),
3176 (Some("".into()), None, "7.88.0", Some(false)),
3177 (Some("".into()), None, "7.88.1", Some(false)),
3178 (Some("".into()), None, "8.0.0", None),
3179 (Some("".into()), Some(false), "7.87.0", Some(false)),
3180 (Some("".into()), Some(false), "7.88.0", Some(false)),
3181 (Some("".into()), Some(false), "7.88.1", Some(false)),
3182 (Some("".into()), Some(false), "8.0.0", Some(false)),
3183 ];
3184
3185 for (proxy, multiplexing, curl_v, result) in cases {
3186 let mut http = CargoHttpConfig {
3187 multiplexing,
3188 proxy,
3189 ..Default::default()
3190 };
3191 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3192 assert_eq!(http.multiplexing, result);
3193 }
3194 }
3195}