diff --git a/Cargo.lock b/Cargo.lock index a940b421c58..6cf0c2abfea 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.2.0 (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,18 @@ dependencies = [ "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dirs" +version = "0.2.0" +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)", + "uuid-sys 0.1.2 (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-pre5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "docopt" version = "0.6.78" @@ -307,6 +320,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 +392,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" @@ -448,6 +479,15 @@ dependencies = [ "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uuid-sys" +version = "0.1.2" +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 = "winapi" version = "0.2.5" @@ -467,3 +507,8 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.0.0-pre5" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/Cargo.toml b/Cargo.toml index ed3e7cb8e97..0d57b4bf950 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.2" 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..ca1df3cf7ab 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(()) } } @@ -262,8 +276,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 +293,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 +303,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 +339,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 +348,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..4452d5f04ef 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 bin_path(&self) -> PathBuf { + self.paths.bin.clone() } - pub fn home(&self) -> &Path { &self.home_path } + 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,74 @@ 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) -> Option { + 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 Some(Paths { + bin: v.clone(), + cache: v.clone(), + config: v, + }); + } + + let user_home = env::home_dir(); + + let dirs = dirs::Directories::with_prefix("cargo", "Cargo").ok(); + + let legacy = user_home.map(|h| h.join(".cargo")); + + let platform_bin = dirs.as_ref().map(|d| d.bin_home()); + let platform_cache = dirs.as_ref().map(|d| d.cache_home()); + let platform_config = dirs.as_ref().map(|d| d.config_home()); + + let mut bin: Option; + let mut cache: Option; + let mut config: Option; + + // 2) + bin = platform_bin.clone().map(|p| path_exists(p)).unwrap_or(None); + cache = platform_cache.clone().map(|p| path_exists(p)).unwrap_or(None); + config = platform_config.clone().map(|p| path_exists(p)).unwrap_or(None); + + // 3) + if let Some(l) = legacy.map(|l| path_exists(l)).unwrap_or(None) { + cache = cache.or_else(|| Some(l.clone())); + bin = bin.or_else(|| Some(l.clone())); + config = config.or_else(|| Some(l)); + } + + // 4) + bin = bin.or(platform_bin); + cache = cache.or(platform_cache); + config = config.or(platform_config); + + match (bin, cache, config) { + (Some(bin), Some(cache), Some(config)) => Some(Paths { + bin: bin, + cache: cache, + config: config, + }), + _ => None + } } -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 +559,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 +577,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()));