Skip to content

Commit 57b1d16

Browse files
committed
Use platform-defined directories for cargo state
This commit changes the cargo data directories from $HOME/.cargo to using the XDG spec directories. It also adds the directories crate as a dependency. Currently, only `XGD_DATA_HOME` (on Linux this is `$HOME/.local/share` by default) and `XDG_CACHE_HOME` (on Linux this is `$HOME/.cache` by default) are used. 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
1 parent 3e25c9d commit 57b1d16

File tree

9 files changed

+171
-26
lines changed

9 files changed

+171
-26
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ crypto-hash = "0.3.1"
2828
curl = { version = "0.4.23", features = ["http2"] }
2929
curl-sys = "0.4.22"
3030
env_logger = "0.8.1"
31+
directories = "1.0"
3132
pretty_env_logger = { version = "0.4", optional = true }
3233
anyhow = "1.0"
3334
filetime = "0.2.9"

src/cargo/util/config/dirs.rs

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! An abstraction over what directories cargo should use for state
2+
3+
use crate::util::{
4+
config::Filesystem,
5+
errors::{CargoResult, CargoResultExt},
6+
};
7+
use directories::ProjectDirs;
8+
use log::debug;
9+
use std::env;
10+
use std::path::PathBuf;
11+
12+
#[derive(Clone, Debug)]
13+
pub struct CargoDirs {
14+
/// The dir cargo was run in
15+
pub current_dir: PathBuf,
16+
/// Main directory for cargo data
17+
pub data_dir: Filesystem,
18+
/// Immutable global cargo configuration
19+
pub config_dir: Filesystem,
20+
/// Caching registry artefacts (previously .cargo/registry/cache)
21+
pub cache_dir: Filesystem,
22+
/// Directory where binaries are kept
23+
pub bin_dir: Filesystem,
24+
/// Kept to walk upwards the directory tree to find a Cargo.toml
25+
pub home_dir: Filesystem,
26+
}
27+
28+
impl CargoDirs {
29+
/// Constructs the hierarchy of directories that cargo will use
30+
pub fn new(home_dir: PathBuf) -> CargoResult<CargoDirs> {
31+
let current_dir =
32+
env::current_dir().chain_err(|| "couldn't get the current directory of the process")?;
33+
34+
let mut cache_dir = PathBuf::default();
35+
let mut config_dir = PathBuf::default();
36+
let mut data_dir = PathBuf::default();
37+
let mut bin_dir = PathBuf::default();
38+
39+
// 1. CARGO_HOME set
40+
let cargo_home_env = env::var_os("CARGO_HOME").map(|home| current_dir.join(home));
41+
if let Some(cargo_home) = cargo_home_env.clone() {
42+
cache_dir = cargo_home.clone();
43+
config_dir = cargo_home.clone();
44+
data_dir = cargo_home.clone();
45+
bin_dir = cargo_home.join("bin");
46+
}
47+
48+
// 2. CARGO_CACHE_DIR, CARGO_CONFIG_DIR, CARGO_BIN_DIR, ... set
49+
let cargo_cache_env = env::var_os("CARGO_CACHE_DIR").map(|home| current_dir.join(home));
50+
let cargo_config_env = env::var_os("CARGO_CONFIG_DIR").map(|home| current_dir.join(home));
51+
let cargo_data_env = env::var_os("CARGO_DATA_DIR").map(|home| current_dir.join(home));
52+
let cargo_bin_env = env::var_os("CARGO_BIN_DIR").map(|home| current_dir.join(home));
53+
if let Some(cargo_cache) = cargo_cache_env.clone() {
54+
cache_dir = cargo_cache.clone();
55+
}
56+
if let Some(cargo_config) = cargo_config_env.clone() {
57+
config_dir = cargo_config.clone();
58+
}
59+
if let Some(cargo_data) = cargo_data_env.clone() {
60+
data_dir = cargo_data.clone();
61+
}
62+
if let Some(cargo_bin) = cargo_bin_env.clone() {
63+
bin_dir = cargo_bin.clone();
64+
}
65+
66+
// no env vars are set ...
67+
if cargo_home_env.is_none()
68+
&& cargo_cache_env.is_none()
69+
&& cargo_config_env.is_none()
70+
&& cargo_data_env.is_none()
71+
&& cargo_bin_env.is_none()
72+
{
73+
let legacy_cargo_dir = home_dir.join(".cargo");
74+
75+
// 3. ... and .cargo exist
76+
if legacy_cargo_dir.exists() {
77+
debug!("Using legacy paths at $HOME, consider moving to $XDG_DATA_HOME");
78+
cache_dir = legacy_cargo_dir.clone();
79+
config_dir = legacy_cargo_dir.clone();
80+
data_dir = legacy_cargo_dir.clone();
81+
bin_dir = legacy_cargo_dir.join("bin");
82+
83+
// 4. ... otherwise follow platform conventions
84+
} else {
85+
let xdg_dirs = match ProjectDirs::from("org", "rust-lang", "cargo") {
86+
Some(d) => Ok(d),
87+
None => Err(anyhow::format_err!(
88+
"failed to get directories according to XDG settings"
89+
)),
90+
}?;
91+
92+
cache_dir = xdg_dirs.cache_dir().to_path_buf();
93+
config_dir = xdg_dirs.config_dir().to_path_buf();
94+
data_dir = xdg_dirs.data_dir().to_path_buf();
95+
bin_dir = CargoDirs::find_bin_dir(&xdg_dirs)
96+
.ok_or_else(|| {
97+
anyhow::format_err!(
98+
"couldn't find the directory in which executables are placed"
99+
)
100+
})?
101+
.to_path_buf();
102+
}
103+
}
104+
105+
Ok(CargoDirs {
106+
current_dir,
107+
cache_dir: Filesystem::new(cache_dir),
108+
config_dir: Filesystem::new(config_dir),
109+
data_dir: Filesystem::new(data_dir),
110+
bin_dir: Filesystem::new(bin_dir),
111+
home_dir: Filesystem::new(home_dir),
112+
})
113+
}
114+
115+
#[cfg(target_os = "linux")]
116+
fn find_bin_dir(_dirs: &ProjectDirs) -> Option<PathBuf> {
117+
use directories::BaseDirs;
118+
let base_dir = BaseDirs::new()?;
119+
base_dir.executable_dir().map(|p| p.to_path_buf())
120+
}
121+
122+
#[cfg(target_os = "macos")]
123+
fn find_bin_dir(dirs: &ProjectDirs) -> Option<PathBuf> {
124+
dirs.data_dir()
125+
.parent()
126+
.map(|p| p.join("bin"))
127+
.map(|p| p.to_path_buf())
128+
}
129+
130+
#[cfg(target_os = "windows")]
131+
fn find_bin_dir(dirs: &ProjectDirs) -> Option<PathBuf> {
132+
dirs.data_dir()
133+
.parent()
134+
.map(|p| p.join("bin"))
135+
.map(|p| p.to_path_buf())
136+
}
137+
}

src/cargo/util/config/mod.rs

+24-20
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
8282
mod de;
8383
use de::Deserializer;
8484

85+
mod dirs;
86+
use dirs::CargoDirs;
87+
8588
mod value;
8689
pub use value::{Definition, OptValue, Value};
8790

@@ -122,8 +125,8 @@ macro_rules! get_value_typed {
122125
/// relating to cargo itself.
123126
#[derive(Debug)]
124127
pub struct Config {
125-
/// The location of the user's Cargo home directory. OS-dependent.
126-
home_path: Filesystem,
128+
/// The location of the user's 'home' directory. OS-dependent.
129+
dirs: CargoDirs,
127130
/// Information about how to write messages to the shell
128131
shell: RefCell<Shell>,
129132
/// A collection of configuration options
@@ -191,7 +194,7 @@ impl Config {
191194
///
192195
/// This does only minimal initialization. In particular, it does not load
193196
/// any config files from disk. Those will be loaded lazily as-needed.
194-
pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> Config {
197+
pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> CargoResult<Config> {
195198
static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
196199
static INIT: Once = Once::new();
197200

@@ -227,8 +230,8 @@ impl Config {
227230
_ => true,
228231
};
229232

230-
Config {
231-
home_path: Filesystem::new(homedir),
233+
Ok(Config {
234+
dirs: CargoDirs::new(homedir)?,
232235
shell: RefCell::new(shell),
233236
cwd,
234237
search_stop_path: None,
@@ -264,7 +267,7 @@ impl Config {
264267
target_cfgs: LazyCell::new(),
265268
doc_extern_map: LazyCell::new(),
266269
progress_config: ProgressConfig::default(),
267-
}
270+
})
268271
}
269272

270273
/// Creates a new Config instance, with all default settings.
@@ -281,32 +284,32 @@ impl Config {
281284
This probably means that $HOME was not set."
282285
)
283286
})?;
284-
Ok(Config::new(shell, cwd, homedir))
287+
Config::new(shell, cwd, homedir)
285288
}
286289

287290
/// Gets the user's Cargo home directory (OS-dependent).
288291
pub fn home(&self) -> &Filesystem {
289-
&self.home_path
292+
&self.dirs.data_dir
290293
}
291294

292295
/// Gets the Cargo Git directory (`<cargo_home>/git`).
293296
pub fn git_path(&self) -> Filesystem {
294-
self.home_path.join("git")
297+
self.dirs.data_dir.join("git")
295298
}
296299

297300
/// Gets the Cargo registry index directory (`<cargo_home>/registry/index`).
298301
pub fn registry_index_path(&self) -> Filesystem {
299-
self.home_path.join("registry").join("index")
302+
self.dirs.data_dir.join("registry").join("index")
300303
}
301304

302305
/// Gets the Cargo registry cache directory (`<cargo_home>/registry/path`).
303306
pub fn registry_cache_path(&self) -> Filesystem {
304-
self.home_path.join("registry").join("cache")
307+
self.dirs.cache_dir.clone()
305308
}
306309

307310
/// Gets the Cargo registry source directory (`<cargo_home>/registry/src`).
308311
pub fn registry_source_path(&self) -> Filesystem {
309-
self.home_path.join("registry").join("src")
312+
self.dirs.data_dir.join("registry").join("src")
310313
}
311314

312315
/// Gets the default Cargo registry.
@@ -870,7 +873,7 @@ impl Config {
870873
// This definition path is ignored, this is just a temporary container
871874
// representing the entire file.
872875
let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
873-
let home = self.home_path.clone().into_path_unlocked();
876+
let home = self.dirs.home_dir.clone().into_path_unlocked();
874877

875878
self.walk_tree(path, &home, |path| {
876879
let value = self.load_file(path)?;
@@ -1137,7 +1140,7 @@ impl Config {
11371140

11381141
/// Loads credentials config from the credentials file, if present.
11391142
pub fn load_credentials(&mut self) -> CargoResult<()> {
1140-
let home_path = self.home_path.clone().into_path_unlocked();
1143+
let home_path = self.dirs.data_dir.clone().into_path_unlocked();
11411144
let credentials = match self.get_file_path(&home_path, "credentials", true)? {
11421145
Some(credentials) => credentials,
11431146
None => return Ok(()),
@@ -1316,7 +1319,7 @@ impl Config {
13161319
"package cache lock is not currently held, Cargo forgot to call \
13171320
`acquire_package_cache_lock` before we got to this stack frame",
13181321
);
1319-
assert!(ret.starts_with(self.home_path.as_path_unlocked()));
1322+
assert!(ret.starts_with(self.dirs.cache_dir.as_path_unlocked()));
13201323
ret
13211324
}
13221325

@@ -1353,11 +1356,11 @@ impl Config {
13531356
// someone else on the system we should synchronize with them,
13541357
// but if we can't even do that then we did our best and we just
13551358
// keep on chugging elsewhere.
1356-
match self.home_path.open_rw(path, self, desc) {
1359+
match self.dirs.data_dir.open_rw(path, self, desc) {
13571360
Ok(lock) => *slot = Some((Some(lock), 1)),
13581361
Err(e) => {
13591362
if maybe_readonly(&e) {
1360-
let lock = self.home_path.open_ro(path, self, desc).ok();
1363+
let lock = self.dirs.data_dir.open_ro(path, self, desc).ok();
13611364
*slot = Some((lock, 1));
13621365
return Ok(PackageCacheLock(self));
13631366
}
@@ -1677,7 +1680,7 @@ pub fn save_credentials(
16771680
// If 'credentials.toml' exists, we should write to that, otherwise
16781681
// use the legacy 'credentials'. There's no need to print the warning
16791682
// here, because it would already be printed at load time.
1680-
let home_path = cfg.home_path.clone().into_path_unlocked();
1683+
let home_path = cfg.dirs.data_dir.clone().into_path_unlocked();
16811684
let filename = match cfg.get_file_path(&home_path, "credentials", false)? {
16821685
Some(path) => match path.file_name() {
16831686
Some(filename) => Path::new(filename).to_owned(),
@@ -1687,8 +1690,9 @@ pub fn save_credentials(
16871690
};
16881691

16891692
let mut file = {
1690-
cfg.home_path.create_dir()?;
1691-
cfg.home_path
1693+
cfg.dirs.data_dir.create_dir()?;
1694+
cfg.dirs
1695+
.data_dir
16921696
.open_rw(filename, cfg, "credentials' config file")?
16931697
};
16941698

tests/testsuite/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ fn cargo_compile_api_exposes_artifact_paths() {
431431
.build();
432432

433433
let shell = Shell::from_write(Box::new(Vec::new()));
434-
let config = Config::new(shell, env::current_dir().unwrap(), paths::home());
434+
let config = Config::new(shell, env::current_dir().unwrap(), paths::home()).unwrap();
435435
let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
436436
let compile_options = CompileOptions::new(ws.config(), CompileMode::Build).unwrap();
437437

tests/testsuite/config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl ConfigBuilder {
7575
let shell = Shell::from_write(output);
7676
let cwd = self.cwd.clone().unwrap_or_else(|| paths::root());
7777
let homedir = paths::home();
78-
let mut config = Config::new(shell, cwd, homedir);
78+
let mut config = Config::new(shell, cwd, homedir)?;
7979
config.set_env(self.env.clone());
8080
config.set_search_stop_path(paths::root());
8181
config.configure(

tests/testsuite/login.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ fn new_credentials_is_used_instead_old() {
135135
.arg(TOKEN)
136136
.run();
137137

138-
let mut config = Config::new(Shell::new(), cargo_home(), cargo_home());
138+
let mut config = Config::new(Shell::new(), cargo_home(), cargo_home()).unwrap();
139139
let _ = config.values();
140140
let _ = config.load_credentials();
141141

tests/testsuite/member_discovery.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ fn bad_file_member_exclusion() {
3737
Shell::from_write(Box::new(Vec::new())),
3838
cargo_home(),
3939
cargo_home(),
40-
);
40+
)
41+
.unwrap();
4142
let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
4243
assert_eq!(ws.members().count(), 1);
4344
assert_eq!(ws.members().next().unwrap().name(), "bar");

tests/testsuite/member_errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ fn member_manifest_version_error() {
149149
Shell::from_write(Box::new(Vec::new())),
150150
cargo_home(),
151151
cargo_home(),
152-
);
152+
)
153+
.unwrap();
153154
let ws = Workspace::new(&p.root().join("Cargo.toml"), &config).unwrap();
154155
let compile_options = CompileOptions::new(&config, CompileMode::Build).unwrap();
155156
let member_bar = ws.members().find(|m| &*m.name() == "bar").unwrap();

tests/testsuite/search.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ fn not_update() {
148148
Shell::from_write(Box::new(Vec::new())),
149149
paths::root(),
150150
paths::home().join(".cargo"),
151-
);
151+
)
152+
.unwrap();
152153
let lock = cfg.acquire_package_cache_lock().unwrap();
153154
let mut regsrc = RegistrySource::remote(sid, &HashSet::new(), &cfg);
154155
regsrc.update().unwrap();

0 commit comments

Comments
 (0)