diff --git a/Cargo.lock b/Cargo.lock index a940b421c58..08c17053c58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ dependencies = [ "crates-io 0.1.0", "crossbeam 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "curl 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.78 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "filetime 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -104,6 +105,17 @@ dependencies = [ "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dirs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.0.0-pre6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "docopt" version = "0.6.78" @@ -307,6 +319,15 @@ dependencies = [ "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ole32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "openssl-sys" version = "0.7.5" @@ -370,6 +391,15 @@ dependencies = [ "nom 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shell32-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.3.0" @@ -467,3 +497,8 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.0.0-pre6" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/Cargo.toml b/Cargo.toml index ed3e7cb8e97..a20b215f66c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ advapi32-sys = "0.1" crates-io = { path = "src/crates-io", version = "0.1" } crossbeam = "0.2" curl = "0.2" +dirs = "0.3.1" docopt = "0.6" env_logger = "0.3" filetime = "0.1" diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index ab2765da558..e6db2f40fcd 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -242,7 +242,7 @@ fn is_executable(metadata: &fs::Metadata) -> bool { } fn search_directories(config: &Config) -> Vec { - let mut dirs = vec![config.home().join("bin")]; + let mut dirs = vec![config.bin_path()]; if let Some(val) = env::var_os("PATH") { dirs.extend(env::split_paths(&val)); } diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 8a4b4338d87..7c761061596 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -6,6 +6,7 @@ extern crate crates_io as registry; extern crate crossbeam; extern crate curl; +extern crate dirs; extern crate docopt; extern crate filetime; extern crate flate2; diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 0554fcd7ba4..1fc4827826a 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -15,6 +15,20 @@ use ops::{self, CompileFilter}; use sources::{GitSource, PathSource, RegistrySource}; use util::{CargoResult, ChainError, Config, human, internal}; +struct Paths { + bin: PathBuf, + config: PathBuf, +} + +impl Paths { + fn from_root(root: PathBuf) -> Paths { + Paths { + bin: root.join("bin"), + config: root, + } + } +} + #[derive(RustcDecodable, RustcEncodable)] enum CrateListing { V1(CrateListingV1), @@ -43,7 +57,7 @@ pub fn install(root: Option<&str>, vers: Option<&str>, opts: &ops::CompileOptions) -> CargoResult<()> { let config = opts.config; - let root = try!(resolve_root(root, config)); + let paths = try!(resolve_paths(root, config)); let (pkg, source) = if source_id.is_git() { try!(select_pkg(GitSource::new(source_id, config), source_id, krate, vers, &mut |git| git.read_packages())) @@ -61,9 +75,9 @@ pub fn install(root: Option<&str>, specify alternate source")))) }; - let mut list = try!(read_crate_list(&root)); - let dst = root.join("bin"); - try!(check_overwrites(&dst, &pkg, &opts.filter, &list)); + let mut list = try!(read_crate_list(&paths.config)); + let dst = &paths.bin; + try!(check_overwrites(dst, &pkg, &opts.filter, &list)); let target_dir = config.cwd().join("target-install"); config.set_target_dir(&target_dir); @@ -73,7 +87,7 @@ pub fn install(root: Option<&str>, })); let mut t = Transaction { bins: Vec::new() }; - try!(fs::create_dir_all(&dst)); + try!(fs::create_dir_all(dst)); for bin in compile.binaries.iter() { let dst = dst.join(bin.file_name().unwrap()); try!(config.shell().status("Installing", dst.display())); @@ -90,7 +104,7 @@ pub fn install(root: Option<&str>, }).extend(t.bins.iter().map(|t| { t.file_name().unwrap().to_string_lossy().into_owned() })); - try!(write_crate_list(&root, list)); + try!(write_crate_list(&paths.config, list)); t.bins.truncate(0); @@ -98,7 +112,7 @@ pub fn install(root: Option<&str>, // able to run these commands. let path = env::var_os("PATH").unwrap_or(OsString::new()); for path in env::split_paths(&path) { - if path == dst { + if &path == dst { return Ok(()) } } @@ -249,6 +263,7 @@ fn read_crate_list(path: &Path) -> CargoResult { } fn write_crate_list(path: &Path, listing: CrateListingV1) -> CargoResult<()> { + try!(fs::create_dir_all(path)); let metadata = path.join(".crates.toml"); (|| -> CargoResult<_> { let mut f = try!(File::create(&metadata)); @@ -262,8 +277,8 @@ fn write_crate_list(path: &Path, listing: CrateListingV1) -> CargoResult<()> { } pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> { - let dst = try!(resolve_root(dst, config)); - let list = try!(read_crate_list(&dst)); + let paths = try!(resolve_paths(dst, config)); + let list = try!(read_crate_list(&paths.config)); let mut shell = config.shell(); let out = shell.out(); for (k, v) in list.v1.iter() { @@ -279,8 +294,8 @@ pub fn uninstall(root: Option<&str>, spec: &str, bins: &[String], config: &Config) -> CargoResult<()> { - let root = try!(resolve_root(root, config)); - let mut metadata = try!(read_crate_list(&root)); + let paths = try!(resolve_paths(root, config)); + let mut metadata = try!(read_crate_list(&paths.config)); let mut to_remove = Vec::new(); { let result = try!(PackageIdSpec::query_str(spec, metadata.v1.keys())) @@ -289,7 +304,7 @@ pub fn uninstall(root: Option<&str>, Entry::Occupied(e) => e, Entry::Vacant(..) => panic!("entry not found: {}", result), }; - let dst = root.join("bin"); + let dst = &paths.bin; for bin in installed.get() { let bin = dst.join(bin); if fs::metadata(&bin).is_err() { @@ -325,7 +340,7 @@ pub fn uninstall(root: Option<&str>, installed.remove(); } } - try!(write_crate_list(&root, metadata)); + try!(write_crate_list(&paths.config, metadata)); for bin in to_remove { try!(config.shell().status("Removing", bin.display())); try!(fs::remove_file(bin)); @@ -334,13 +349,16 @@ pub fn uninstall(root: Option<&str>, Ok(()) } -fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult { +fn resolve_paths(flag: Option<&str>, config: &Config) -> CargoResult { let config_root = try!(config.get_string("install.root")); Ok(flag.map(PathBuf::from).or_else(|| { env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from) }).or_else(|| { config_root.clone().map(|(v, _)| PathBuf::from(v)) - }).unwrap_or_else(|| { - config.home().to_owned() + }).map(|r| Paths::from_root(r)).unwrap_or_else(|| { + Paths { + bin: config.bin_path(), + config: config.config_path(), + } })) } diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 88a42383be0..eb161a42de0 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -19,8 +19,14 @@ use util::toml as cargo_toml; use self::ConfigValue as CV; +struct Paths { + pub bin: PathBuf, + pub cache: PathBuf, + pub config: PathBuf, +} + pub struct Config { - home_path: PathBuf, + paths: Paths, shell: RefCell, rustc_info: Rustc, values: RefCell>, @@ -32,11 +38,9 @@ pub struct Config { } impl Config { - pub fn new(shell: MultiShell, - cwd: PathBuf, - homedir: PathBuf) -> CargoResult { + fn new(shell: MultiShell, cwd: PathBuf, paths: Paths) -> CargoResult { let mut cfg = Config { - home_path: homedir, + paths: paths, shell: RefCell::new(shell), rustc_info: Rustc::blank(), cwd: cwd, @@ -59,33 +63,39 @@ impl Config { let cwd = try!(env::current_dir().chain_error(|| { human("couldn't get the current directory of the process") })); - let homedir = try!(homedir(&cwd).chain_error(|| { + let paths = try!(determine_paths(&cwd).chain_error(|| { human("Cargo couldn't find your home directory. \ This probably means that $HOME was not set.") })); - Config::new(shell, cwd, homedir) + Config::new(shell, cwd, paths) } - pub fn home(&self) -> &Path { &self.home_path } + pub fn bin_path(&self) -> PathBuf { + self.paths.bin.clone() + } + + pub fn config_path(&self) -> PathBuf { + self.paths.config.clone() + } pub fn git_db_path(&self) -> PathBuf { - self.home_path.join("git").join("db") + self.paths.cache.join("git").join("db") } pub fn git_checkout_path(&self) -> PathBuf { - self.home_path.join("git").join("checkouts") + self.paths.cache.join("git").join("checkouts") } pub fn registry_index_path(&self) -> PathBuf { - self.home_path.join("registry").join("index") + self.paths.cache.join("registry").join("index") } pub fn registry_cache_path(&self) -> PathBuf { - self.home_path.join("registry").join("cache") + self.paths.cache.join("registry").join("cache") } pub fn registry_source_path(&self) -> PathBuf { - self.home_path.join("registry").join("src") + self.paths.cache.join("registry").join("src") } pub fn shell(&self) -> RefMut { @@ -207,7 +217,7 @@ impl Config { fn load_values(&self) -> CargoResult<()> { let mut cfg = CV::Table(HashMap::new(), PathBuf::from(".")); - try!(walk_tree(&self.cwd, |mut file, path| { + try!(walk_tree(self, |mut file, path| { let mut contents = String::new(); try!(file.read_to_string(&mut contents)); let table = try!(cargo_toml::parse(&contents, &path).chain_error(|| { @@ -464,18 +474,73 @@ impl ConfigValue { } } -fn homedir(cwd: &Path) -> Option { - let cargo_home = env::var_os("CARGO_HOME").map(|home| { - cwd.join(home) - }); - let user_home = env::home_dir().map(|p| p.join(".cargo")); - cargo_home.or(user_home) +fn determine_paths(cwd: &Path) -> CargoResult { + use dirs; + fn path_exists(path: PathBuf) -> Option { + fs::metadata(&path).ok().map(|_| path) + } + + // Strategy to determine where to put files: + // + // 1) Use the environment variable CARGO_HOME if it exists. + // 2) Use the platform-specific location if it exists. + // 3) Use the legacy location (~/.cargo) if it exists. + // 4) Fall back to platform-specific location if all of the above things + // fail. + + // 1) + if let Some(v) = env::var_os("CARGO_HOME").map(|p| cwd.join(p)) { + return Ok(Paths { + bin: v.join("bin"), + cache: v.clone(), + config: v, + }); + } + + let dirs = try!(dirs::Directories::with_prefix("cargo", "Cargo").chain_error(|| { + human("Cargo couldn't find its configuration directory. \ + This probably means that $HOME was not set.") + })); + + let user_home = env::home_dir(); + let legacy = user_home.map(|h| h.join(".cargo")); + + let platform_bin = dirs.bin_home(); + let platform_cache = dirs.cache_home(); + let platform_config = dirs.config_home(); + + let mut bin: Option; + let mut cache: Option; + let mut config: Option; + + // 2) + bin = path_exists(platform_bin.clone()); + cache = path_exists(platform_cache.clone()); + config = path_exists(platform_config.clone()); + + // 3) + if let Some(l) = legacy.map(|l| path_exists(l)).unwrap_or(None) { + bin = bin.or_else(|| Some(l.join("bin"))); + cache = cache.or_else(|| Some(l.clone())); + config = config.or_else(|| Some(l)); + } + + // 4) + let bin = bin.unwrap_or(platform_bin); + let cache = cache.unwrap_or(platform_cache); + let config = config.unwrap_or(platform_config); + + Ok(Paths { + bin: bin, + cache: cache, + config: config, + }) } -fn walk_tree(pwd: &Path, mut walk: F) -> CargoResult<()> +fn walk_tree(config: &Config, mut walk: F) -> CargoResult<()> where F: FnMut(File, &Path) -> CargoResult<()> { - let mut current = pwd; + let mut current: &Path = &config.cwd; loop { let possible = current.join(".cargo").join("config"); @@ -493,18 +558,13 @@ fn walk_tree(pwd: &Path, mut walk: F) -> CargoResult<()> // 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 home = try!(homedir(pwd).chain_error(|| { - human("Cargo couldn't find your home directory. \ - This probably means that $HOME was not set.") - })); - if !pwd.starts_with(&home) { - let config = home.join("config"); + if !config.cwd.starts_with(&config.paths.config) { + let config = config.paths.config.join("config"); if fs::metadata(&config).is_ok() { let file = try!(File::open(&config)); try!(walk(file, &config)); } } - Ok(()) } @@ -516,7 +576,7 @@ pub fn set_config(cfg: &Config, loc: Location, key: &str, // 2. This blows away all comments in a file // 3. This blows away the previous ordering of a file. let file = match loc { - Location::Global => cfg.home_path.join("config"), + Location::Global => cfg.paths.config.join("config"), Location::Project => unimplemented!(), }; try!(fs::create_dir_all(file.parent().unwrap())); diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 94499a9bafa..b67490125c6 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -6,6 +6,7 @@ use std::process::{Output, ExitStatus}; use std::str; use curl; +use dirs; use git2; use rustc_serialize::json; use semver; @@ -309,6 +310,7 @@ from_error! { toml::DecodeError, ffi::NulError, term::Error, + dirs::Error, } impl From> for Box { @@ -329,6 +331,7 @@ impl CargoError for toml::DecodeError {} impl CargoError for url::ParseError {} impl CargoError for ffi::NulError {} impl CargoError for term::Error {} +impl CargoError for dirs::Error {} // ============================================================================= // Construction helpers diff --git a/tests/tests.rs b/tests/tests.rs index 8eb5f5aa753..c960fb4cc27 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -87,9 +87,10 @@ fn can_panic() -> bool { fn process>(t: T) -> cargo::util::ProcessBuilder { let mut p = cargo::util::process(t.as_ref()); + let home = support::paths::home(); p.cwd(&support::paths::root()) - .env("HOME", &support::paths::home()) - .env_remove("CARGO_HOME") + .env("HOME", &home) + .env("CARGO_HOME", &home.join(".cargo")) .env_remove("CARGO_TARGET_DIR") // we assume 'target' .env_remove("MSYSTEM"); // assume cmd.exe everywhere on windows return p