cargo/util/config/
mod.rs

1//! Cargo's config system.
2//!
3//! The `Config` object contains general information about the environment,
4//! and provides access to Cargo's configuration files.
5//!
6//! ## Config value API
7//!
8//! The primary API for fetching user-defined config values is the
9//! `Config::get` method. It uses `serde` to translate config values to a
10//! target type.
11//!
12//! There are a variety of helper types for deserializing some common formats:
13//!
14//! - `value::Value`: This type provides access to the location where the
15//!   config value was defined.
16//! - `ConfigRelativePath`: For a path that is relative to where it is
17//!   defined.
18//! - `PathAndArgs`: Similar to `ConfigRelativePath`, but also supports a list
19//!   of arguments, useful for programs to execute.
20//! - `StringList`: Get a value that is either a list or a whitespace split
21//!   string.
22//!
23//! ## Map key recommendations
24//!
25//! Handling tables that have arbitrary keys can be tricky, particularly if it
26//! should support environment variables. In general, if possible, the caller
27//! should pass the full key path into the `get()` method so that the config
28//! deserializer can properly handle environment variables (which need to be
29//! uppercased, and dashes converted to underscores).
30//!
31//! A good example is the `[target]` table. The code will request
32//! `target.$TRIPLE` and the config system can then appropriately fetch
33//! environment variables like `CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER`.
34//! Conversely, it is not possible do the same thing for the `cfg()` target
35//! tables (because Cargo must fetch all of them), so those do not support
36//! environment variables.
37//!
38//! Try to avoid keys that are a prefix of another with a dash/underscore. For
39//! example `build.target` and `build.target-dir`. This is OK if these are not
40//! structs/maps, but if it is a struct or map, then it will not be able to
41//! read the environment variable due to ambiguity. (See `ConfigMapAccess` for
42//! more details.)
43//!
44//! ## Internal API
45//!
46//! Internally config values are stored with the `ConfigValue` type after they
47//! have been loaded from disk. This is similar to the `toml::Value` type, but
48//! includes the definition location. The `get()` method uses serde to
49//! translate from `ConfigValue` and environment variables to the caller's
50//! desired type.
51
52use std::cell::{RefCell, RefMut};
53use std::collections::hash_map::Entry::{Occupied, Vacant};
54use std::collections::{HashMap, HashSet};
55use std::env;
56use std::fmt;
57use std::fs::{self, File};
58use std::io::prelude::*;
59use std::io::{self, SeekFrom};
60use std::mem;
61use std::path::{Path, PathBuf};
62use std::str::FromStr;
63use std::sync::Once;
64use std::time::Instant;
65
66use anyhow::{anyhow, bail};
67use curl::easy::Easy;
68use lazycell::LazyCell;
69use serde::Deserialize;
70use url::Url;
71
72use self::ConfigValue as CV;
73use crate::core::shell::Verbosity;
74use crate::core::{nightly_features_allowed, CliUnstable, Shell, SourceId, Workspace};
75use crate::ops;
76use crate::util::errors::{CargoResult, CargoResultExt};
77use crate::util::toml as cargo_toml;
78use crate::util::{paths, validate_package_name};
79use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
80
81mod de;
82use de::Deserializer;
83
84mod value;
85pub use value::{Definition, OptValue, Value};
86
87mod key;
88use key::ConfigKey;
89
90mod path;
91pub use path::{ConfigRelativePath, PathAndArgs};
92
93mod target;
94pub use target::{TargetCfgConfig, TargetConfig};
95
96// Helper macro for creating typed access methods.
97macro_rules! get_value_typed {
98    ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
99        /// Low-level private method for getting a config value as an OptValue.
100        fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
101            let cv = self.get_cv(key)?;
102            let env = self.get_env::<$ty>(key)?;
103            match (cv, env) {
104                (Some(CV::$variant(val, definition)), Some(env)) => {
105                    if definition.is_higher_priority(&env.definition) {
106                        Ok(Some(Value { val, definition }))
107                    } else {
108                        Ok(Some(env))
109                    }
110                }
111                (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
112                (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
113                (None, Some(env)) => Ok(Some(env)),
114                (None, None) => Ok(None),
115            }
116        }
117    };
118}
119
120/// Configuration information for cargo. This is not specific to a build, it is information
121/// relating to cargo itself.
122#[derive(Debug)]
123pub struct Config {
124    /// The location of the user's 'home' directory. OS-dependent.
125    home_path: Filesystem,
126    /// Information about how to write messages to the shell
127    shell: RefCell<Shell>,
128    /// A collection of configuration options
129    values: LazyCell<HashMap<String, ConfigValue>>,
130    /// CLI config values, passed in via `configure`.
131    cli_config: Option<Vec<String>>,
132    /// The current working directory of cargo
133    cwd: PathBuf,
134    /// The location of the cargo executable (path to current process)
135    cargo_exe: LazyCell<PathBuf>,
136    /// The location of the rustdoc executable
137    rustdoc: LazyCell<PathBuf>,
138    /// Whether we are printing extra verbose messages
139    extra_verbose: bool,
140    /// `frozen` is the same as `locked`, but additionally will not access the
141    /// network to determine if the lock file is out-of-date.
142    frozen: bool,
143    /// `locked` is set if we should not update lock files. If the lock file
144    /// is missing, or needs to be updated, an error is produced.
145    locked: bool,
146    /// `offline` is set if we should never access the network, but otherwise
147    /// continue operating if possible.
148    offline: bool,
149    /// A global static IPC control mechanism (used for managing parallel builds)
150    jobserver: Option<jobserver::Client>,
151    /// Cli flags of the form "-Z something"
152    unstable_flags: CliUnstable,
153    /// A handle on curl easy mode for http calls
154    easy: LazyCell<RefCell<Easy>>,
155    /// Cache of the `SourceId` for crates.io
156    crates_io_source_id: LazyCell<SourceId>,
157    /// If false, don't cache `rustc --version --verbose` invocations
158    cache_rustc_info: bool,
159    /// Creation time of this config, used to output the total build time
160    creation_time: Instant,
161    /// Target Directory via resolved Cli parameter
162    target_dir: Option<Filesystem>,
163    /// Environment variables, separated to assist testing.
164    env: HashMap<String, String>,
165    /// Tracks which sources have been updated to avoid multiple updates.
166    updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
167    /// Lock, if held, of the global package cache along with the number of
168    /// acquisitions so far.
169    package_cache_lock: RefCell<Option<(Option<FileLock>, usize)>>,
170    /// Cached configuration parsed by Cargo
171    http_config: LazyCell<CargoHttpConfig>,
172    net_config: LazyCell<CargoNetConfig>,
173    build_config: LazyCell<CargoBuildConfig>,
174    target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
175}
176
177impl Config {
178    /// Creates a new config instance.
179    ///
180    /// This is typically used for tests or other special cases. `default` is
181    /// preferred otherwise.
182    ///
183    /// This does only minimal initialization. In particular, it does not load
184    /// any config files from disk. Those will be loaded lazily as-needed.
185    pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> Config {
186        static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
187        static INIT: Once = Once::new();
188
189        // This should be called early on in the process, so in theory the
190        // unsafety is ok here. (taken ownership of random fds)
191        INIT.call_once(|| unsafe {
192            if let Some(client) = jobserver::Client::from_env() {
193                GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
194            }
195        });
196
197        let env: HashMap<_, _> = env::vars_os()
198            .filter_map(|(k, v)| {
199                // Ignore any key/values that are not valid Unicode.
200                match (k.into_string(), v.into_string()) {
201                    (Ok(k), Ok(v)) => Some((k, v)),
202                    _ => None,
203                }
204            })
205            .collect();
206
207        let cache_rustc_info = match env.get("CARGO_CACHE_RUSTC_INFO") {
208            Some(cache) => cache != "0",
209            _ => true,
210        };
211
212        Config {
213            home_path: Filesystem::new(homedir),
214            shell: RefCell::new(shell),
215            cwd,
216            values: LazyCell::new(),
217            cli_config: None,
218            cargo_exe: LazyCell::new(),
219            rustdoc: LazyCell::new(),
220            extra_verbose: false,
221            frozen: false,
222            locked: false,
223            offline: false,
224            jobserver: unsafe {
225                if GLOBAL_JOBSERVER.is_null() {
226                    None
227                } else {
228                    Some((*GLOBAL_JOBSERVER).clone())
229                }
230            },
231            unstable_flags: CliUnstable::default(),
232            easy: LazyCell::new(),
233            crates_io_source_id: LazyCell::new(),
234            cache_rustc_info,
235            creation_time: Instant::now(),
236            target_dir: None,
237            env,
238            updated_sources: LazyCell::new(),
239            package_cache_lock: RefCell::new(None),
240            http_config: LazyCell::new(),
241            net_config: LazyCell::new(),
242            build_config: LazyCell::new(),
243            target_cfgs: LazyCell::new(),
244        }
245    }
246
247    /// Creates a new Config instance, with all default settings.
248    ///
249    /// This does only minimal initialization. In particular, it does not load
250    /// any config files from disk. Those will be loaded lazily as-needed.
251    pub fn default() -> CargoResult<Config> {
252        let shell = Shell::new();
253        let cwd =
254            env::current_dir().chain_err(|| "couldn't get the current directory of the process")?;
255        let homedir = homedir(&cwd).ok_or_else(|| {
256            anyhow!(
257                "Cargo couldn't find your home directory. \
258                 This probably means that $HOME was not set."
259            )
260        })?;
261        Ok(Config::new(shell, cwd, homedir))
262    }
263
264    /// Gets the user's Cargo home directory (OS-dependent).
265    pub fn home(&self) -> &Filesystem {
266        &self.home_path
267    }
268
269    /// Gets the Cargo Git directory (`<cargo_home>/git`).
270    pub fn git_path(&self) -> Filesystem {
271        self.home_path.join("git")
272    }
273
274    /// Gets the Cargo registry index directory (`<cargo_home>/registry/index`).
275    pub fn registry_index_path(&self) -> Filesystem {
276        self.home_path.join("registry").join("index")
277    }
278
279    /// Gets the Cargo registry cache directory (`<cargo_home>/registry/path`).
280    pub fn registry_cache_path(&self) -> Filesystem {
281        self.home_path.join("registry").join("cache")
282    }
283
284    /// Gets the Cargo registry source directory (`<cargo_home>/registry/src`).
285    pub fn registry_source_path(&self) -> Filesystem {
286        self.home_path.join("registry").join("src")
287    }
288
289    /// Gets the default Cargo registry.
290    pub fn default_registry(&self) -> CargoResult<Option<String>> {
291        Ok(match self.get_string("registry.default")? {
292            Some(registry) => Some(registry.val),
293            None => None,
294        })
295    }
296
297    /// Gets a reference to the shell, e.g., for writing error messages.
298    pub fn shell(&self) -> RefMut<'_, Shell> {
299        self.shell.borrow_mut()
300    }
301
302    /// Gets the path to the `rustdoc` executable.
303    pub fn rustdoc(&self) -> CargoResult<&Path> {
304        self.rustdoc
305            .try_borrow_with(|| Ok(self.get_tool("rustdoc", &self.build_config()?.rustdoc)))
306            .map(AsRef::as_ref)
307    }
308
309    /// Gets the path to the `rustc` executable.
310    pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
311        let cache_location = ws.map(|ws| {
312            ws.target_dir()
313                .join(".rustc_info.json")
314                .into_path_unlocked()
315        });
316        let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
317        let rustc_workspace_wrapper = self.maybe_get_tool(
318            "rustc_workspace_wrapper",
319            &self.build_config()?.rustc_workspace_wrapper,
320        );
321
322        if !self.cli_unstable().unstable_options && rustc_workspace_wrapper.is_some() {
323            bail!("Usage of `RUSTC_WORKSPACE_WRAPPER` requires `-Z unstable-options`")
324        }
325
326        Rustc::new(
327            self.get_tool("rustc", &self.build_config()?.rustc),
328            wrapper,
329            rustc_workspace_wrapper,
330            &self
331                .home()
332                .join("bin")
333                .join("rustc")
334                .into_path_unlocked()
335                .with_extension(env::consts::EXE_EXTENSION),
336            if self.cache_rustc_info {
337                cache_location
338            } else {
339                None
340            },
341        )
342    }
343
344    /// Gets the path to the `cargo` executable.
345    pub fn cargo_exe(&self) -> CargoResult<&Path> {
346        self.cargo_exe
347            .try_borrow_with(|| {
348                fn from_current_exe() -> CargoResult<PathBuf> {
349                    // Try fetching the path to `cargo` using `env::current_exe()`.
350                    // The method varies per operating system and might fail; in particular,
351                    // it depends on `/proc` being mounted on Linux, and some environments
352                    // (like containers or chroots) may not have that available.
353                    let exe = env::current_exe()?.canonicalize()?;
354                    Ok(exe)
355                }
356
357                fn from_argv() -> CargoResult<PathBuf> {
358                    // Grab `argv[0]` and attempt to resolve it to an absolute path.
359                    // If `argv[0]` has one component, it must have come from a `PATH` lookup,
360                    // so probe `PATH` in that case.
361                    // Otherwise, it has multiple components and is either:
362                    // - a relative path (e.g., `./cargo`, `target/debug/cargo`), or
363                    // - an absolute path (e.g., `/usr/local/bin/cargo`).
364                    // In either case, `Path::canonicalize` will return the full absolute path
365                    // to the target if it exists.
366                    let argv0 = env::args_os()
367                        .map(PathBuf::from)
368                        .next()
369                        .ok_or_else(|| anyhow!("no argv[0]"))?;
370                    paths::resolve_executable(&argv0)
371                }
372
373                let exe = from_current_exe()
374                    .or_else(|_| from_argv())
375                    .chain_err(|| "couldn't get the path to cargo executable")?;
376                Ok(exe)
377            })
378            .map(AsRef::as_ref)
379    }
380
381    /// Which package sources have been updated, used to ensure it is only done once.
382    pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
383        self.updated_sources
384            .borrow_with(|| RefCell::new(HashSet::new()))
385            .borrow_mut()
386    }
387
388    /// Gets all config values from disk.
389    ///
390    /// This will lazy-load the values as necessary. Callers are responsible
391    /// for checking environment variables. Callers outside of the `config`
392    /// module should avoid using this.
393    pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
394        self.values.try_borrow_with(|| self.load_values())
395    }
396
397    /// Gets a mutable copy of the on-disk config values.
398    ///
399    /// This requires the config values to already have been loaded. This
400    /// currently only exists for `cargo vendor` to remove the `source`
401    /// entries. This doesn't respect environment variables. You should avoid
402    /// using this if possible.
403    pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
404        match self.values.borrow_mut() {
405            Some(map) => Ok(map),
406            None => bail!("config values not loaded yet"),
407        }
408    }
409
410    // Note: this is used by RLS, not Cargo.
411    pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
412        if self.values.borrow().is_some() {
413            bail!("config values already found")
414        }
415        match self.values.fill(values) {
416            Ok(()) => Ok(()),
417            Err(_) => bail!("could not fill values"),
418        }
419    }
420
421    /// Reloads on-disk configuration values, starting at the given path and
422    /// walking up its ancestors.
423    pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
424        let values = self.load_values_from(path.as_ref())?;
425        self.values.replace(values);
426        self.merge_cli_args()?;
427        Ok(())
428    }
429
430    /// The current working directory.
431    pub fn cwd(&self) -> &Path {
432        &self.cwd
433    }
434
435    /// The `target` output directory to use.
436    ///
437    /// Returns `None` if the user has not chosen an explicit directory.
438    ///
439    /// Callers should prefer `Workspace::target_dir` instead.
440    pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
441        if let Some(dir) = &self.target_dir {
442            Ok(Some(dir.clone()))
443        } else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
444            Ok(Some(Filesystem::new(self.cwd.join(dir))))
445        } else if let Some(val) = &self.build_config()?.target_dir {
446            let val = val.resolve_path(self);
447            Ok(Some(Filesystem::new(val)))
448        } else {
449            Ok(None)
450        }
451    }
452
453    /// Get a configuration value by key.
454    ///
455    /// This does NOT look at environment variables, the caller is responsible
456    /// for that.
457    fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
458        log::trace!("get cv {:?}", key);
459        let vals = self.values()?;
460        let mut parts = key.parts().enumerate();
461        let mut val = match vals.get(parts.next().unwrap().1) {
462            Some(val) => val,
463            None => return Ok(None),
464        };
465        for (i, part) in parts {
466            match val {
467                CV::Table(map, _) => {
468                    val = match map.get(part) {
469                        Some(val) => val,
470                        None => return Ok(None),
471                    }
472                }
473                CV::Integer(_, def)
474                | CV::String(_, def)
475                | CV::List(_, def)
476                | CV::Boolean(_, def) => {
477                    let key_so_far: Vec<&str> = key.parts().take(i).collect();
478                    bail!(
479                        "expected table for configuration key `{}`, \
480                         but found {} in {}",
481                        // This join doesn't handle quoting properly.
482                        key_so_far.join("."),
483                        val.desc(),
484                        def
485                    )
486                }
487            }
488        }
489        Ok(Some(val.clone()))
490    }
491
492    /// Helper primarily for testing.
493    pub fn set_env(&mut self, env: HashMap<String, String>) {
494        self.env = env;
495    }
496
497    fn get_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
498    where
499        T: FromStr,
500        <T as FromStr>::Err: fmt::Display,
501    {
502        match self.env.get(key.as_env_key()) {
503            Some(value) => {
504                let definition = Definition::Environment(key.as_env_key().to_string());
505                Ok(Some(Value {
506                    val: value
507                        .parse()
508                        .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
509                    definition,
510                }))
511            }
512            None => Ok(None),
513        }
514    }
515
516    fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> bool {
517        if self.env.contains_key(key.as_env_key()) {
518            return true;
519        }
520        // See ConfigMapAccess for a description of this.
521        if env_prefix_ok {
522            let env_prefix = format!("{}_", key.as_env_key());
523            if self.env.keys().any(|k| k.starts_with(&env_prefix)) {
524                return true;
525            }
526        }
527        if let Ok(o_cv) = self.get_cv(key) {
528            if o_cv.is_some() {
529                return true;
530            }
531        }
532        false
533    }
534
535    /// Get a string config value.
536    ///
537    /// See `get` for more details.
538    pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
539        self.get::<Option<Value<String>>>(key)
540    }
541
542    /// Get a config value that is expected to be a path.
543    ///
544    /// This returns a relative path if the value does not contain any
545    /// directory separators. See `ConfigRelativePath::resolve_program` for
546    /// more details.
547    pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
548        self.get::<Option<Value<ConfigRelativePath>>>(key).map(|v| {
549            v.map(|v| Value {
550                val: v.val.resolve_program(self),
551                definition: v.definition,
552            })
553        })
554    }
555
556    fn string_to_path(&self, value: String, definition: &Definition) -> PathBuf {
557        let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
558        if is_path {
559            definition.root(self).join(value)
560        } else {
561            // A pathless name.
562            PathBuf::from(value)
563        }
564    }
565
566    /// Get a list of strings.
567    ///
568    /// DO NOT USE outside of the config module. `pub` will be removed in the
569    /// future.
570    ///
571    /// NOTE: this does **not** support environment variables. Use `get` instead
572    /// if you want that.
573    pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
574        let key = ConfigKey::from_str(key);
575        self._get_list(&key)
576    }
577
578    fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
579        match self.get_cv(key)? {
580            Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
581            Some(val) => self.expected("list", key, &val),
582            None => Ok(None),
583        }
584    }
585
586    /// Helper for StringList type to get something that is a string or list.
587    fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
588        let mut res = Vec::new();
589        match self.get_cv(key)? {
590            Some(CV::List(val, _def)) => res.extend(val),
591            Some(CV::String(val, def)) => {
592                let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
593                res.extend(split_vs);
594            }
595            Some(val) => {
596                return self.expected("string or array of strings", key, &val);
597            }
598            None => {}
599        }
600
601        self.get_env_list(key, &mut res)?;
602        Ok(res)
603    }
604
605    /// Internal method for getting an environment variable as a list.
606    fn get_env_list(
607        &self,
608        key: &ConfigKey,
609        output: &mut Vec<(String, Definition)>,
610    ) -> CargoResult<()> {
611        let env_val = match self.env.get(key.as_env_key()) {
612            Some(v) => v,
613            None => return Ok(()),
614        };
615
616        let def = Definition::Environment(key.as_env_key().to_string());
617        if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
618            // Parse an environment string as a TOML array.
619            let toml_s = format!("value={}", env_val);
620            let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| {
621                ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
622            })?;
623            let values = toml_v
624                .as_table()
625                .unwrap()
626                .get("value")
627                .unwrap()
628                .as_array()
629                .expect("env var was not array");
630            for value in values {
631                // TODO: support other types.
632                let s = value.as_str().ok_or_else(|| {
633                    ConfigError::new(
634                        format!("expected string, found {}", value.type_str()),
635                        def.clone(),
636                    )
637                })?;
638                output.push((s.to_string(), def.clone()));
639            }
640        } else {
641            output.extend(
642                env_val
643                    .split_whitespace()
644                    .map(|s| (s.to_string(), def.clone())),
645            );
646        }
647        Ok(())
648    }
649
650    /// Low-level method for getting a config value as a `OptValue<HashMap<String, CV>>`.
651    ///
652    /// NOTE: This does not read from env. The caller is responsible for that.
653    fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
654        match self.get_cv(key)? {
655            Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
656            Some(val) => self.expected("table", key, &val),
657            None => Ok(None),
658        }
659    }
660
661    get_value_typed! {get_integer, i64, Integer, "an integer"}
662    get_value_typed! {get_bool, bool, Boolean, "true/false"}
663    get_value_typed! {get_string_priv, String, String, "a string"}
664
665    /// Generate an error when the given value is the wrong type.
666    fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
667        val.expected(ty, &key.to_string())
668            .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
669    }
670
671    /// Update the Config instance based on settings typically passed in on
672    /// the command-line.
673    ///
674    /// This may also load the config from disk if it hasn't already been
675    /// loaded.
676    pub fn configure(
677        &mut self,
678        verbose: u32,
679        quiet: bool,
680        color: Option<&str>,
681        frozen: bool,
682        locked: bool,
683        offline: bool,
684        target_dir: &Option<PathBuf>,
685        unstable_flags: &[String],
686        cli_config: &[String],
687    ) -> CargoResult<()> {
688        self.unstable_flags.parse(unstable_flags)?;
689        if !cli_config.is_empty() {
690            self.unstable_flags.fail_if_stable_opt("--config", 6699)?;
691            self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
692            self.merge_cli_args()?;
693        }
694        let extra_verbose = verbose >= 2;
695        let verbose = verbose != 0;
696
697        #[derive(Deserialize, Default)]
698        struct TermConfig {
699            verbose: Option<bool>,
700            color: Option<String>,
701        }
702
703        // Ignore errors in the configuration files.
704        let term = self.get::<TermConfig>("term").unwrap_or_default();
705
706        let color = color.or_else(|| term.color.as_deref());
707
708        let verbosity = match (verbose, term.verbose, quiet) {
709            (true, _, false) | (_, Some(true), false) => Verbosity::Verbose,
710
711            // Command line takes precedence over configuration, so ignore the
712            // configuration..
713            (false, _, true) => Verbosity::Quiet,
714
715            // Can't pass both at the same time on the command line regardless
716            // of configuration.
717            (true, _, true) => {
718                bail!("cannot set both --verbose and --quiet");
719            }
720
721            (false, _, false) => Verbosity::Normal,
722        };
723
724        let cli_target_dir = match target_dir.as_ref() {
725            Some(dir) => Some(Filesystem::new(dir.clone())),
726            None => None,
727        };
728
729        self.shell().set_verbosity(verbosity);
730        self.shell().set_color_choice(color)?;
731        self.extra_verbose = extra_verbose;
732        self.frozen = frozen;
733        self.locked = locked;
734        self.offline = offline
735            || self
736                .net_config()
737                .ok()
738                .and_then(|n| n.offline)
739                .unwrap_or(false);
740        self.target_dir = cli_target_dir;
741
742        if nightly_features_allowed() {
743            if let Some(val) = self.get::<Option<bool>>("unstable.mtime_on_use")? {
744                self.unstable_flags.mtime_on_use |= val;
745            }
746        }
747
748        Ok(())
749    }
750
751    pub fn cli_unstable(&self) -> &CliUnstable {
752        &self.unstable_flags
753    }
754
755    pub fn extra_verbose(&self) -> bool {
756        self.extra_verbose
757    }
758
759    pub fn network_allowed(&self) -> bool {
760        !self.frozen() && !self.offline()
761    }
762
763    pub fn offline(&self) -> bool {
764        self.offline
765    }
766
767    pub fn frozen(&self) -> bool {
768        self.frozen
769    }
770
771    pub fn lock_update_allowed(&self) -> bool {
772        !self.frozen && !self.locked
773    }
774
775    /// Loads configuration from the filesystem.
776    pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
777        self.load_values_from(&self.cwd)
778    }
779
780    fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
781        // This definition path is ignored, this is just a temporary container
782        // representing the entire file.
783        let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
784        let home = self.home_path.clone().into_path_unlocked();
785
786        self.walk_tree(path, &home, |path| {
787            let value = self.load_file(path)?;
788            cfg.merge(value, false)
789                .chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?;
790            Ok(())
791        })
792        .chain_err(|| "could not load Cargo configuration")?;
793
794        match cfg {
795            CV::Table(map, _) => Ok(map),
796            _ => unreachable!(),
797        }
798    }
799
800    fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
801        let mut seen = HashSet::new();
802        self._load_file(path, &mut seen)
803    }
804
805    fn _load_file(&self, path: &Path, seen: &mut HashSet<PathBuf>) -> CargoResult<ConfigValue> {
806        if !seen.insert(path.to_path_buf()) {
807            bail!(
808                "config `include` cycle detected with path `{}`",
809                path.display()
810            );
811        }
812        let contents = fs::read_to_string(path)
813            .chain_err(|| format!("failed to read configuration file `{}`", path.display()))?;
814        let toml = cargo_toml::parse(&contents, path, self)
815            .chain_err(|| format!("could not parse TOML configuration in `{}`", path.display()))?;
816        let value = CV::from_toml(Definition::Path(path.to_path_buf()), toml).chain_err(|| {
817            format!(
818                "failed to load TOML configuration from `{}`",
819                path.display()
820            )
821        })?;
822        let value = self.load_includes(value, seen)?;
823        Ok(value)
824    }
825
826    /// Load any `include` files listed in the given `value`.
827    ///
828    /// Returns `value` with the given include files merged into it.
829    ///
830    /// `seen` is used to check for cyclic includes.
831    fn load_includes(&self, mut value: CV, seen: &mut HashSet<PathBuf>) -> CargoResult<CV> {
832        // Get the list of files to load.
833        let (includes, def) = match &mut value {
834            CV::Table(table, _def) => match table.remove("include") {
835                Some(CV::String(s, def)) => (vec![(s, def.clone())], def),
836                Some(CV::List(list, def)) => (list, def),
837                Some(other) => bail!(
838                    "`include` expected a string or list, but found {} in `{}`",
839                    other.desc(),
840                    other.definition()
841                ),
842                None => {
843                    return Ok(value);
844                }
845            },
846            _ => unreachable!(),
847        };
848        // Check unstable.
849        if !self.cli_unstable().config_include {
850            self.shell().warn(format!("config `include` in `{}` ignored, the -Zconfig-include command-line flag is required",
851                def))?;
852            return Ok(value);
853        }
854        // Accumulate all values here.
855        let mut root = CV::Table(HashMap::new(), value.definition().clone());
856        for (path, def) in includes {
857            let abs_path = match &def {
858                Definition::Path(p) => p.parent().unwrap().join(&path),
859                Definition::Environment(_) | Definition::Cli => self.cwd().join(&path),
860            };
861            self._load_file(&abs_path, seen)
862                .and_then(|include| root.merge(include, true))
863                .chain_err(|| format!("failed to load config include `{}` from `{}`", path, def))?;
864        }
865        root.merge(value, true)?;
866        Ok(root)
867    }
868
869    /// Add config arguments passed on the command line.
870    fn merge_cli_args(&mut self) -> CargoResult<()> {
871        let cli_args = match &self.cli_config {
872            Some(cli_args) => cli_args,
873            None => return Ok(()),
874        };
875        let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli);
876        for arg in cli_args {
877            let arg_as_path = self.cwd.join(arg);
878            let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
879                // --config path_to_file
880                let str_path = arg_as_path
881                    .to_str()
882                    .ok_or_else(|| {
883                        anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
884                    })?
885                    .to_string();
886                let mut map = HashMap::new();
887                let value = CV::String(str_path, Definition::Cli);
888                map.insert("include".to_string(), value);
889                CV::Table(map, Definition::Cli)
890            } else {
891                // TODO: This should probably use a more narrow parser, reject
892                // comments, blank lines, [headers], etc.
893                let toml_v: toml::Value = toml::de::from_str(arg)
894                    .chain_err(|| format!("failed to parse --config argument `{}`", arg))?;
895                let toml_table = toml_v.as_table().unwrap();
896                if toml_table.len() != 1 {
897                    bail!(
898                        "--config argument `{}` expected exactly one key=value pair, got {} keys",
899                        arg,
900                        toml_table.len()
901                    );
902                }
903                CV::from_toml(Definition::Cli, toml_v)
904                    .chain_err(|| format!("failed to convert --config argument `{}`", arg))?
905            };
906            let mut seen = HashSet::new();
907            let tmp_table = self
908                .load_includes(tmp_table, &mut seen)
909                .chain_err(|| "failed to load --config include".to_string())?;
910            loaded_args
911                .merge(tmp_table, true)
912                .chain_err(|| format!("failed to merge --config argument `{}`", arg))?;
913        }
914        // Force values to be loaded.
915        let _ = self.values()?;
916        let values = self.values_mut()?;
917        let loaded_map = match loaded_args {
918            CV::Table(table, _def) => table,
919            _ => unreachable!(),
920        };
921        for (key, value) in loaded_map.into_iter() {
922            match values.entry(key) {
923                Vacant(entry) => {
924                    entry.insert(value);
925                }
926                Occupied(mut entry) => entry.get_mut().merge(value, true).chain_err(|| {
927                    format!(
928                        "failed to merge --config key `{}` into `{}`",
929                        entry.key(),
930                        entry.get().definition(),
931                    )
932                })?,
933            };
934        }
935        Ok(())
936    }
937
938    /// The purpose of this function is to aid in the transition to using
939    /// .toml extensions on Cargo's config files, which were historically not used.
940    /// Both 'config.toml' and 'credentials.toml' should be valid with or without extension.
941    /// When both exist, we want to prefer the one without an extension for
942    /// backwards compatibility, but warn the user appropriately.
943    fn get_file_path(
944        &self,
945        dir: &Path,
946        filename_without_extension: &str,
947        warn: bool,
948    ) -> CargoResult<Option<PathBuf>> {
949        let possible = dir.join(filename_without_extension);
950        let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
951
952        if fs::metadata(&possible).is_ok() {
953            if warn && fs::metadata(&possible_with_extension).is_ok() {
954                // We don't want to print a warning if the version
955                // without the extension is just a symlink to the version
956                // WITH an extension, which people may want to do to
957                // support multiple Cargo versions at once and not
958                // get a warning.
959                let skip_warning = if let Ok(target_path) = fs::read_link(&possible) {
960                    target_path == possible_with_extension
961                } else {
962                    false
963                };
964
965                if !skip_warning {
966                    self.shell().warn(format!(
967                        "Both `{}` and `{}` exist. Using `{}`",
968                        possible.display(),
969                        possible_with_extension.display(),
970                        possible.display()
971                    ))?;
972                }
973            }
974
975            Ok(Some(possible))
976        } else if fs::metadata(&possible_with_extension).is_ok() {
977            Ok(Some(possible_with_extension))
978        } else {
979            Ok(None)
980        }
981    }
982
983    fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
984    where
985        F: FnMut(&Path) -> CargoResult<()>,
986    {
987        let mut stash: HashSet<PathBuf> = HashSet::new();
988
989        for current in paths::ancestors(pwd) {
990            if let Some(path) = self.get_file_path(&current.join(".cargo"), "config", true)? {
991                walk(&path)?;
992                stash.insert(path);
993            }
994        }
995
996        // Once we're done, also be sure to walk the home directory even if it's not
997        // in our history to be sure we pick up that standard location for
998        // information.
999        if let Some(path) = self.get_file_path(home, "config", true)? {
1000            if !stash.contains(&path) {
1001                walk(&path)?;
1002            }
1003        }
1004
1005        Ok(())
1006    }
1007
1008    /// Gets the index for a registry.
1009    pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1010        validate_package_name(registry, "registry name", "")?;
1011        Ok(
1012            match self.get_string(&format!("registries.{}.index", registry))? {
1013                Some(index) => self.resolve_registry_index(index)?,
1014                None => bail!("No index found for registry: `{}`", registry),
1015            },
1016        )
1017    }
1018
1019    /// Gets the index for the default registry.
1020    pub fn get_default_registry_index(&self) -> CargoResult<Option<Url>> {
1021        Ok(match self.get_string("registry.index")? {
1022            Some(index) => Some(self.resolve_registry_index(index)?),
1023            None => None,
1024        })
1025    }
1026
1027    fn resolve_registry_index(&self, index: Value<String>) -> CargoResult<Url> {
1028        let base = index
1029            .definition
1030            .root(self)
1031            .join("truncated-by-url_with_base");
1032        // Parse val to check it is a URL, not a relative path without a protocol.
1033        let _parsed = index.val.into_url()?;
1034        let url = index.val.into_url_with_base(Some(&*base))?;
1035        if url.password().is_some() {
1036            bail!("Registry URLs may not contain passwords");
1037        }
1038        Ok(url)
1039    }
1040
1041    /// Loads credentials config from the credentials file, if present.
1042    pub fn load_credentials(&mut self) -> CargoResult<()> {
1043        let home_path = self.home_path.clone().into_path_unlocked();
1044        let credentials = match self.get_file_path(&home_path, "credentials", true)? {
1045            Some(credentials) => credentials,
1046            None => return Ok(()),
1047        };
1048
1049        let mut value = self.load_file(&credentials)?;
1050        // Backwards compatibility for old `.cargo/credentials` layout.
1051        {
1052            let (value_map, def) = match value {
1053                CV::Table(ref mut value, ref def) => (value, def),
1054                _ => unreachable!(),
1055            };
1056
1057            if let Some(token) = value_map.remove("token") {
1058                if let Vacant(entry) = value_map.entry("registry".into()) {
1059                    let mut map = HashMap::new();
1060                    map.insert("token".into(), token);
1061                    let table = CV::Table(map, def.clone());
1062                    entry.insert(table);
1063                }
1064            }
1065        }
1066
1067        if let CV::Table(map, _) = value {
1068            let base_map = self.values_mut()?;
1069            for (k, v) in map {
1070                match base_map.entry(k) {
1071                    Vacant(entry) => {
1072                        entry.insert(v);
1073                    }
1074                    Occupied(mut entry) => {
1075                        entry.get_mut().merge(v, true)?;
1076                    }
1077                }
1078            }
1079        }
1080
1081        Ok(())
1082    }
1083
1084    /// Looks for a path for `tool` in an environment variable or the given config, and returns
1085    /// `None` if it's not present.
1086    fn maybe_get_tool(&self, tool: &str, from_config: &Option<PathBuf>) -> Option<PathBuf> {
1087        let var = tool.to_uppercase();
1088
1089        match env::var_os(&var) {
1090            Some(tool_path) => {
1091                let maybe_relative = match tool_path.to_str() {
1092                    Some(s) => s.contains('/') || s.contains('\\'),
1093                    None => false,
1094                };
1095                let path = if maybe_relative {
1096                    self.cwd.join(tool_path)
1097                } else {
1098                    PathBuf::from(tool_path)
1099                };
1100                Some(path)
1101            }
1102
1103            None => from_config.clone(),
1104        }
1105    }
1106
1107    /// Looks for a path for `tool` in an environment variable or config path, defaulting to `tool`
1108    /// as a path.
1109    fn get_tool(&self, tool: &str, from_config: &Option<PathBuf>) -> PathBuf {
1110        self.maybe_get_tool(tool, from_config)
1111            .unwrap_or_else(|| PathBuf::from(tool))
1112    }
1113
1114    pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1115        self.jobserver.as_ref()
1116    }
1117
1118    pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1119        let http = self
1120            .easy
1121            .try_borrow_with(|| ops::http_handle(self).map(RefCell::new))?;
1122        {
1123            let mut http = http.borrow_mut();
1124            http.reset();
1125            let timeout = ops::configure_http_handle(self, &mut http)?;
1126            timeout.configure(&mut http)?;
1127        }
1128        Ok(http)
1129    }
1130
1131    pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1132        self.http_config
1133            .try_borrow_with(|| Ok(self.get::<CargoHttpConfig>("http")?))
1134    }
1135
1136    pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1137        self.net_config
1138            .try_borrow_with(|| Ok(self.get::<CargoNetConfig>("net")?))
1139    }
1140
1141    pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1142        self.build_config
1143            .try_borrow_with(|| Ok(self.get::<CargoBuildConfig>("build")?))
1144    }
1145
1146    /// Returns a list of [target.'cfg()'] tables.
1147    ///
1148    /// The list is sorted by the table name.
1149    pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1150        self.target_cfgs
1151            .try_borrow_with(|| target::load_target_cfgs(self))
1152    }
1153
1154    /// Returns the `[target]` table definition for the given target triple.
1155    pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1156        target::load_target_triple(self, target)
1157    }
1158
1159    pub fn crates_io_source_id<F>(&self, f: F) -> CargoResult<SourceId>
1160    where
1161        F: FnMut() -> CargoResult<SourceId>,
1162    {
1163        Ok(*(self.crates_io_source_id.try_borrow_with(f)?))
1164    }
1165
1166    pub fn creation_time(&self) -> Instant {
1167        self.creation_time
1168    }
1169
1170    /// Retrieves a config variable.
1171    ///
1172    /// This supports most serde `Deserialize` types. Examples:
1173    ///
1174    /// ```rust,ignore
1175    /// let v: Option<u32> = config.get("some.nested.key")?;
1176    /// let v: Option<MyStruct> = config.get("some.key")?;
1177    /// let v: Option<HashMap<String, MyStruct>> = config.get("foo")?;
1178    /// ```
1179    ///
1180    /// The key may be a dotted key, but this does NOT support TOML key
1181    /// quoting. Avoid key components that may have dots. For example,
1182    /// `foo.'a.b'.bar" does not work if you try to fetch `foo.'a.b'". You can
1183    /// fetch `foo` if it is a map, though.
1184    pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1185        let d = Deserializer {
1186            config: self,
1187            key: ConfigKey::from_str(key),
1188            env_prefix_ok: true,
1189        };
1190        T::deserialize(d).map_err(|e| e.into())
1191    }
1192
1193    pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path {
1194        let ret = f.as_path_unlocked();
1195        assert!(
1196            self.package_cache_lock.borrow().is_some(),
1197            "package cache lock is not currently held, Cargo forgot to call \
1198             `acquire_package_cache_lock` before we got to this stack frame",
1199        );
1200        assert!(ret.starts_with(self.home_path.as_path_unlocked()));
1201        ret
1202    }
1203
1204    /// Acquires an exclusive lock on the global "package cache"
1205    ///
1206    /// This lock is global per-process and can be acquired recursively. An RAII
1207    /// structure is returned to release the lock, and if this process
1208    /// abnormally terminates the lock is also released.
1209    pub fn acquire_package_cache_lock(&self) -> CargoResult<PackageCacheLock<'_>> {
1210        let mut slot = self.package_cache_lock.borrow_mut();
1211        match *slot {
1212            // We've already acquired the lock in this process, so simply bump
1213            // the count and continue.
1214            Some((_, ref mut cnt)) => {
1215                *cnt += 1;
1216            }
1217            None => {
1218                let path = ".package-cache";
1219                let desc = "package cache";
1220
1221                // First, attempt to open an exclusive lock which is in general
1222                // the purpose of this lock!
1223                //
1224                // If that fails because of a readonly filesystem or a
1225                // permission error, though, then we don't really want to fail
1226                // just because of this. All files that this lock protects are
1227                // in subfolders, so they're assumed by Cargo to also be
1228                // readonly or have invalid permissions for us to write to. If
1229                // that's the case, then we don't really need to grab a lock in
1230                // the first place here.
1231                //
1232                // Despite this we attempt to grab a readonly lock. This means
1233                // that if our read-only folder is shared read-write with
1234                // someone else on the system we should synchronize with them,
1235                // but if we can't even do that then we did our best and we just
1236                // keep on chugging elsewhere.
1237                match self.home_path.open_rw(path, self, desc) {
1238                    Ok(lock) => *slot = Some((Some(lock), 1)),
1239                    Err(e) => {
1240                        if maybe_readonly(&e) {
1241                            let lock = self.home_path.open_ro(path, self, desc).ok();
1242                            *slot = Some((lock, 1));
1243                            return Ok(PackageCacheLock(self));
1244                        }
1245
1246                        Err(e).chain_err(|| "failed to acquire package cache lock")?;
1247                    }
1248                }
1249            }
1250        }
1251        return Ok(PackageCacheLock(self));
1252
1253        fn maybe_readonly(err: &anyhow::Error) -> bool {
1254            err.chain().any(|err| {
1255                if let Some(io) = err.downcast_ref::<io::Error>() {
1256                    if io.kind() == io::ErrorKind::PermissionDenied {
1257                        return true;
1258                    }
1259
1260                    #[cfg(unix)]
1261                    return io.raw_os_error() == Some(libc::EROFS);
1262                }
1263
1264                false
1265            })
1266        }
1267    }
1268
1269    pub fn release_package_cache_lock(&self) {}
1270}
1271
1272/// Internal error for serde errors.
1273#[derive(Debug)]
1274pub struct ConfigError {
1275    error: anyhow::Error,
1276    definition: Option<Definition>,
1277}
1278
1279impl ConfigError {
1280    fn new(message: String, definition: Definition) -> ConfigError {
1281        ConfigError {
1282            error: anyhow::Error::msg(message),
1283            definition: Some(definition),
1284        }
1285    }
1286
1287    fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
1288        ConfigError {
1289            error: anyhow!(
1290                "`{}` expected {}, but found a {}",
1291                key,
1292                expected,
1293                found.desc()
1294            ),
1295            definition: Some(found.definition().clone()),
1296        }
1297    }
1298
1299    fn missing(key: &ConfigKey) -> ConfigError {
1300        ConfigError {
1301            error: anyhow!("missing config key `{}`", key),
1302            definition: None,
1303        }
1304    }
1305
1306    fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError {
1307        ConfigError {
1308            error: anyhow::Error::from(self)
1309                .context(format!("could not load config key `{}`", key)),
1310            definition: Some(definition),
1311        }
1312    }
1313}
1314
1315impl std::error::Error for ConfigError {
1316    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1317        self.error.source()
1318    }
1319}
1320
1321impl fmt::Display for ConfigError {
1322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1323        if let Some(definition) = &self.definition {
1324            write!(f, "error in {}: {}", definition, self.error)
1325        } else {
1326            self.error.fmt(f)
1327        }
1328    }
1329}
1330
1331impl serde::de::Error for ConfigError {
1332    fn custom<T: fmt::Display>(msg: T) -> Self {
1333        ConfigError {
1334            error: anyhow::Error::msg(msg.to_string()),
1335            definition: None,
1336        }
1337    }
1338}
1339
1340impl From<anyhow::Error> for ConfigError {
1341    fn from(error: anyhow::Error) -> Self {
1342        ConfigError {
1343            error,
1344            definition: None,
1345        }
1346    }
1347}
1348
1349#[derive(Eq, PartialEq, Clone)]
1350pub enum ConfigValue {
1351    Integer(i64, Definition),
1352    String(String, Definition),
1353    List(Vec<(String, Definition)>, Definition),
1354    Table(HashMap<String, ConfigValue>, Definition),
1355    Boolean(bool, Definition),
1356}
1357
1358impl fmt::Debug for ConfigValue {
1359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1360        match self {
1361            CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
1362            CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
1363            CV::String(s, def) => write!(f, "{} (from {})", s, def),
1364            CV::List(list, def) => {
1365                write!(f, "[")?;
1366                for (i, (s, def)) in list.iter().enumerate() {
1367                    if i > 0 {
1368                        write!(f, ", ")?;
1369                    }
1370                    write!(f, "{} (from {})", s, def)?;
1371                }
1372                write!(f, "] (from {})", def)
1373            }
1374            CV::Table(table, _) => write!(f, "{:?}", table),
1375        }
1376    }
1377}
1378
1379impl ConfigValue {
1380    fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
1381        match toml {
1382            toml::Value::String(val) => Ok(CV::String(val, def)),
1383            toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
1384            toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
1385            toml::Value::Array(val) => Ok(CV::List(
1386                val.into_iter()
1387                    .map(|toml| match toml {
1388                        toml::Value::String(val) => Ok((val, def.clone())),
1389                        v => bail!("expected string but found {} in list", v.type_str()),
1390                    })
1391                    .collect::<CargoResult<_>>()?,
1392                def,
1393            )),
1394            toml::Value::Table(val) => Ok(CV::Table(
1395                val.into_iter()
1396                    .map(|(key, value)| {
1397                        let value = CV::from_toml(def.clone(), value)
1398                            .chain_err(|| format!("failed to parse key `{}`", key))?;
1399                        Ok((key, value))
1400                    })
1401                    .collect::<CargoResult<_>>()?,
1402                def,
1403            )),
1404            v => bail!(
1405                "found TOML configuration value of unknown type `{}`",
1406                v.type_str()
1407            ),
1408        }
1409    }
1410
1411    fn into_toml(self) -> toml::Value {
1412        match self {
1413            CV::Boolean(s, _) => toml::Value::Boolean(s),
1414            CV::String(s, _) => toml::Value::String(s),
1415            CV::Integer(i, _) => toml::Value::Integer(i),
1416            CV::List(l, _) => {
1417                toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
1418            }
1419            CV::Table(l, _) => {
1420                toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
1421            }
1422        }
1423    }
1424
1425    /// Merge the given value into self.
1426    ///
1427    /// If `force` is true, primitive (non-container) types will override existing values.
1428    /// If false, the original will be kept and the new value ignored.
1429    ///
1430    /// Container types (tables and arrays) are merged with existing values.
1431    ///
1432    /// Container and non-container types cannot be mixed.
1433    fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
1434        match (self, from) {
1435            (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
1436                let new = mem::replace(new, Vec::new());
1437                old.extend(new.into_iter());
1438            }
1439            (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
1440                let new = mem::replace(new, HashMap::new());
1441                for (key, value) in new {
1442                    match old.entry(key.clone()) {
1443                        Occupied(mut entry) => {
1444                            let new_def = value.definition().clone();
1445                            let entry = entry.get_mut();
1446                            entry.merge(value, force).chain_err(|| {
1447                                format!(
1448                                    "failed to merge key `{}` between \
1449                                     {} and {}",
1450                                    key,
1451                                    entry.definition(),
1452                                    new_def,
1453                                )
1454                            })?;
1455                        }
1456                        Vacant(entry) => {
1457                            entry.insert(value);
1458                        }
1459                    };
1460                }
1461            }
1462            // Allow switching types except for tables or arrays.
1463            (expected @ &mut CV::List(_, _), found)
1464            | (expected @ &mut CV::Table(_, _), found)
1465            | (expected, found @ CV::List(_, _))
1466            | (expected, found @ CV::Table(_, _)) => {
1467                return Err(anyhow!(
1468                    "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
1469                    found.definition(),
1470                    expected.definition(),
1471                    expected.desc(),
1472                    found.desc()
1473                ));
1474            }
1475            (old, mut new) => {
1476                if force || new.definition().is_higher_priority(old.definition()) {
1477                    mem::swap(old, &mut new);
1478                }
1479            }
1480        }
1481
1482        Ok(())
1483    }
1484
1485    pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
1486        match self {
1487            CV::Integer(i, def) => Ok((*i, def)),
1488            _ => self.expected("integer", key),
1489        }
1490    }
1491
1492    pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
1493        match self {
1494            CV::String(s, def) => Ok((s, def)),
1495            _ => self.expected("string", key),
1496        }
1497    }
1498
1499    pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
1500        match self {
1501            CV::Table(table, def) => Ok((table, def)),
1502            _ => self.expected("table", key),
1503        }
1504    }
1505
1506    pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
1507        match self {
1508            CV::List(list, _) => Ok(list),
1509            _ => self.expected("list", key),
1510        }
1511    }
1512
1513    pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
1514        match self {
1515            CV::Boolean(b, def) => Ok((*b, def)),
1516            _ => self.expected("bool", key),
1517        }
1518    }
1519
1520    pub fn desc(&self) -> &'static str {
1521        match *self {
1522            CV::Table(..) => "table",
1523            CV::List(..) => "array",
1524            CV::String(..) => "string",
1525            CV::Boolean(..) => "boolean",
1526            CV::Integer(..) => "integer",
1527        }
1528    }
1529
1530    pub fn definition(&self) -> &Definition {
1531        match self {
1532            CV::Boolean(_, def)
1533            | CV::Integer(_, def)
1534            | CV::String(_, def)
1535            | CV::List(_, def)
1536            | CV::Table(_, def) => def,
1537        }
1538    }
1539
1540    fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
1541        bail!(
1542            "expected a {}, but found a {} for `{}` in {}",
1543            wanted,
1544            self.desc(),
1545            key,
1546            self.definition()
1547        )
1548    }
1549}
1550
1551pub fn homedir(cwd: &Path) -> Option<PathBuf> {
1552    ::home::cargo_home_with_cwd(cwd).ok()
1553}
1554
1555pub fn save_credentials(cfg: &Config, token: String, registry: Option<String>) -> CargoResult<()> {
1556    // If 'credentials.toml' exists, we should write to that, otherwise
1557    // use the legacy 'credentials'. There's no need to print the warning
1558    // here, because it would already be printed at load time.
1559    let home_path = cfg.home_path.clone().into_path_unlocked();
1560    let filename = match cfg.get_file_path(&home_path, "credentials", false)? {
1561        Some(path) => match path.file_name() {
1562            Some(filename) => Path::new(filename).to_owned(),
1563            None => Path::new("credentials").to_owned(),
1564        },
1565        None => Path::new("credentials").to_owned(),
1566    };
1567
1568    let mut file = {
1569        cfg.home_path.create_dir()?;
1570        cfg.home_path
1571            .open_rw(filename, cfg, "credentials' config file")?
1572    };
1573
1574    let (key, mut value) = {
1575        let key = "token".to_string();
1576        let value = ConfigValue::String(token, Definition::Path(file.path().to_path_buf()));
1577        let mut map = HashMap::new();
1578        map.insert(key, value);
1579        let table = CV::Table(map, Definition::Path(file.path().to_path_buf()));
1580
1581        if let Some(registry) = registry.clone() {
1582            let mut map = HashMap::new();
1583            map.insert(registry, table);
1584            (
1585                "registries".into(),
1586                CV::Table(map, Definition::Path(file.path().to_path_buf())),
1587            )
1588        } else {
1589            ("registry".into(), table)
1590        }
1591    };
1592
1593    let mut contents = String::new();
1594    file.read_to_string(&mut contents).chain_err(|| {
1595        format!(
1596            "failed to read configuration file `{}`",
1597            file.path().display()
1598        )
1599    })?;
1600
1601    let mut toml = cargo_toml::parse(&contents, file.path(), cfg)?;
1602
1603    // Move the old token location to the new one.
1604    if let Some(token) = toml.as_table_mut().unwrap().remove("token") {
1605        let mut map = HashMap::new();
1606        map.insert("token".to_string(), token);
1607        toml.as_table_mut()
1608            .unwrap()
1609            .insert("registry".into(), map.into());
1610    }
1611
1612    if registry.is_some() {
1613        if let Some(table) = toml.as_table_mut().unwrap().remove("registries") {
1614            let v = CV::from_toml(Definition::Path(file.path().to_path_buf()), table)?;
1615            value.merge(v, false)?;
1616        }
1617    }
1618    toml.as_table_mut().unwrap().insert(key, value.into_toml());
1619
1620    let contents = toml.to_string();
1621    file.seek(SeekFrom::Start(0))?;
1622    file.write_all(contents.as_bytes())?;
1623    file.file().set_len(contents.len() as u64)?;
1624    set_permissions(file.file(), 0o600)?;
1625
1626    return Ok(());
1627
1628    #[cfg(unix)]
1629    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
1630        use std::os::unix::fs::PermissionsExt;
1631
1632        let mut perms = file.metadata()?.permissions();
1633        perms.set_mode(mode);
1634        file.set_permissions(perms)?;
1635        Ok(())
1636    }
1637
1638    #[cfg(not(unix))]
1639    #[allow(unused)]
1640    fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
1641        Ok(())
1642    }
1643}
1644
1645pub struct PackageCacheLock<'a>(&'a Config);
1646
1647impl Drop for PackageCacheLock<'_> {
1648    fn drop(&mut self) {
1649        let mut slot = self.0.package_cache_lock.borrow_mut();
1650        let (_, cnt) = slot.as_mut().unwrap();
1651        *cnt -= 1;
1652        if *cnt == 0 {
1653            *slot = None;
1654        }
1655    }
1656}
1657
1658#[derive(Debug, Default, Deserialize, PartialEq)]
1659#[serde(rename_all = "kebab-case")]
1660pub struct CargoHttpConfig {
1661    pub proxy: Option<String>,
1662    pub low_speed_limit: Option<u32>,
1663    pub timeout: Option<u64>,
1664    pub cainfo: Option<ConfigRelativePath>,
1665    pub check_revoke: Option<bool>,
1666    pub user_agent: Option<String>,
1667    pub debug: Option<bool>,
1668    pub multiplexing: Option<bool>,
1669    pub ssl_version: Option<SslVersionConfig>,
1670}
1671
1672/// Configuration for `ssl-version` in `http` section
1673/// There are two ways to configure:
1674///
1675/// ```text
1676/// [http]
1677/// ssl-version = "tlsv1.3"
1678/// ```
1679///
1680/// ```text
1681/// [http]
1682/// ssl-version.min = "tlsv1.2"
1683/// ssl-version.max = "tlsv1.3"
1684/// ```
1685#[derive(Clone, Debug, Deserialize, PartialEq)]
1686#[serde(untagged)]
1687pub enum SslVersionConfig {
1688    Single(String),
1689    Range(SslVersionConfigRange),
1690}
1691
1692#[derive(Clone, Debug, Deserialize, PartialEq)]
1693pub struct SslVersionConfigRange {
1694    pub min: Option<String>,
1695    pub max: Option<String>,
1696}
1697
1698#[derive(Debug, Deserialize)]
1699#[serde(rename_all = "kebab-case")]
1700pub struct CargoNetConfig {
1701    pub retry: Option<u32>,
1702    pub offline: Option<bool>,
1703    pub git_fetch_with_cli: Option<bool>,
1704}
1705
1706#[derive(Debug, Deserialize)]
1707#[serde(rename_all = "kebab-case")]
1708pub struct CargoBuildConfig {
1709    pub pipelining: Option<bool>,
1710    pub dep_info_basedir: Option<ConfigRelativePath>,
1711    pub target_dir: Option<ConfigRelativePath>,
1712    pub incremental: Option<bool>,
1713    pub target: Option<ConfigRelativePath>,
1714    pub jobs: Option<u32>,
1715    pub rustflags: Option<StringList>,
1716    pub rustdocflags: Option<StringList>,
1717    pub rustc_wrapper: Option<PathBuf>,
1718    pub rustc_workspace_wrapper: Option<PathBuf>,
1719    pub rustc: Option<PathBuf>,
1720    pub rustdoc: Option<PathBuf>,
1721    pub out_dir: Option<ConfigRelativePath>,
1722}
1723
1724/// A type to deserialize a list of strings from a toml file.
1725///
1726/// Supports deserializing either a whitespace-separated list of arguments in a
1727/// single string or a string list itself. For example these deserialize to
1728/// equivalent values:
1729///
1730/// ```toml
1731/// a = 'a b c'
1732/// b = ['a', 'b', 'c']
1733/// ```
1734#[derive(Debug, Deserialize)]
1735pub struct StringList(Vec<String>);
1736
1737impl StringList {
1738    pub fn as_slice(&self) -> &[String] {
1739        &self.0
1740    }
1741}