From 442350b769225d7bf3f78235107a7897e77c606c Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Wed, 1 Apr 2020 16:32:10 +0200 Subject: [PATCH] Use platform-defined directories for cargo state This commit is a continuation and adaptation of #5183, which aimed to make cargo no longer reliant on the `$HOME/.cargo` directory in user's home's, and instead uses the `directories` crate to get platform-defined standard directories for data, caches, and configs. The priority of paths cargo will check is as follows: 1. Use `$CARGO_HOME`, if it is set 2. Use `$CARGO_CACHE_DIR`, `$CARGO_CONFIG_DIR`, etc, if they are set 3. If no environment variables are set, and `$HOME/.cargo` is present, use that 4. Finally, use the platform-default directory paths --- Cargo.toml | 1 + src/cargo/util/config/dirs.rs | 79 ++++++++++++++++++++++++++++++++ src/cargo/util/config/mod.rs | 42 +++++++++-------- tests/testsuite/config.rs | 2 +- tests/testsuite/login.rs | 2 +- tests/testsuite/member_errors.rs | 3 +- tests/testsuite/search.rs | 3 +- 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 src/cargo/util/config/dirs.rs diff --git a/Cargo.toml b/Cargo.toml index 088ed28bd40..ff0b2dcef43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ crossbeam-utils = "0.7" crypto-hash = "0.3.1" curl = { version = "0.4.23", features = ["http2"] } curl-sys = "0.4.22" +directories = "2.0" env_logger = "0.7.0" pretty_env_logger = { version = "0.4", optional = true } anyhow = "1.0" diff --git a/src/cargo/util/config/dirs.rs b/src/cargo/util/config/dirs.rs new file mode 100644 index 00000000000..49a4e38eca6 --- /dev/null +++ b/src/cargo/util/config/dirs.rs @@ -0,0 +1,79 @@ +//! An abstraction over what directories cargo should use for state + +use crate::util::{ + config::Filesystem, + errors::{CargoResult, CargoResultExt}, +}; +use directories::ProjectDirs; +use log::debug; +use std::env; +use std::path::PathBuf; + +#[derive(Clone, Debug)] +pub struct CargoDirs { + /// Main directory for cargo data + pub data_dir: Filesystem, + /// Caching registry artefacts (previously .cargo/registry/cache) + pub cache_dir: Filesystem, + /// Kept to walk upwards the directory tree to find a Cargo.toml + pub home_dir: Filesystem, +} + +impl CargoDirs { + /// Constructs the hierarchy of directories that cargo will use + pub fn new(home_dir: PathBuf) -> CargoResult { + let current_dir = + env::current_dir().chain_err(|| "couldn't get the current directory of the process")?; + + let mut cache_dir = PathBuf::default(); + let mut data_dir = PathBuf::default(); + + // 1. CARGO_HOME set + let cargo_home_env = env::var_os("CARGO_HOME").map(|home| current_dir.join(home)); + if let Some(cargo_home) = cargo_home_env.clone() { + cache_dir = cargo_home.clone(); + data_dir = cargo_home.clone(); + } + + // 2. CARGO_CACHE_DIR, CARGO_CONFIG_DIR, CARGO_BIN_DIR, ... set + let cargo_cache_env = env::var_os("CARGO_CACHE_DIR").map(|home| current_dir.join(home)); + let cargo_data_env = env::var_os("CARGO_DATA_DIR").map(|home| current_dir.join(home)); + + if let Some(cargo_cache) = cargo_cache_env.clone() { + cache_dir = cargo_cache.clone(); + } + if let Some(cargo_data) = cargo_data_env.clone() { + data_dir = cargo_data.clone(); + } + + // none of the env vars are set ... + if cargo_home_env.is_none() && cargo_cache_env.is_none() && cargo_data_env.is_none() { + let legacy_cargo_dir = home_dir.join(".cargo"); + + // 3. ... and .cargo exist + if legacy_cargo_dir.exists() { + debug!("Using legacy paths at $HOME, consider moving to $XDG_DATA_HOME"); + cache_dir = legacy_cargo_dir.clone(); + data_dir = legacy_cargo_dir.clone(); + + // 4. ... otherwise follow platform conventions + } else { + let xdg_dirs = match ProjectDirs::from("org", "rust-lang", "cargo") { + Some(d) => Ok(d), + None => Err(anyhow::format_err!( + "failed to get directories according to XDG settings" + )), + }?; + + cache_dir = xdg_dirs.cache_dir().to_path_buf(); + data_dir = xdg_dirs.data_dir().to_path_buf(); + } + } + + dbg!(Ok(CargoDirs { + cache_dir: Filesystem::new(cache_dir), + data_dir: Filesystem::new(data_dir), + home_dir: Filesystem::new(home_dir), + })) + } +} diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 2bc261ff3d8..511f27c3de6 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -81,6 +81,9 @@ use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc}; mod de; use de::Deserializer; +mod dirs; +use dirs::CargoDirs; + mod value; pub use value::{Definition, OptValue, Value}; @@ -122,7 +125,7 @@ macro_rules! get_value_typed { #[derive(Debug)] pub struct Config { /// The location of the user's 'home' directory. OS-dependent. - home_path: Filesystem, + dirs: CargoDirs, /// Information about how to write messages to the shell shell: RefCell, /// A collection of configuration options @@ -182,7 +185,7 @@ impl Config { /// /// This does only minimal initialization. In particular, it does not load /// any config files from disk. Those will be loaded lazily as-needed. - pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> Config { + pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> CargoResult { static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _; static INIT: Once = Once::new(); @@ -209,8 +212,8 @@ impl Config { _ => true, }; - Config { - home_path: Filesystem::new(homedir), + Ok(Config { + dirs: CargoDirs::new(homedir)?, shell: RefCell::new(shell), cwd, values: LazyCell::new(), @@ -241,7 +244,7 @@ impl Config { net_config: LazyCell::new(), build_config: LazyCell::new(), target_cfgs: LazyCell::new(), - } + }) } /// Creates a new Config instance, with all default settings. @@ -258,32 +261,32 @@ impl Config { This probably means that $HOME was not set." ) })?; - Ok(Config::new(shell, cwd, homedir)) + Config::new(shell, cwd, homedir) } /// Gets the user's Cargo home directory (OS-dependent). pub fn home(&self) -> &Filesystem { - &self.home_path + &self.dirs.data_dir } /// Gets the Cargo Git directory (`/git`). pub fn git_path(&self) -> Filesystem { - self.home_path.join("git") + self.dirs.data_dir.join("git") } /// Gets the Cargo registry index directory (`/registry/index`). pub fn registry_index_path(&self) -> Filesystem { - self.home_path.join("registry").join("index") + self.dirs.data_dir.join("registry").join("index") } /// Gets the Cargo registry cache directory (`/registry/path`). pub fn registry_cache_path(&self) -> Filesystem { - self.home_path.join("registry").join("cache") + self.dirs.cache_dir.clone() } /// Gets the Cargo registry source directory (`/registry/src`). pub fn registry_source_path(&self) -> Filesystem { - self.home_path.join("registry").join("src") + self.dirs.data_dir.join("registry").join("src") } /// Gets the default Cargo registry. @@ -781,7 +784,7 @@ impl Config { // This definition path is ignored, this is just a temporary container // representing the entire file. let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from("."))); - let home = self.home_path.clone().into_path_unlocked(); + let home = self.dirs.home_dir.clone().into_path_unlocked(); self.walk_tree(path, &home, |path| { let value = self.load_file(path)?; @@ -1040,7 +1043,7 @@ impl Config { /// Loads credentials config from the credentials file, if present. pub fn load_credentials(&mut self) -> CargoResult<()> { - let home_path = self.home_path.clone().into_path_unlocked(); + let home_path = self.dirs.data_dir.clone().into_path_unlocked(); let credentials = match self.get_file_path(&home_path, "credentials", true)? { Some(credentials) => credentials, None => return Ok(()), @@ -1197,7 +1200,7 @@ impl Config { "package 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())); + assert!(ret.starts_with(self.dirs.cache_dir.as_path_unlocked())); ret } @@ -1234,11 +1237,11 @@ impl Config { // 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) { + match self.dirs.data_dir.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(); + let lock = self.dirs.data_dir.open_ro(path, self, desc).ok(); *slot = Some((lock, 1)); return Ok(PackageCacheLock(self)); } @@ -1556,7 +1559,7 @@ pub fn save_credentials(cfg: &Config, token: String, registry: Option) - // If 'credentials.toml' exists, we should write to that, otherwise // use the legacy 'credentials'. There's no need to print the warning // here, because it would already be printed at load time. - let home_path = cfg.home_path.clone().into_path_unlocked(); + let home_path = cfg.dirs.data_dir.clone().into_path_unlocked(); let filename = match cfg.get_file_path(&home_path, "credentials", false)? { Some(path) => match path.file_name() { Some(filename) => Path::new(filename).to_owned(), @@ -1566,8 +1569,9 @@ pub fn save_credentials(cfg: &Config, token: String, registry: Option) - }; let mut file = { - cfg.home_path.create_dir()?; - cfg.home_path + cfg.dirs.data_dir.create_dir()?; + cfg.dirs + .data_dir .open_rw(filename, cfg, "credentials' config file")? }; diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 46dbca69d50..5486a3024d7 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -74,7 +74,7 @@ impl ConfigBuilder { let shell = Shell::from_write(output); let cwd = self.cwd.clone().unwrap_or_else(|| paths::root()); let homedir = paths::home(); - let mut config = Config::new(shell, cwd, homedir); + let mut config = Config::new(shell, cwd, homedir)?; config.set_env(self.env.clone()); config.configure( 0, diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index 33129141a5e..383d3a29ae8 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -138,7 +138,7 @@ fn new_credentials_is_used_instead_old() { .arg(TOKEN) .run(); - let mut config = Config::new(Shell::new(), cargo_home(), cargo_home()); + let mut config = Config::new(Shell::new(), cargo_home(), cargo_home()).unwrap(); let _ = config.values(); let _ = config.load_credentials(); diff --git a/tests/testsuite/member_errors.rs b/tests/testsuite/member_errors.rs index 10024dad74c..d7306ecf4d4 100644 --- a/tests/testsuite/member_errors.rs +++ b/tests/testsuite/member_errors.rs @@ -149,7 +149,8 @@ fn member_manifest_version_error() { Shell::from_write(Box::new(Vec::new())), cargo_home(), cargo_home(), - ); + ) + .unwrap(); let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap(); let compile_options = CompileOptions::new(&config, CompileMode::Build).unwrap(); let member_bar = ws.members().find(|m| &*m.name() == "bar").unwrap(); diff --git a/tests/testsuite/search.rs b/tests/testsuite/search.rs index 6229358e602..243498fd9b8 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -113,7 +113,8 @@ fn not_update() { Shell::from_write(Box::new(Vec::new())), paths::root(), paths::home().join(".cargo"), - ); + ) + .unwrap(); let lock = cfg.acquire_package_cache_lock().unwrap(); let mut regsrc = RegistrySource::remote(sid, &HashSet::new(), &cfg); regsrc.update().unwrap();