| use std::cell::{RefCell, RefMut}; |
| use std::collections::hash_map::Entry::{Occupied, Vacant}; |
| use std::collections::hash_map::HashMap; |
| use std::collections::HashSet; |
| use std::env; |
| use std::fmt; |
| use std::fs::{self, File}; |
| use std::io::prelude::*; |
| use std::io::{self, SeekFrom}; |
| use std::mem; |
| use std::path::{Path, PathBuf}; |
| use std::str::FromStr; |
| use std::sync::Once; |
| use std::time::Instant; |
| use std::vec; |
| |
| use curl::easy::Easy; |
| use lazycell::LazyCell; |
| use serde::Deserialize; |
| use serde::{de, de::IntoDeserializer}; |
| use url::Url; |
| |
| use self::ConfigValue as CV; |
| use crate::core::profiles::ConfigProfiles; |
| use crate::core::shell::Verbosity; |
| use crate::core::{CliUnstable, Shell, SourceId, Workspace}; |
| use crate::ops; |
| use crate::util::errors::{self, internal, CargoResult, CargoResultExt}; |
| use crate::util::toml as cargo_toml; |
| use crate::util::Filesystem; |
| use crate::util::Rustc; |
| use crate::util::{paths, validate_package_name, FileLock}; |
| use crate::util::{IntoUrl, IntoUrlWithBase}; |
| |
| /// Configuration information for cargo. This is not specific to a build, it is information |
| /// relating to cargo itself. |
| /// |
| /// This struct implements `Default`: all fields can be inferred. |
| #[derive(Debug)] |
| pub struct Config { |
| /// The location of the user's 'home' directory. OS-dependent. |
| home_path: Filesystem, |
| /// Information about how to write messages to the shell |
| shell: RefCell<Shell>, |
| /// A collection of configuration options |
| values: LazyCell<HashMap<String, ConfigValue>>, |
| /// The current working directory of cargo |
| cwd: PathBuf, |
| /// The location of the cargo executable (path to current process) |
| cargo_exe: LazyCell<PathBuf>, |
| /// The location of the rustdoc executable |
| rustdoc: LazyCell<PathBuf>, |
| /// Whether we are printing extra verbose messages |
| extra_verbose: bool, |
| /// `frozen` is the same as `locked`, but additionally will not access the |
| /// network to determine if the lock file is out-of-date. |
| frozen: bool, |
| /// `locked` is set if we should not update lock files. If the lock file |
| /// is missing, or needs to be updated, an error is produced. |
| locked: bool, |
| /// `offline` is set if we should never access the network, but otherwise |
| /// continue operating if possible. |
| offline: bool, |
| /// A global static IPC control mechanism (used for managing parallel builds) |
| jobserver: Option<jobserver::Client>, |
| /// Cli flags of the form "-Z something" |
| cli_flags: CliUnstable, |
| /// A handle on curl easy mode for http calls |
| easy: LazyCell<RefCell<Easy>>, |
| /// Cache of the `SourceId` for crates.io |
| crates_io_source_id: LazyCell<SourceId>, |
| /// If false, don't cache `rustc --version --verbose` invocations |
| cache_rustc_info: bool, |
| /// Creation time of this config, used to output the total build time |
| creation_time: Instant, |
| /// Target Directory via resolved Cli parameter |
| target_dir: Option<Filesystem>, |
| /// Environment variables, separated to assist testing. |
| env: HashMap<String, String>, |
| /// Profiles loaded from config. |
| profiles: LazyCell<ConfigProfiles>, |
| /// Tracks which sources have been updated to avoid multiple updates. |
| updated_sources: LazyCell<RefCell<HashSet<SourceId>>>, |
| /// Lock, if held, of the global package cache along with the number of |
| /// acquisitions so far. |
| package_cache_lock: RefCell<Option<(Option<FileLock>, usize)>>, |
| } |
| |
| impl Config { |
| pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> Config { |
| static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _; |
| static INIT: Once = Once::new(); |
| |
| // This should be called early on in the process, so in theory the |
| // unsafety is ok here. (taken ownership of random fds) |
| INIT.call_once(|| unsafe { |
| if let Some(client) = jobserver::Client::from_env() { |
| GLOBAL_JOBSERVER = Box::into_raw(Box::new(client)); |
| } |
| }); |
| |
| let env: HashMap<_, _> = env::vars_os() |
| .filter_map(|(k, v)| { |
| // Ignore any key/values that are not valid Unicode. |
| match (k.into_string(), v.into_string()) { |
| (Ok(k), Ok(v)) => Some((k, v)), |
| _ => None, |
| } |
| }) |
| .collect(); |
| |
| let cache_rustc_info = match env.get("CARGO_CACHE_RUSTC_INFO") { |
| Some(cache) => cache != "0", |
| _ => true, |
| }; |
| |
| Config { |
| home_path: Filesystem::new(homedir), |
| shell: RefCell::new(shell), |
| cwd, |
| values: LazyCell::new(), |
| cargo_exe: LazyCell::new(), |
| rustdoc: LazyCell::new(), |
| extra_verbose: false, |
| frozen: false, |
| locked: false, |
| offline: false, |
| jobserver: unsafe { |
| if GLOBAL_JOBSERVER.is_null() { |
| None |
| } else { |
| Some((*GLOBAL_JOBSERVER).clone()) |
| } |
| }, |
| cli_flags: CliUnstable::default(), |
| easy: LazyCell::new(), |
| crates_io_source_id: LazyCell::new(), |
| cache_rustc_info, |
| creation_time: Instant::now(), |
| target_dir: None, |
| env, |
| profiles: LazyCell::new(), |
| updated_sources: LazyCell::new(), |
| package_cache_lock: RefCell::new(None), |
| } |
| } |
| |
| pub fn default() -> CargoResult<Config> { |
| let shell = Shell::new(); |
| let cwd = |
| env::current_dir().chain_err(|| "couldn't get the current directory of the process")?; |
| let homedir = homedir(&cwd).ok_or_else(|| { |
| failure::format_err!( |
| "Cargo couldn't find your home directory. \ |
| This probably means that $HOME was not set." |
| ) |
| })?; |
| Ok(Config::new(shell, cwd, homedir)) |
| } |
| |
| /// Gets the user's Cargo home directory (OS-dependent). |
| pub fn home(&self) -> &Filesystem { |
| &self.home_path |
| } |
| |
| /// Gets the Cargo Git directory (`<cargo_home>/git`). |
| pub fn git_path(&self) -> Filesystem { |
| self.home_path.join("git") |
| } |
| |
| /// Gets the Cargo registry index directory (`<cargo_home>/registry/index`). |
| pub fn registry_index_path(&self) -> Filesystem { |
| self.home_path.join("registry").join("index") |
| } |
| |
| /// Gets the Cargo registry cache directory (`<cargo_home>/registry/path`). |
| pub fn registry_cache_path(&self) -> Filesystem { |
| self.home_path.join("registry").join("cache") |
| } |
| |
| /// Gets the Cargo registry source directory (`<cargo_home>/registry/src`). |
| pub fn registry_source_path(&self) -> Filesystem { |
| self.home_path.join("registry").join("src") |
| } |
| |
| /// Gets the default Cargo registry. |
| pub fn default_registry(&self) -> CargoResult<Option<String>> { |
| Ok(match self.get_string("registry.default")? { |
| Some(registry) => Some(registry.val), |
| None => None, |
| }) |
| } |
| |
| /// Gets a reference to the shell, e.g., for writing error messages. |
| pub fn shell(&self) -> RefMut<'_, Shell> { |
| self.shell.borrow_mut() |
| } |
| |
| /// Gets the path to the `rustdoc` executable. |
| pub fn rustdoc(&self) -> CargoResult<&Path> { |
| self.rustdoc |
| .try_borrow_with(|| self.get_tool("rustdoc")) |
| .map(AsRef::as_ref) |
| } |
| |
| /// Gets the path to the `rustc` executable. |
| pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> { |
| let cache_location = ws.map(|ws| { |
| ws.target_dir() |
| .join(".rustc_info.json") |
| .into_path_unlocked() |
| }); |
| let wrapper = self.maybe_get_tool("rustc_wrapper")?; |
| Rustc::new( |
| self.get_tool("rustc")?, |
| wrapper, |
| &self |
| .home() |
| .join("bin") |
| .join("rustc") |
| .into_path_unlocked() |
| .with_extension(env::consts::EXE_EXTENSION), |
| if self.cache_rustc_info { |
| cache_location |
| } else { |
| None |
| }, |
| ) |
| } |
| |
| /// Gets the path to the `cargo` executable. |
| pub fn cargo_exe(&self) -> CargoResult<&Path> { |
| self.cargo_exe |
| .try_borrow_with(|| { |
| fn from_current_exe() -> CargoResult<PathBuf> { |
| // Try fetching the path to `cargo` using `env::current_exe()`. |
| // The method varies per operating system and might fail; in particular, |
| // it depends on `/proc` being mounted on Linux, and some environments |
| // (like containers or chroots) may not have that available. |
| let exe = env::current_exe()?.canonicalize()?; |
| Ok(exe) |
| } |
| |
| fn from_argv() -> CargoResult<PathBuf> { |
| // Grab `argv[0]` and attempt to resolve it to an absolute path. |
| // If `argv[0]` has one component, it must have come from a `PATH` lookup, |
| // so probe `PATH` in that case. |
| // Otherwise, it has multiple components and is either: |
| // - a relative path (e.g., `./cargo`, `target/debug/cargo`), or |
| // - an absolute path (e.g., `/usr/local/bin/cargo`). |
| // In either case, `Path::canonicalize` will return the full absolute path |
| // to the target if it exists. |
| let argv0 = env::args_os() |
| .map(PathBuf::from) |
| .next() |
| .ok_or_else(|| failure::format_err!("no argv[0]"))?; |
| paths::resolve_executable(&argv0) |
| } |
| |
| let exe = from_current_exe() |
| .or_else(|_| from_argv()) |
| .chain_err(|| "couldn't get the path to cargo executable")?; |
| Ok(exe) |
| }) |
| .map(AsRef::as_ref) |
| } |
| |
| pub fn profiles(&self) -> CargoResult<&ConfigProfiles> { |
| self.profiles.try_borrow_with(|| { |
| let ocp = self.get::<Option<ConfigProfiles>>("profile")?; |
| if let Some(config_profiles) = ocp { |
| // Warn if config profiles without CLI option. |
| if !self.cli_unstable().config_profile { |
| self.shell().warn( |
| "profiles in config files require `-Z config-profile` \ |
| command-line option", |
| )?; |
| return Ok(ConfigProfiles::default()); |
| } |
| Ok(config_profiles) |
| } else { |
| Ok(ConfigProfiles::default()) |
| } |
| }) |
| } |
| |
| pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> { |
| self.updated_sources |
| .borrow_with(|| RefCell::new(HashSet::new())) |
| .borrow_mut() |
| } |
| |
| pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> { |
| self.values.try_borrow_with(|| self.load_values()) |
| } |
| |
| pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> { |
| match self.values.borrow_mut() { |
| Some(map) => Ok(map), |
| None => failure::bail!("config values not loaded yet"), |
| } |
| } |
| |
| // Note: this is used by RLS, not Cargo. |
| pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> { |
| if self.values.borrow().is_some() { |
| failure::bail!("config values already found") |
| } |
| match self.values.fill(values) { |
| Ok(()) => Ok(()), |
| Err(_) => failure::bail!("could not fill values"), |
| } |
| } |
| |
| pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> { |
| let values = self.load_values_from(path.as_ref())?; |
| self.values.replace(values); |
| Ok(()) |
| } |
| |
| pub fn cwd(&self) -> &Path { |
| &self.cwd |
| } |
| |
| pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> { |
| if let Some(ref dir) = self.target_dir { |
| Ok(Some(dir.clone())) |
| } else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") { |
| Ok(Some(Filesystem::new(self.cwd.join(dir)))) |
| } else if let Some(val) = self.get_path("build.target-dir")? { |
| let val = self.cwd.join(val.val); |
| Ok(Some(Filesystem::new(val))) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| fn get_cv(&self, key: &str) -> CargoResult<Option<ConfigValue>> { |
| let vals = self.values()?; |
| let mut parts = key.split('.').enumerate(); |
| let mut val = match vals.get(parts.next().unwrap().1) { |
| Some(val) => val, |
| None => return Ok(None), |
| }; |
| for (i, part) in parts { |
| match *val { |
| CV::Table(ref map, _) => { |
| val = match map.get(part) { |
| Some(val) => val, |
| None => return Ok(None), |
| } |
| } |
| CV::Integer(_, ref path) |
| | CV::String(_, ref path) |
| | CV::List(_, ref path) |
| | CV::Boolean(_, ref path) => { |
| let idx = key.split('.').take(i).fold(0, |n, s| n + s.len()) + i - 1; |
| let key_so_far = &key[..idx]; |
| failure::bail!( |
| "expected table for configuration key `{}`, \ |
| but found {} in {}", |
| key_so_far, |
| val.desc(), |
| path.display() |
| ) |
| } |
| } |
| } |
| Ok(Some(val.clone())) |
| } |
| |
| // Helper primarily for testing. |
| pub fn set_env(&mut self, env: HashMap<String, String>) { |
| self.env = env; |
| } |
| |
| fn get_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError> |
| where |
| T: FromStr, |
| <T as FromStr>::Err: fmt::Display, |
| { |
| let key = key.to_env(); |
| match self.env.get(&key) { |
| Some(value) => { |
| let definition = Definition::Environment(key); |
| Ok(Some(Value { |
| val: value |
| .parse() |
| .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?, |
| definition, |
| })) |
| } |
| None => Ok(None), |
| } |
| } |
| |
| fn has_key(&self, key: &ConfigKey) -> bool { |
| let env_key = key.to_env(); |
| if self.env.get(&env_key).is_some() { |
| return true; |
| } |
| let env_pattern = format!("{}_", env_key); |
| if self.env.keys().any(|k| k.starts_with(&env_pattern)) { |
| return true; |
| } |
| if let Ok(o_cv) = self.get_cv(&key.to_config()) { |
| if o_cv.is_some() { |
| return true; |
| } |
| } |
| false |
| } |
| |
| pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> { |
| self.get_string_priv(&ConfigKey::from_str(key)) |
| .map_err(|e| e.into()) |
| } |
| |
| fn get_string_priv(&self, key: &ConfigKey) -> Result<OptValue<String>, ConfigError> { |
| match self.get_env(key)? { |
| Some(v) => Ok(Some(v)), |
| None => { |
| let config_key = key.to_config(); |
| let o_cv = self.get_cv(&config_key)?; |
| match o_cv { |
| Some(CV::String(s, path)) => Ok(Some(Value { |
| val: s, |
| definition: Definition::Path(path), |
| })), |
| Some(cv) => Err(ConfigError::expected(&config_key, "a string", &cv)), |
| None => Ok(None), |
| } |
| } |
| } |
| } |
| |
| pub fn get_bool(&self, key: &str) -> CargoResult<OptValue<bool>> { |
| self.get_bool_priv(&ConfigKey::from_str(key)) |
| .map_err(|e| e.into()) |
| } |
| |
| fn get_bool_priv(&self, key: &ConfigKey) -> Result<OptValue<bool>, ConfigError> { |
| match self.get_env(key)? { |
| Some(v) => Ok(Some(v)), |
| None => { |
| let config_key = key.to_config(); |
| let o_cv = self.get_cv(&config_key)?; |
| match o_cv { |
| Some(CV::Boolean(b, path)) => Ok(Some(Value { |
| val: b, |
| definition: Definition::Path(path), |
| })), |
| Some(cv) => Err(ConfigError::expected(&config_key, "true/false", &cv)), |
| None => Ok(None), |
| } |
| } |
| } |
| } |
| |
| fn string_to_path(&self, value: String, definition: &Definition) -> PathBuf { |
| let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\')); |
| if is_path { |
| definition.root(self).join(value) |
| } else { |
| // A pathless name. |
| PathBuf::from(value) |
| } |
| } |
| |
| pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> { |
| if let Some(val) = self.get_string(key)? { |
| Ok(Some(Value { |
| val: self.string_to_path(val.val, &val.definition), |
| definition: val.definition, |
| })) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| pub fn get_path_and_args(&self, key: &str) -> CargoResult<OptValue<(PathBuf, Vec<String>)>> { |
| if let Some(mut val) = self.get_list_or_split_string(key)? { |
| if !val.val.is_empty() { |
| return Ok(Some(Value { |
| val: ( |
| self.string_to_path(val.val.remove(0), &val.definition), |
| val.val, |
| ), |
| definition: val.definition, |
| })); |
| } |
| } |
| Ok(None) |
| } |
| |
| // NOTE: this does **not** support environment variables. Use `get` instead |
| // if you want that. |
| pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, PathBuf)>>> { |
| match self.get_cv(key)? { |
| Some(CV::List(i, path)) => Ok(Some(Value { |
| val: i, |
| definition: Definition::Path(path), |
| })), |
| Some(val) => self.expected("list", key, &val), |
| None => Ok(None), |
| } |
| } |
| |
| pub fn get_list_or_split_string(&self, key: &str) -> CargoResult<OptValue<Vec<String>>> { |
| if let Some(value) = self.get_env::<String>(&ConfigKey::from_str(key))? { |
| return Ok(Some(Value { |
| val: value.val.split(' ').map(str::to_string).collect(), |
| definition: value.definition, |
| })); |
| } |
| |
| match self.get_cv(key)? { |
| Some(CV::List(i, path)) => Ok(Some(Value { |
| val: i.into_iter().map(|(s, _)| s).collect(), |
| definition: Definition::Path(path), |
| })), |
| Some(CV::String(i, path)) => Ok(Some(Value { |
| val: i.split(' ').map(str::to_string).collect(), |
| definition: Definition::Path(path), |
| })), |
| Some(val) => self.expected("list or string", key, &val), |
| None => Ok(None), |
| } |
| } |
| |
| pub fn get_table(&self, key: &str) -> CargoResult<OptValue<HashMap<String, CV>>> { |
| match self.get_cv(key)? { |
| Some(CV::Table(i, path)) => Ok(Some(Value { |
| val: i, |
| definition: Definition::Path(path), |
| })), |
| Some(val) => self.expected("table", key, &val), |
| None => Ok(None), |
| } |
| } |
| |
| // Recommended to use `get` if you want a specific type, such as an unsigned value. |
| // Example: `config.get::<Option<u32>>("some.key")?`. |
| pub fn get_i64(&self, key: &str) -> CargoResult<OptValue<i64>> { |
| self.get_integer(&ConfigKey::from_str(key)) |
| .map_err(|e| e.into()) |
| } |
| |
| fn get_integer(&self, key: &ConfigKey) -> Result<OptValue<i64>, ConfigError> { |
| let config_key = key.to_config(); |
| match self.get_env::<i64>(key)? { |
| Some(v) => Ok(Some(v)), |
| None => match self.get_cv(&config_key)? { |
| Some(CV::Integer(i, path)) => Ok(Some(Value { |
| val: i, |
| definition: Definition::Path(path), |
| })), |
| Some(cv) => Err(ConfigError::expected(&config_key, "an integer", &cv)), |
| None => Ok(None), |
| }, |
| } |
| } |
| |
| fn expected<T>(&self, ty: &str, key: &str, val: &CV) -> CargoResult<T> { |
| val.expected(ty, key) |
| .map_err(|e| failure::format_err!("invalid configuration for key `{}`\n{}", key, e)) |
| } |
| |
| pub fn configure( |
| &mut self, |
| verbose: u32, |
| quiet: Option<bool>, |
| color: &Option<String>, |
| frozen: bool, |
| locked: bool, |
| offline: bool, |
| target_dir: &Option<PathBuf>, |
| unstable_flags: &[String], |
| ) -> CargoResult<()> { |
| let extra_verbose = verbose >= 2; |
| let verbose = if verbose == 0 { None } else { Some(true) }; |
| |
| // Ignore errors in the configuration files. |
| let cfg_verbose = self.get_bool("term.verbose").unwrap_or(None).map(|v| v.val); |
| let cfg_color = self.get_string("term.color").unwrap_or(None).map(|v| v.val); |
| |
| let color = color.as_ref().or_else(|| cfg_color.as_ref()); |
| |
| let verbosity = match (verbose, cfg_verbose, quiet) { |
| (Some(true), _, None) | (None, Some(true), None) => Verbosity::Verbose, |
| |
| // Command line takes precedence over configuration, so ignore the |
| // configuration.. |
| (None, _, Some(true)) => Verbosity::Quiet, |
| |
| // Can't pass both at the same time on the command line regardless |
| // of configuration. |
| (Some(true), _, Some(true)) => { |
| failure::bail!("cannot set both --verbose and --quiet"); |
| } |
| |
| // Can't actually get `Some(false)` as a value from the command |
| // line, so just ignore them here to appease exhaustiveness checking |
| // in match statements. |
| (Some(false), _, _) |
| | (_, _, Some(false)) |
| | (None, Some(false), None) |
| | (None, None, None) => Verbosity::Normal, |
| }; |
| |
| let cli_target_dir = match target_dir.as_ref() { |
| Some(dir) => Some(Filesystem::new(dir.clone())), |
| None => None, |
| }; |
| |
| self.shell().set_verbosity(verbosity); |
| self.shell().set_color_choice(color.map(|s| &s[..]))?; |
| self.extra_verbose = extra_verbose; |
| self.frozen = frozen; |
| self.locked = locked; |
| self.offline = offline |
| || self |
| .get::<Option<bool>>("net.offline") |
| .unwrap_or(None) |
| .unwrap_or(false); |
| self.target_dir = cli_target_dir; |
| self.cli_flags.parse(unstable_flags)?; |
| |
| Ok(()) |
| } |
| |
| pub fn cli_unstable(&self) -> &CliUnstable { |
| &self.cli_flags |
| } |
| |
| pub fn extra_verbose(&self) -> bool { |
| self.extra_verbose |
| } |
| |
| pub fn network_allowed(&self) -> bool { |
| !self.frozen() && !self.offline() |
| } |
| |
| pub fn offline(&self) -> bool { |
| self.offline |
| } |
| |
| pub fn frozen(&self) -> bool { |
| self.frozen |
| } |
| |
| pub fn lock_update_allowed(&self) -> bool { |
| !self.frozen && !self.locked |
| } |
| |
| /// Loads configuration from the filesystem. |
| pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> { |
| self.load_values_from(&self.cwd) |
| } |
| |
| fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> { |
| let mut cfg = CV::Table(HashMap::new(), PathBuf::from(".")); |
| let home = self.home_path.clone().into_path_unlocked(); |
| |
| walk_tree(path, &home, |path| { |
| let mut contents = String::new(); |
| let mut file = File::open(&path)?; |
| file.read_to_string(&mut contents) |
| .chain_err(|| format!("failed to read configuration file `{}`", path.display()))?; |
| let toml = cargo_toml::parse(&contents, path, self).chain_err(|| { |
| format!("could not parse TOML configuration in `{}`", path.display()) |
| })?; |
| let value = CV::from_toml(path, toml).chain_err(|| { |
| format!( |
| "failed to load TOML configuration from `{}`", |
| path.display() |
| ) |
| })?; |
| cfg.merge(value) |
| .chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?; |
| Ok(()) |
| }) |
| .chain_err(|| "could not load Cargo configuration")?; |
| |
| self.load_credentials(&mut cfg)?; |
| match cfg { |
| CV::Table(map, _) => Ok(map), |
| _ => unreachable!(), |
| } |
| } |
| |
| /// Gets the index for a registry. |
| pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> { |
| validate_package_name(registry, "registry name", "")?; |
| Ok( |
| match self.get_string(&format!("registries.{}.index", registry))? { |
| Some(index) => self.resolve_registry_index(index)?, |
| None => failure::bail!("No index found for registry: `{}`", registry), |
| }, |
| ) |
| } |
| |
| /// Gets the index for the default registry. |
| pub fn get_default_registry_index(&self) -> CargoResult<Option<Url>> { |
| Ok(match self.get_string("registry.index")? { |
| Some(index) => Some(self.resolve_registry_index(index)?), |
| None => None, |
| }) |
| } |
| |
| fn resolve_registry_index(&self, index: Value<String>) -> CargoResult<Url> { |
| let base = index |
| .definition |
| .root(self) |
| .join("truncated-by-url_with_base"); |
| // Parse val to check it is a URL, not a relative path without a protocol. |
| let _parsed = index.val.into_url()?; |
| let url = index.val.into_url_with_base(Some(&*base))?; |
| if url.password().is_some() { |
| failure::bail!("Registry URLs may not contain passwords"); |
| } |
| Ok(url) |
| } |
| |
| /// Loads credentials config from the credentials file into the `ConfigValue` object, if |
| /// present. |
| fn load_credentials(&self, cfg: &mut ConfigValue) -> CargoResult<()> { |
| let home_path = self.home_path.clone().into_path_unlocked(); |
| let credentials = home_path.join("credentials"); |
| if fs::metadata(&credentials).is_err() { |
| return Ok(()); |
| } |
| |
| let mut contents = String::new(); |
| let mut file = File::open(&credentials)?; |
| file.read_to_string(&mut contents).chain_err(|| { |
| format!( |
| "failed to read configuration file `{}`", |
| credentials.display() |
| ) |
| })?; |
| |
| let toml = cargo_toml::parse(&contents, &credentials, self).chain_err(|| { |
| format!( |
| "could not parse TOML configuration in `{}`", |
| credentials.display() |
| ) |
| })?; |
| |
| let mut value = CV::from_toml(&credentials, toml).chain_err(|| { |
| format!( |
| "failed to load TOML configuration from `{}`", |
| credentials.display() |
| ) |
| })?; |
| |
| // Backwards compatibility for old `.cargo/credentials` layout. |
| { |
| let value = match value { |
| CV::Table(ref mut value, _) => value, |
| _ => unreachable!(), |
| }; |
| |
| if let Some(token) = value.remove("token") { |
| if let Vacant(entry) = value.entry("registry".into()) { |
| let mut map = HashMap::new(); |
| map.insert("token".into(), token); |
| let table = CV::Table(map, PathBuf::from(".")); |
| entry.insert(table); |
| } |
| } |
| } |
| |
| // We want value to override `cfg`, so swap these. |
| mem::swap(cfg, &mut value); |
| cfg.merge(value)?; |
| |
| Ok(()) |
| } |
| |
| /// Looks for a path for `tool` in an environment variable or config path, and returns `None` |
| /// if it's not present. |
| fn maybe_get_tool(&self, tool: &str) -> CargoResult<Option<PathBuf>> { |
| let var = tool |
| .chars() |
| .flat_map(|c| c.to_uppercase()) |
| .collect::<String>(); |
| if let Some(tool_path) = env::var_os(&var) { |
| let maybe_relative = match tool_path.to_str() { |
| Some(s) => s.contains('/') || s.contains('\\'), |
| None => false, |
| }; |
| let path = if maybe_relative { |
| self.cwd.join(tool_path) |
| } else { |
| PathBuf::from(tool_path) |
| }; |
| return Ok(Some(path)); |
| } |
| |
| let var = format!("build.{}", tool); |
| if let Some(tool_path) = self.get_path(&var)? { |
| return Ok(Some(tool_path.val)); |
| } |
| |
| Ok(None) |
| } |
| |
| /// Looks for a path for `tool` in an environment variable or config path, defaulting to `tool` |
| /// as a path. |
| pub fn get_tool(&self, tool: &str) -> CargoResult<PathBuf> { |
| self.maybe_get_tool(tool) |
| .map(|t| t.unwrap_or_else(|| PathBuf::from(tool))) |
| } |
| |
| pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> { |
| self.jobserver.as_ref() |
| } |
| |
| pub fn http(&self) -> CargoResult<&RefCell<Easy>> { |
| let http = self |
| .easy |
| .try_borrow_with(|| ops::http_handle(self).map(RefCell::new))?; |
| { |
| let mut http = http.borrow_mut(); |
| http.reset(); |
| let timeout = ops::configure_http_handle(self, &mut http)?; |
| timeout.configure(&mut http)?; |
| } |
| Ok(http) |
| } |
| |
| pub fn crates_io_source_id<F>(&self, f: F) -> CargoResult<SourceId> |
| where |
| F: FnMut() -> CargoResult<SourceId>, |
| { |
| Ok(*(self.crates_io_source_id.try_borrow_with(f)?)) |
| } |
| |
| pub fn creation_time(&self) -> Instant { |
| self.creation_time |
| } |
| |
| // Retrieves a config variable. |
| // |
| // This supports most serde `Deserialize` types. Examples: |
| // |
| // let v: Option<u32> = config.get("some.nested.key")?; |
| // let v: Option<MyStruct> = config.get("some.key")?; |
| // let v: Option<HashMap<String, MyStruct>> = config.get("foo")?; |
| pub fn get<'de, T: de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> { |
| let d = Deserializer { |
| config: self, |
| key: ConfigKey::from_str(key), |
| }; |
| T::deserialize(d).map_err(|e| e.into()) |
| } |
| |
| pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path { |
| let ret = f.as_path_unlocked(); |
| assert!( |
| self.package_cache_lock.borrow().is_some(), |
| "pacakge cache lock is not currently held, Cargo forgot to call \ |
| `acquire_package_cache_lock` before we got to this stack frame", |
| ); |
| assert!(ret.starts_with(self.home_path.as_path_unlocked())); |
| ret |
| } |
| |
| /// Acquires an exclusive lock on the global "package cache" |
| /// |
| /// This lock is global per-process and can be acquired recursively. An RAII |
| /// structure is returned to release the lock, and if this process |
| /// abnormally terminates the lock is also released. |
| pub fn acquire_package_cache_lock(&self) -> CargoResult<PackageCacheLock<'_>> { |
| let mut slot = self.package_cache_lock.borrow_mut(); |
| match *slot { |
| // We've already acquired the lock in this process, so simply bump |
| // the count and continue. |
| Some((_, ref mut cnt)) => { |
| *cnt += 1; |
| } |
| None => { |
| let path = ".package-cache"; |
| let desc = "package cache"; |
| |
| // First, attempt to open an exclusive lock which is in general |
| // the purpose of this lock! |
| // |
| // If that fails because of a readonly filesystem or a |
| // permission error, though, then we don't really want to fail |
| // just because of this. All files that this lock protects are |
| // in subfolders, so they're assumed by Cargo to also be |
| // readonly or have invalid permissions for us to write to. If |
| // that's the case, then we don't really need to grab a lock in |
| // the first place here. |
| // |
| // Despite this we attempt to grab a readonly lock. This means |
| // that if our read-only folder is shared read-write with |
| // someone else on the system we should synchronize with them, |
| // but if we can't even do that then we did our best and we just |
| // keep on chugging elsewhere. |
| match self.home_path.open_rw(path, self, desc) { |
| Ok(lock) => *slot = Some((Some(lock), 1)), |
| Err(e) => { |
| if maybe_readonly(&e) { |
| let lock = self.home_path.open_ro(path, self, desc).ok(); |
| *slot = Some((lock, 1)); |
| return Ok(PackageCacheLock(self)); |
| } |
| |
| Err(e).chain_err(|| "failed to acquire package cache lock")?; |
| } |
| } |
| } |
| } |
| return Ok(PackageCacheLock(self)); |
| |
| fn maybe_readonly(err: &failure::Error) -> bool { |
| err.iter_chain().any(|err| { |
| if let Some(io) = err.downcast_ref::<io::Error>() { |
| if io.kind() == io::ErrorKind::PermissionDenied { |
| return true; |
| } |
| |
| #[cfg(unix)] |
| return io.raw_os_error() == Some(libc::EROFS); |
| } |
| |
| false |
| }) |
| } |
| } |
| |
| pub fn release_package_cache_lock(&self) {} |
| } |
| |
| /// A segment of a config key. |
| /// |
| /// Config keys are split on dots for regular keys, or underscores for |
| /// environment keys. |
| #[derive(Debug, Clone, Eq, PartialEq, Hash)] |
| enum ConfigKeyPart { |
| /// Case-insensitive part (checks uppercase in environment keys). |
| Part(String), |
| /// Case-sensitive part (environment keys must match exactly). |
| CasePart(String), |
| } |
| |
| impl ConfigKeyPart { |
| fn to_env(&self) -> String { |
| match self { |
| ConfigKeyPart::Part(s) => s.replace("-", "_").to_uppercase(), |
| ConfigKeyPart::CasePart(s) => s.clone(), |
| } |
| } |
| |
| fn to_config(&self) -> String { |
| match self { |
| ConfigKeyPart::Part(s) => s.clone(), |
| ConfigKeyPart::CasePart(s) => s.clone(), |
| } |
| } |
| } |
| |
| /// Key for a configuration variable. |
| #[derive(Debug, Clone)] |
| struct ConfigKey(Vec<ConfigKeyPart>); |
| |
| impl ConfigKey { |
| fn from_str(key: &str) -> ConfigKey { |
| ConfigKey( |
| key.split('.') |
| .map(|p| ConfigKeyPart::Part(p.to_string())) |
| .collect(), |
| ) |
| } |
| |
| fn join(&self, next: ConfigKeyPart) -> ConfigKey { |
| let mut res = self.clone(); |
| res.0.push(next); |
| res |
| } |
| |
| fn to_env(&self) -> String { |
| format!( |
| "CARGO_{}", |
| self.0 |
| .iter() |
| .map(|p| p.to_env()) |
| .collect::<Vec<_>>() |
| .join("_") |
| ) |
| } |
| |
| fn to_config(&self) -> String { |
| self.0 |
| .iter() |
| .map(|p| p.to_config()) |
| .collect::<Vec<_>>() |
| .join(".") |
| } |
| |
| fn last(&self) -> &ConfigKeyPart { |
| self.0.last().unwrap() |
| } |
| } |
| |
| impl fmt::Display for ConfigKey { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| self.to_config().fmt(f) |
| } |
| } |
| |
| /// Internal error for serde errors. |
| #[derive(Debug)] |
| pub struct ConfigError { |
| error: failure::Error, |
| definition: Option<Definition>, |
| } |
| |
| impl ConfigError { |
| fn new(message: String, definition: Definition) -> ConfigError { |
| ConfigError { |
| error: failure::err_msg(message), |
| definition: Some(definition), |
| } |
| } |
| |
| fn expected(key: &str, expected: &str, found: &ConfigValue) -> ConfigError { |
| ConfigError { |
| error: failure::format_err!( |
| "`{}` expected {}, but found a {}", |
| key, |
| expected, |
| found.desc() |
| ), |
| definition: Some(Definition::Path(found.definition_path().to_path_buf())), |
| } |
| } |
| |
| fn missing(key: &str) -> ConfigError { |
| ConfigError { |
| error: failure::format_err!("missing config key `{}`", key), |
| definition: None, |
| } |
| } |
| |
| fn with_key_context(self, key: &str, definition: Definition) -> ConfigError { |
| ConfigError { |
| error: failure::format_err!("could not load config key `{}`: {}", key, self), |
| definition: Some(definition), |
| } |
| } |
| } |
| |
| impl std::error::Error for ConfigError {} |
| |
| // Future note: currently, we cannot override `Fail::cause` (due to |
| // specialization) so we have no way to return the underlying causes. In the |
| // future, once this limitation is lifted, this should instead implement |
| // `cause` and avoid doing the cause formatting here. |
| impl fmt::Display for ConfigError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let message = errors::display_causes(&self.error); |
| if let Some(ref definition) = self.definition { |
| write!(f, "error in {}: {}", definition, message) |
| } else { |
| message.fmt(f) |
| } |
| } |
| } |
| |
| impl de::Error for ConfigError { |
| fn custom<T: fmt::Display>(msg: T) -> Self { |
| ConfigError { |
| error: failure::err_msg(msg.to_string()), |
| definition: None, |
| } |
| } |
| } |
| |
| impl From<failure::Error> for ConfigError { |
| fn from(error: failure::Error) -> Self { |
| ConfigError { |
| error, |
| definition: None, |
| } |
| } |
| } |
| |
| /// Serde deserializer used to convert config values to a target type using |
| /// `Config::get`. |
| pub struct Deserializer<'config> { |
| config: &'config Config, |
| key: ConfigKey, |
| } |
| |
| macro_rules! deserialize_method { |
| ($method:ident, $visit:ident, $getter:ident) => { |
| fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| let v = self.config.$getter(&self.key)?.ok_or_else(|| |
| ConfigError::missing(&self.key.to_config()))?; |
| let Value{val, definition} = v; |
| let res: Result<V::Value, ConfigError> = visitor.$visit(val); |
| res.map_err(|e| e.with_key_context(&self.key.to_config(), definition)) |
| } |
| } |
| } |
| |
| impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { |
| type Error = ConfigError; |
| |
| fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| // Future note: If you ever need to deserialize a non-self describing |
| // map type, this should implement a starts_with check (similar to how |
| // ConfigMapAccess does). |
| if let Some(v) = self.config.env.get(&self.key.to_env()) { |
| let res: Result<V::Value, ConfigError> = if v == "true" || v == "false" { |
| visitor.visit_bool(v.parse().unwrap()) |
| } else if let Ok(v) = v.parse::<i64>() { |
| visitor.visit_i64(v) |
| } else if self.config.cli_unstable().advanced_env |
| && v.starts_with('[') |
| && v.ends_with(']') |
| { |
| visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) |
| } else { |
| visitor.visit_string(v.clone()) |
| }; |
| return res.map_err(|e| { |
| e.with_key_context( |
| &self.key.to_config(), |
| Definition::Environment(self.key.to_env()), |
| ) |
| }); |
| } |
| |
| let o_cv = self.config.get_cv(&self.key.to_config())?; |
| if let Some(cv) = o_cv { |
| let res: (Result<V::Value, ConfigError>, PathBuf) = match cv { |
| CV::Integer(i, path) => (visitor.visit_i64(i), path), |
| CV::String(s, path) => (visitor.visit_string(s), path), |
| CV::List(_, path) => ( |
| visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?), |
| path, |
| ), |
| CV::Table(_, path) => ( |
| visitor.visit_map(ConfigMapAccess::new_map(self.config, self.key.clone())?), |
| path, |
| ), |
| CV::Boolean(b, path) => (visitor.visit_bool(b), path), |
| }; |
| let (res, path) = res; |
| return res |
| .map_err(|e| e.with_key_context(&self.key.to_config(), Definition::Path(path))); |
| } |
| Err(ConfigError::missing(&self.key.to_config())) |
| } |
| |
| deserialize_method!(deserialize_bool, visit_bool, get_bool_priv); |
| deserialize_method!(deserialize_i8, visit_i64, get_integer); |
| deserialize_method!(deserialize_i16, visit_i64, get_integer); |
| deserialize_method!(deserialize_i32, visit_i64, get_integer); |
| deserialize_method!(deserialize_i64, visit_i64, get_integer); |
| deserialize_method!(deserialize_u8, visit_i64, get_integer); |
| deserialize_method!(deserialize_u16, visit_i64, get_integer); |
| deserialize_method!(deserialize_u32, visit_i64, get_integer); |
| deserialize_method!(deserialize_u64, visit_i64, get_integer); |
| deserialize_method!(deserialize_string, visit_string, get_string_priv); |
| |
| fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| if self.config.has_key(&self.key) { |
| visitor.visit_some(self) |
| } else { |
| // Treat missing values as `None`. |
| visitor.visit_none() |
| } |
| } |
| |
| fn deserialize_struct<V>( |
| self, |
| _name: &'static str, |
| fields: &'static [&'static str], |
| visitor: V, |
| ) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| visitor.visit_map(ConfigMapAccess::new_struct(self.config, self.key, fields)?) |
| } |
| |
| fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| visitor.visit_map(ConfigMapAccess::new_map(self.config, self.key)?) |
| } |
| |
| fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) |
| } |
| |
| fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) |
| } |
| |
| fn deserialize_tuple_struct<V>( |
| self, |
| _name: &'static str, |
| _len: usize, |
| visitor: V, |
| ) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) |
| } |
| |
| fn deserialize_newtype_struct<V>( |
| self, |
| name: &'static str, |
| visitor: V, |
| ) -> Result<V::Value, Self::Error> |
| where |
| V: de::Visitor<'de>, |
| { |
| if name == "ConfigRelativePath" { |
| match self.config.get_string_priv(&self.key)? { |
| Some(v) => { |
| let path = v |
| .definition |
| .root(self.config) |
| .join(v.val) |
| .display() |
| .to_string(); |
| visitor.visit_newtype_struct(path.into_deserializer()) |
| } |
| None => Err(ConfigError::missing(&self.key.to_config())), |
| } |
| } else { |
| visitor.visit_newtype_struct(self) |
| } |
| } |
| |
| // These aren't really supported, yet. |
| serde::forward_to_deserialize_any! { |
| f32 f64 char str bytes |
| byte_buf unit unit_struct |
| enum identifier ignored_any |
| } |
| } |
| |
| struct ConfigMapAccess<'config> { |
| config: &'config Config, |
| key: ConfigKey, |
| set_iter: <HashSet<ConfigKeyPart> as IntoIterator>::IntoIter, |
| next: Option<ConfigKeyPart>, |
| } |
| |
| impl<'config> ConfigMapAccess<'config> { |
| fn new_map( |
| config: &'config Config, |
| key: ConfigKey, |
| ) -> Result<ConfigMapAccess<'config>, ConfigError> { |
| let mut set = HashSet::new(); |
| if let Some(mut v) = config.get_table(&key.to_config())? { |
| // `v: Value<HashMap<String, CV>>` |
| for (key, _value) in v.val.drain() { |
| set.insert(ConfigKeyPart::CasePart(key)); |
| } |
| } |
| if config.cli_unstable().advanced_env { |
| // `CARGO_PROFILE_DEV_OVERRIDES_` |
| let env_pattern = format!("{}_", key.to_env()); |
| for env_key in config.env.keys() { |
| if env_key.starts_with(&env_pattern) { |
| // `CARGO_PROFILE_DEV_OVERRIDES_bar_OPT_LEVEL = 3` |
| let rest = &env_key[env_pattern.len()..]; |
| // `rest = bar_OPT_LEVEL` |
| let part = rest.splitn(2, '_').next().unwrap(); |
| // `part = "bar"` |
| set.insert(ConfigKeyPart::CasePart(part.to_string())); |
| } |
| } |
| } |
| Ok(ConfigMapAccess { |
| config, |
| key, |
| set_iter: set.into_iter(), |
| next: None, |
| }) |
| } |
| |
| fn new_struct( |
| config: &'config Config, |
| key: ConfigKey, |
| fields: &'static [&'static str], |
| ) -> Result<ConfigMapAccess<'config>, ConfigError> { |
| let mut set = HashSet::new(); |
| for field in fields { |
| set.insert(ConfigKeyPart::Part(field.to_string())); |
| } |
| if let Some(mut v) = config.get_table(&key.to_config())? { |
| for (t_key, value) in v.val.drain() { |
| let part = ConfigKeyPart::Part(t_key); |
| if !set.contains(&part) { |
| config.shell().warn(format!( |
| "unused key `{}` in config file `{}`", |
| key.join(part).to_config(), |
| value.definition_path().display() |
| ))?; |
| } |
| } |
| } |
| Ok(ConfigMapAccess { |
| config, |
| key, |
| set_iter: set.into_iter(), |
| next: None, |
| }) |
| } |
| } |
| |
| impl<'de, 'config> de::MapAccess<'de> for ConfigMapAccess<'config> { |
| type Error = ConfigError; |
| |
| fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error> |
| where |
| K: de::DeserializeSeed<'de>, |
| { |
| match self.set_iter.next() { |
| Some(key) => { |
| let de_key = key.to_config(); |
| self.next = Some(key); |
| seed.deserialize(de_key.into_deserializer()).map(Some) |
| } |
| None => Ok(None), |
| } |
| } |
| |
| fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error> |
| where |
| V: de::DeserializeSeed<'de>, |
| { |
| let next_key = self.next.take().expect("next field missing"); |
| let next_key = self.key.join(next_key); |
| seed.deserialize(Deserializer { |
| config: self.config, |
| key: next_key, |
| }) |
| } |
| } |
| |
| struct ConfigSeqAccess { |
| list_iter: vec::IntoIter<(String, Definition)>, |
| } |
| |
| impl ConfigSeqAccess { |
| fn new(config: &Config, key: &ConfigKey) -> Result<ConfigSeqAccess, ConfigError> { |
| let mut res = Vec::new(); |
| if let Some(v) = config.get_list(&key.to_config())? { |
| for (s, path) in v.val { |
| res.push((s, Definition::Path(path))); |
| } |
| } |
| |
| if config.cli_unstable().advanced_env { |
| // Parse an environment string as a TOML array. |
| let env_key = key.to_env(); |
| let def = Definition::Environment(env_key.clone()); |
| if let Some(v) = config.env.get(&env_key) { |
| if !(v.starts_with('[') && v.ends_with(']')) { |
| return Err(ConfigError::new( |
| format!("should have TOML list syntax, found `{}`", v), |
| def, |
| )); |
| } |
| let temp_key = key.last().to_env(); |
| let toml_s = format!("{}={}", temp_key, v); |
| let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| { |
| ConfigError::new(format!("could not parse TOML list: {}", e), def.clone()) |
| })?; |
| let values = toml_v |
| .as_table() |
| .unwrap() |
| .get(&temp_key) |
| .unwrap() |
| .as_array() |
| .expect("env var was not array"); |
| for value in values { |
| // TODO: support other types. |
| let s = value.as_str().ok_or_else(|| { |
| ConfigError::new( |
| format!("expected string, found {}", value.type_str()), |
| def.clone(), |
| ) |
| })?; |
| res.push((s.to_string(), def.clone())); |
| } |
| } |
| } |
| Ok(ConfigSeqAccess { |
| list_iter: res.into_iter(), |
| }) |
| } |
| } |
| |
| impl<'de> de::SeqAccess<'de> for ConfigSeqAccess { |
| type Error = ConfigError; |
| |
| fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error> |
| where |
| T: de::DeserializeSeed<'de>, |
| { |
| match self.list_iter.next() { |
| // TODO: add `def` to error? |
| Some((value, _def)) => seed.deserialize(value.into_deserializer()).map(Some), |
| None => Ok(None), |
| } |
| } |
| } |
| |
| /// Use with the `get` API to fetch a string that will be converted to a |
| /// `PathBuf`. Relative paths are converted to absolute paths based on the |
| /// location of the config file. |
| #[derive(Debug, Eq, PartialEq, Clone, Deserialize)] |
| pub struct ConfigRelativePath(PathBuf); |
| |
| impl ConfigRelativePath { |
| pub fn path(self) -> PathBuf { |
| self.0 |
| } |
| } |
| |
| #[derive(Eq, PartialEq, Clone)] |
| pub enum ConfigValue { |
| Integer(i64, PathBuf), |
| String(String, PathBuf), |
| List(Vec<(String, PathBuf)>, PathBuf), |
| Table(HashMap<String, ConfigValue>, PathBuf), |
| Boolean(bool, PathBuf), |
| } |
| |
| pub struct Value<T> { |
| pub val: T, |
| pub definition: Definition, |
| } |
| |
| pub type OptValue<T> = Option<Value<T>>; |
| |
| #[derive(Clone, Debug)] |
| pub enum Definition { |
| Path(PathBuf), |
| Environment(String), |
| } |
| |
| impl fmt::Debug for ConfigValue { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match *self { |
| CV::Integer(i, ref path) => write!(f, "{} (from {})", i, path.display()), |
| CV::Boolean(b, ref path) => write!(f, "{} (from {})", b, path.display()), |
| CV::String(ref s, ref path) => write!(f, "{} (from {})", s, path.display()), |
| CV::List(ref list, ref path) => { |
| write!(f, "[")?; |
| for (i, &(ref s, ref path)) in list.iter().enumerate() { |
| if i > 0 { |
| write!(f, ", ")?; |
| } |
| write!(f, "{} (from {})", s, path.display())?; |
| } |
| write!(f, "] (from {})", path.display()) |
| } |
| CV::Table(ref table, _) => write!(f, "{:?}", table), |
| } |
| } |
| } |
| |
| impl ConfigValue { |
| fn from_toml(path: &Path, toml: toml::Value) -> CargoResult<ConfigValue> { |
| match toml { |
| toml::Value::String(val) => Ok(CV::String(val, path.to_path_buf())), |
| toml::Value::Boolean(b) => Ok(CV::Boolean(b, path.to_path_buf())), |
| toml::Value::Integer(i) => Ok(CV::Integer(i, path.to_path_buf())), |
| toml::Value::Array(val) => Ok(CV::List( |
| val.into_iter() |
| .map(|toml| match toml { |
| toml::Value::String(val) => Ok((val, path.to_path_buf())), |
| v => failure::bail!("expected string but found {} in list", v.type_str()), |
| }) |
| .collect::<CargoResult<_>>()?, |
| path.to_path_buf(), |
| )), |
| toml::Value::Table(val) => Ok(CV::Table( |
| val.into_iter() |
| .map(|(key, value)| { |
| let value = CV::from_toml(path, value) |
| .chain_err(|| format!("failed to parse key `{}`", key))?; |
| Ok((key, value)) |
| }) |
| .collect::<CargoResult<_>>()?, |
| path.to_path_buf(), |
| )), |
| v => failure::bail!( |
| "found TOML configuration value of unknown type `{}`", |
| v.type_str() |
| ), |
| } |
| } |
| |
| fn into_toml(self) -> toml::Value { |
| match self { |
| CV::Boolean(s, _) => toml::Value::Boolean(s), |
| CV::String(s, _) => toml::Value::String(s), |
| CV::Integer(i, _) => toml::Value::Integer(i), |
| CV::List(l, _) => { |
| toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect()) |
| } |
| CV::Table(l, _) => { |
| toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect()) |
| } |
| } |
| } |
| |
| fn merge(&mut self, from: ConfigValue) -> CargoResult<()> { |
| match (self, from) { |
| (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => { |
| let new = mem::replace(new, Vec::new()); |
| old.extend(new.into_iter()); |
| } |
| (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => { |
| let new = mem::replace(new, HashMap::new()); |
| for (key, value) in new { |
| match old.entry(key.clone()) { |
| Occupied(mut entry) => { |
| let path = value.definition_path().to_path_buf(); |
| let entry = entry.get_mut(); |
| entry.merge(value).chain_err(|| { |
| format!( |
| "failed to merge key `{}` between \ |
| files:\n \ |
| file 1: {}\n \ |
| file 2: {}", |
| key, |
| entry.definition_path().display(), |
| path.display() |
| ) |
| })?; |
| } |
| Vacant(entry) => { |
| entry.insert(value); |
| } |
| }; |
| } |
| } |
| // Allow switching types except for tables or arrays. |
| (expected @ &mut CV::List(_, _), found) |
| | (expected @ &mut CV::Table(_, _), found) |
| | (expected, found @ CV::List(_, _)) |
| | (expected, found @ CV::Table(_, _)) => { |
| return Err(internal(format!( |
| "expected {}, but found {}", |
| expected.desc(), |
| found.desc() |
| ))); |
| } |
| _ => {} |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn i64(&self, key: &str) -> CargoResult<(i64, &Path)> { |
| match *self { |
| CV::Integer(i, ref p) => Ok((i, p)), |
| _ => self.expected("integer", key), |
| } |
| } |
| |
| pub fn string(&self, key: &str) -> CargoResult<(&str, &Path)> { |
| match *self { |
| CV::String(ref s, ref p) => Ok((s, p)), |
| _ => self.expected("string", key), |
| } |
| } |
| |
| pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Path)> { |
| match *self { |
| CV::Table(ref table, ref p) => Ok((table, p)), |
| _ => self.expected("table", key), |
| } |
| } |
| |
| pub fn list(&self, key: &str) -> CargoResult<&[(String, PathBuf)]> { |
| match *self { |
| CV::List(ref list, _) => Ok(list), |
| _ => self.expected("list", key), |
| } |
| } |
| |
| pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Path)> { |
| match *self { |
| CV::Boolean(b, ref p) => Ok((b, p)), |
| _ => self.expected("bool", key), |
| } |
| } |
| |
| pub fn desc(&self) -> &'static str { |
| match *self { |
| CV::Table(..) => "table", |
| CV::List(..) => "array", |
| CV::String(..) => "string", |
| CV::Boolean(..) => "boolean", |
| CV::Integer(..) => "integer", |
| } |
| } |
| |
| pub fn definition_path(&self) -> &Path { |
| match *self { |
| CV::Boolean(_, ref p) |
| | CV::Integer(_, ref p) |
| | CV::String(_, ref p) |
| | CV::List(_, ref p) |
| | CV::Table(_, ref p) => p, |
| } |
| } |
| |
| fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> { |
| failure::bail!( |
| "expected a {}, but found a {} for `{}` in {}", |
| wanted, |
| self.desc(), |
| key, |
| self.definition_path().display() |
| ) |
| } |
| } |
| |
| impl Definition { |
| pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path { |
| match *self { |
| Definition::Path(ref p) => p.parent().unwrap().parent().unwrap(), |
| Definition::Environment(_) => config.cwd(), |
| } |
| } |
| } |
| |
| impl fmt::Display for Definition { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match *self { |
| Definition::Path(ref p) => p.display().fmt(f), |
| Definition::Environment(ref key) => write!(f, "environment variable `{}`", key), |
| } |
| } |
| } |
| |
| pub fn homedir(cwd: &Path) -> Option<PathBuf> { |
| ::home::cargo_home_with_cwd(cwd).ok() |
| } |
| |
| fn walk_tree<F>(pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()> |
| where |
| F: FnMut(&Path) -> CargoResult<()>, |
| { |
| let mut stash: HashSet<PathBuf> = HashSet::new(); |
| |
| for current in paths::ancestors(pwd) { |
| let possible = current.join(".cargo").join("config"); |
| if fs::metadata(&possible).is_ok() { |
| walk(&possible)?; |
| stash.insert(possible); |
| } |
| } |
| |
| // Once we're done, also be sure to walk the home directory even if it's not |
| // in our history to be sure we pick up that standard location for |
| // information. |
| let config = home.join("config"); |
| if !stash.contains(&config) && fs::metadata(&config).is_ok() { |
| walk(&config)?; |
| } |
| |
| Ok(()) |
| } |
| |
| pub fn save_credentials(cfg: &Config, token: String, registry: Option<String>) -> CargoResult<()> { |
| let mut file = { |
| cfg.home_path.create_dir()?; |
| cfg.home_path |
| .open_rw(Path::new("credentials"), cfg, "credentials' config file")? |
| }; |
| |
| let (key, value) = { |
| let key = "token".to_string(); |
| let value = ConfigValue::String(token, file.path().to_path_buf()); |
| let mut map = HashMap::new(); |
| map.insert(key, value); |
| let table = CV::Table(map, file.path().to_path_buf()); |
| |
| if let Some(registry) = registry { |
| let mut map = HashMap::new(); |
| map.insert(registry, table); |
| ( |
| "registries".into(), |
| CV::Table(map, file.path().to_path_buf()), |
| ) |
| } else { |
| ("registry".into(), table) |
| } |
| }; |
| |
| let mut contents = String::new(); |
| file.read_to_string(&mut contents).chain_err(|| { |
| format!( |
| "failed to read configuration file `{}`", |
| file.path().display() |
| ) |
| })?; |
| |
| let mut toml = cargo_toml::parse(&contents, file.path(), cfg)?; |
| |
| // Move the old token location to the new one. |
| if let Some(token) = toml.as_table_mut().unwrap().remove("token") { |
| let mut map = HashMap::new(); |
| map.insert("token".to_string(), token); |
| toml.as_table_mut() |
| .unwrap() |
| .insert("registry".into(), map.into()); |
| } |
| |
| toml.as_table_mut().unwrap().insert(key, value.into_toml()); |
| |
| let contents = toml.to_string(); |
| file.seek(SeekFrom::Start(0))?; |
| file.write_all(contents.as_bytes())?; |
| file.file().set_len(contents.len() as u64)?; |
| set_permissions(file.file(), 0o600)?; |
| |
| return Ok(()); |
| |
| #[cfg(unix)] |
| fn set_permissions(file: &File, mode: u32) -> CargoResult<()> { |
| use std::os::unix::fs::PermissionsExt; |
| |
| let mut perms = file.metadata()?.permissions(); |
| perms.set_mode(mode); |
| file.set_permissions(perms)?; |
| Ok(()) |
| } |
| |
| #[cfg(not(unix))] |
| #[allow(unused)] |
| fn set_permissions(file: &File, mode: u32) -> CargoResult<()> { |
| Ok(()) |
| } |
| } |
| |
| pub struct PackageCacheLock<'a>(&'a Config); |
| |
| impl Drop for PackageCacheLock<'_> { |
| fn drop(&mut self) { |
| let mut slot = self.0.package_cache_lock.borrow_mut(); |
| let (_, cnt) = slot.as_mut().unwrap(); |
| *cnt -= 1; |
| if *cnt == 0 { |
| *slot = None; |
| } |
| } |
| } |