diff --git a/Cargo.toml b/Cargo.toml index b7dc747b80a..8bb0711cf25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ crypto-hash = "0.3.1" curl = { version = "0.4.23", features = ["http2"] } curl-sys = "0.4.22" env_logger = "0.8.1" +directories = "1.0" pretty_env_logger = { version = "0.4", optional = true } anyhow = "1.0" filetime = "0.2.9" diff --git a/src/cargo/util/config/dirs.rs b/src/cargo/util/config/dirs.rs new file mode 100644 index 00000000000..6ee4c7badac --- /dev/null +++ b/src/cargo/util/config/dirs.rs @@ -0,0 +1,137 @@ +//! 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 { + /// The dir cargo was run in + pub current_dir: PathBuf, + /// Main directory for cargo data + pub data_dir: Filesystem, + /// Immutable global cargo configuration + pub config_dir: Filesystem, + /// Caching registry artefacts (previously .cargo/registry/cache) + pub cache_dir: Filesystem, + /// Directory where binaries are kept + pub bin_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 config_dir = PathBuf::default(); + let mut data_dir = PathBuf::default(); + let mut bin_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(); + config_dir = cargo_home.clone(); + data_dir = cargo_home.clone(); + bin_dir = cargo_home.join("bin"); + } + + // 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_config_env = env::var_os("CARGO_CONFIG_DIR").map(|home| current_dir.join(home)); + let cargo_data_env = env::var_os("CARGO_DATA_DIR").map(|home| current_dir.join(home)); + let cargo_bin_env = env::var_os("CARGO_BIN_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_config) = cargo_config_env.clone() { + config_dir = cargo_config.clone(); + } + if let Some(cargo_data) = cargo_data_env.clone() { + data_dir = cargo_data.clone(); + } + if let Some(cargo_bin) = cargo_bin_env.clone() { + bin_dir = cargo_bin.clone(); + } + + // no env vars are set ... + if cargo_home_env.is_none() + && cargo_cache_env.is_none() + && cargo_config_env.is_none() + && cargo_data_env.is_none() + && cargo_bin_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(); + config_dir = legacy_cargo_dir.clone(); + data_dir = legacy_cargo_dir.clone(); + bin_dir = legacy_cargo_dir.join("bin"); + + // 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(); + config_dir = xdg_dirs.config_dir().to_path_buf(); + data_dir = xdg_dirs.data_dir().to_path_buf(); + bin_dir = CargoDirs::find_bin_dir(&xdg_dirs) + .ok_or_else(|| { + anyhow::format_err!( + "couldn't find the directory in which executables are placed" + ) + })? + .to_path_buf(); + } + } + + Ok(CargoDirs { + current_dir, + cache_dir: Filesystem::new(cache_dir), + config_dir: Filesystem::new(config_dir), + data_dir: Filesystem::new(data_dir), + bin_dir: Filesystem::new(bin_dir), + home_dir: Filesystem::new(home_dir), + }) + } + + #[cfg(target_os = "linux")] + fn find_bin_dir(_dirs: &ProjectDirs) -> Option { + use directories::BaseDirs; + let base_dir = BaseDirs::new()?; + base_dir.executable_dir().map(|p| p.to_path_buf()) + } + + #[cfg(target_os = "macos")] + fn find_bin_dir(dirs: &ProjectDirs) -> Option { + dirs.data_dir() + .parent() + .map(|p| p.join("bin")) + .map(|p| p.to_path_buf()) + } + + #[cfg(target_os = "windows")] + fn find_bin_dir(dirs: &ProjectDirs) -> Option { + dirs.data_dir() + .parent() + .map(|p| p.join("bin")) + .map(|p| p.to_path_buf()) + } +} diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 7cea1bcecc3..2ee1c03554e 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -82,6 +82,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,8 +125,8 @@ macro_rules! get_value_typed { /// relating to cargo itself. #[derive(Debug)] pub struct Config { - /// The location of the user's Cargo home directory. OS-dependent. - home_path: Filesystem, + /// The location of the user's 'home' directory. OS-dependent. + dirs: CargoDirs, /// Information about how to write messages to the shell shell: RefCell, /// A collection of configuration options @@ -191,7 +194,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(); @@ -227,8 +230,8 @@ impl Config { _ => true, }; - Config { - home_path: Filesystem::new(homedir), + Ok(Config { + dirs: CargoDirs::new(homedir)?, shell: RefCell::new(shell), cwd, search_stop_path: None, @@ -264,7 +267,7 @@ impl Config { target_cfgs: LazyCell::new(), doc_extern_map: LazyCell::new(), progress_config: ProgressConfig::default(), - } + }) } /// Creates a new Config instance, with all default settings. @@ -281,32 +284,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. @@ -870,7 +873,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)?; @@ -1137,7 +1140,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(()), @@ -1316,7 +1319,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 } @@ -1353,11 +1356,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)); } @@ -1677,7 +1680,7 @@ pub fn save_credentials( // 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(), @@ -1687,8 +1690,9 @@ pub fn save_credentials( }; 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/build.rs b/tests/testsuite/build.rs index 7a9a82612d2..87432929a8c 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -431,7 +431,7 @@ fn cargo_compile_api_exposes_artifact_paths() { .build(); let shell = Shell::from_write(Box::new(Vec::new())); - let config = Config::new(shell, env::current_dir().unwrap(), paths::home()); + let config = Config::new(shell, env::current_dir().unwrap(), paths::home()).unwrap(); let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap(); let compile_options = CompileOptions::new(ws.config(), CompileMode::Build).unwrap(); diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 1b45b65b307..1608783c630 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -75,7 +75,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.set_search_stop_path(paths::root()); config.configure( diff --git a/tests/testsuite/login.rs b/tests/testsuite/login.rs index 835461af8de..64abc4a33bd 100644 --- a/tests/testsuite/login.rs +++ b/tests/testsuite/login.rs @@ -135,7 +135,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_discovery.rs b/tests/testsuite/member_discovery.rs index e882add3e1c..d80f32f6289 100644 --- a/tests/testsuite/member_discovery.rs +++ b/tests/testsuite/member_discovery.rs @@ -37,7 +37,8 @@ fn bad_file_member_exclusion() { Shell::from_write(Box::new(Vec::new())), cargo_home(), cargo_home(), - ); + ) + .unwrap(); let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap(); assert_eq!(ws.members().count(), 1); assert_eq!(ws.members().next().unwrap().name(), "bar"); diff --git a/tests/testsuite/member_errors.rs b/tests/testsuite/member_errors.rs index 10533b292c6..2954d4263bb 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 0d239b3b455..390135f39ef 100644 --- a/tests/testsuite/search.rs +++ b/tests/testsuite/search.rs @@ -148,7 +148,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();