Skip to content

Commit

Permalink
Add support for platform-defined standard directories
Browse files Browse the repository at this point in the history
This change stops cargo from violating the operating system rules
regarding the placement of config, cache, ... directories on Linux,
macOS and Windows.

Existing directories and overrides are retained.

The precedence is as follows:

1) use the `CARGO_HOME` environment variable if it exists (legacy)
2) use `CARGO_CACHE_DIR`, `CARGO_CONFIG_DIR` etc. env vars if they exist
3) use the ~/.cargo directory if it exists (legacy)
4) follow operating system standards

A new cargo command, `dirs`,  is added, which can provide path
information to other command line tools.

Fixes:
  rust-lang#1734
  rust-lang#1976
  rust-lang/rust#12725

Addresses:
  rust-lang/rfcs#1615
  rust-lang#148,
  rust-lang#3981
  • Loading branch information
soc committed Apr 28, 2018
1 parent f2d51b9 commit d3ffb18
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 74 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ crates-io = { path = "src/crates-io", version = "0.16" }
crossbeam = "0.3"
crypto-hash = "0.3"
curl = "0.4.6"
directories = "0.8.5"
env_logger = "0.5"
failure = "0.1.1"
filetime = "0.1"
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ clone_depth: 1
build: false

test_script:
- set RUST_BACKTRACE=1
- cargo test
2 changes: 1 addition & 1 deletion src/bin/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn is_executable<P: AsRef<Path>>(path: P) -> bool {
}

fn search_directories(config: &Config) -> Vec<PathBuf> {
let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")];
let mut dirs = vec![config.bin_path()];
if let Some(val) = env::var_os("PATH") {
dirs.extend(env::split_paths(&val));
}
Expand Down
16 changes: 16 additions & 0 deletions src/bin/commands/dirs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use command_prelude::*;

pub fn cli() -> App {
subcommand("dirs")
.about("Display directories (cache, config, ...) used by cargo")
.after_help("\
")
}

pub fn exec(config: &mut Config, _args: &ArgMatches) -> CliResult {
println!("CARGO_CACHE_DIR: {:?}", config.cache_path().into_path_unlocked());
println!("CARGO_CONFIG_DIR: {:?}", config.config_path().into_path_unlocked());
println!("CARGO_DATA_DIR: {:?}", config.data_path());
println!("CARGO_BIN_DIR: {:?}", config.bin_path());
Ok(())
}
3 changes: 3 additions & 0 deletions src/bin/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub fn builtin() -> Vec<App> {
build::cli(),
check::cli(),
clean::cli(),
dirs::cli(),
doc::cli(),
fetch::cli(),
generate_lockfile::cli(),
Expand Down Expand Up @@ -40,6 +41,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches) -> CliResu
"build" => build::exec,
"check" => check::exec,
"clean" => clean::exec,
"dirs" => dirs::exec,
"doc" => doc::exec,
"fetch" => fetch::exec,
"generate-lockfile" => generate_lockfile::exec,
Expand Down Expand Up @@ -74,6 +76,7 @@ pub mod bench;
pub mod build;
pub mod check;
pub mod clean;
pub mod dirs;
pub mod doc;
pub mod fetch;
pub mod generate_lockfile;
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ impl<'cfg> Workspace<'cfg> {
// `CARGO_HOME` pointing inside of the workspace root or in the
// current project, but we don't want to mistakenly try to put
// crates.io crates into the workspace by accident.
if self.config.home() == path {
if &self.config.cache_path() == path {
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/cargo/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate core_foundation;
extern crate crates_io as registry;
extern crate crossbeam;
extern crate curl;
extern crate directories;
#[macro_use]
extern crate failure;
extern crate filetime;
Expand Down
64 changes: 46 additions & 18 deletions src/cargo/ops/cargo_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ impl Drop for Transaction {
}
}

#[derive(Clone)]
struct CargoInstallDirs {
config_dir: PathBuf,
bin_dir: PathBuf,
}

impl CargoInstallDirs {
fn from_root(root: PathBuf) -> CargoInstallDirs {
CargoInstallDirs {
bin_dir: root.join("bin"),
config_dir: root,
}
}
fn from_config(config: &Config) -> CargoInstallDirs {
CargoInstallDirs {
bin_dir: config.bin_path(),
config_dir: config.config_path().into_path_unlocked(),
}
}
}

pub fn install(
root: Option<&str>,
krates: Vec<&str>,
Expand All @@ -61,7 +82,7 @@ pub fn install(
opts: &ops::CompileOptions,
force: bool,
) -> CargoResult<()> {
let root = resolve_root(root, opts.config)?;
let root = resolve_install_dirs(root, opts.config)?;
let map = SourceConfigMap::new(opts.config)?;

let (installed_anything, scheduled_error) = if krates.len() <= 1 {
Expand Down Expand Up @@ -122,7 +143,7 @@ pub fn install(
if installed_anything {
// Print a warning that if this directory isn't in PATH that they won't be
// able to run these commands.
let dst = metadata(opts.config, &root)?.parent().join("bin");
let dst = root.bin_dir;
let path = env::var_os("PATH").unwrap_or_default();
for path in env::split_paths(&path) {
if path == dst {
Expand All @@ -145,7 +166,7 @@ pub fn install(
}

fn install_one(
root: &Filesystem,
dirs: &CargoInstallDirs,
map: &SourceConfigMap,
krate: Option<&str>,
source_id: &SourceId,
Expand Down Expand Up @@ -235,9 +256,9 @@ fn install_one(
// We have to check this again afterwards, but may as well avoid building
// anything if we're gonna throw it away anyway.
{
let metadata = metadata(config, root)?;
let metadata = metadata(config, &Filesystem::new(dirs.config_dir.clone()))?;
let list = read_crate_list(&metadata)?;
let dst = metadata.parent().join("bin");
let dst = config.bin_path();
check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
}

Expand Down Expand Up @@ -274,7 +295,7 @@ fn install_one(
);
}

let metadata = metadata(config, root)?;
let metadata = metadata(config, &Filesystem::new(dirs.config_dir.clone()))?;
let mut list = read_crate_list(&metadata)?;
let dst = metadata.parent().join("bin");
let duplicates = check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
Expand Down Expand Up @@ -655,8 +676,8 @@ fn write_crate_list(file: &FileLock, listing: CrateListingV1) -> CargoResult<()>
}

pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
let dst = resolve_root(dst, config)?;
let dst = metadata(config, &dst)?;
let dst = resolve_install_dirs(dst, config)?;
let dst = metadata(config, &Filesystem::new(dst.config_dir))?;
let list = read_crate_list(&dst)?;
for (k, v) in list.v1.iter() {
println!("{}:", k);
Expand All @@ -677,7 +698,7 @@ pub fn uninstall(
bail!("A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.");
}

let root = resolve_root(root, config)?;
let root = resolve_install_dirs(root, config)?;
let scheduled_error = if specs.len() == 1 {
uninstall_one(&root, specs[0], bins, config)?;
false
Expand Down Expand Up @@ -723,13 +744,13 @@ pub fn uninstall(
Ok(())
}

pub fn uninstall_one(
root: &Filesystem,
fn uninstall_one(
dirs: &CargoInstallDirs,
spec: &str,
bins: &[String],
config: &Config,
) -> CargoResult<()> {
let crate_metadata = metadata(config, root)?;
let crate_metadata = metadata(config, &Filesystem::new(dirs.config_dir.clone()))?;
let mut metadata = read_crate_list(&crate_metadata)?;
let mut to_remove = Vec::new();
{
Expand All @@ -738,7 +759,7 @@ pub fn uninstall_one(
Entry::Occupied(e) => e,
Entry::Vacant(..) => panic!("entry not found: {}", result),
};
let dst = crate_metadata.parent().join("bin");
let dst = &dirs.bin_dir;
for bin in installed.get() {
let bin = dst.join(bin);
if fs::metadata(&bin).is_err() {
Expand Down Expand Up @@ -787,15 +808,22 @@ pub fn uninstall_one(
Ok(())
}

/// Return a file lock for the .crates.toml file at the given root.
/// The config argument is only used for logging to the shell.
fn metadata(config: &Config, root: &Filesystem) -> CargoResult<FileLock> {
root.open_rw(Path::new(".crates.toml"), config, "crate metadata")
}

fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesystem> {
// Determine cargo directories by first checking whether an argument was given
// on the command line, if not checking whether the environment variable
// CARGO_INSTALL_ROOT is set, and if not using the paths of the configuration.
fn resolve_install_dirs(
root: Option<&str>,
config: &Config,
) -> CargoResult<CargoInstallDirs> {
let config_root = config.get_path("install.root")?;
Ok(flag.map(PathBuf::from)
Ok(root.map(PathBuf::from)
.or_else(|| env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
.or_else(move || config_root.map(|v| v.val))
.map(Filesystem::new)
.unwrap_or_else(|| config.home().clone()))
.or_else(move || config_root.map(|v| v.val)).map(CargoInstallDirs::from_root)
.unwrap_or_else(|| CargoInstallDirs::from_config(config)))
}
Loading

0 comments on commit d3ffb18

Please sign in to comment.