diff --git a/Cargo.lock b/Cargo.lock index 8341da37b048..160fce6e55d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,60 @@ dependencies = [ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "backtrace" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cargo_metadata" +version = "0.3.2" +source = "git+https://github.com/topecongiro/cargo_metadata#1f5bbc43efdad3dfc1d622174b976982cf1f8bf8" +dependencies = [ + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (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 = "derive-new" version = "0.5.0" @@ -34,6 +88,14 @@ dependencies = [ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "getopts" version = "0.2.15" @@ -103,10 +165,16 @@ name = "regex-syntax" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustc-demangle" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustfmt-nightly" version = "0.2.16" dependencies = [ + "cargo_metadata 0.3.2 (git+https://github.com/topecongiro/cargo_metadata)", "derive-new 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -125,6 +193,20 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "semver" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.21" @@ -252,10 +334,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" +"checksum backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8709cc7ec06f6f0ae6c2c7e12f6ed41540781f72b488d83734978295ceae182e" +"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" +"checksum cargo_metadata 0.3.2 (git+https://github.com/topecongiro/cargo_metadata)" = "" +"checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" "checksum derive-new 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "415f627ab054041c3eb748c2e1da0ef751989f5f0c386b63a098e545854a98ba" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "65922871abd2f101a2eb0eaebadc66668e54a87ad9c3dd82520b5f86ede5eff9" "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -267,6 +356,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" +"checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" +"checksum semver 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bee2bc909ab2d8d60dab26e8cad85b25d795b14603a0dcb627b78b9d30b6454b" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6eda663e865517ee783b0891a3f6eb3a253e0b0dabb46418969ee9635beadd9e" "checksum serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "652bc323d694dc925829725ec6c890156d8e70ae5202919869cb00fe2eff3788" "checksum serde_derive_internals 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32f1926285523b2db55df263d2aa4eb69ddcfa7a7eade6430323637866b513ab" diff --git a/Cargo.toml b/Cargo.toml index 9e9090e1f0ef..64036396dc90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ log = "0.3" env_logger = "0.4" getopts = "0.2" derive-new = "0.5" +cargo_metadata = { git = "https://github.com/topecongiro/cargo_metadata" } [target.'cfg(unix)'.dependencies] libc = "0.2.11" diff --git a/src/bin/cargo-fmt.rs b/src/bin/cargo-fmt.rs index fdeb3cf42868..001ffb649c64 100644 --- a/src/bin/cargo-fmt.rs +++ b/src/bin/cargo-fmt.rs @@ -13,19 +13,21 @@ #![cfg(not(test))] #![deny(warnings)] +extern crate cargo_metadata; extern crate getopts; extern crate serde_json as json; use std::env; +use std::fs; +use std::hash::{Hash, Hasher}; use std::io::{self, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; use std::str; use std::collections::HashSet; use std::iter::FromIterator; use getopts::{Matches, Options}; -use json::Value; fn main() { let exit_status = execute(); @@ -50,13 +52,16 @@ fn execute() -> i32 { opts.optflag("", "all", "format all packages (only usable in workspaces)"); // If there is any invalid argument passed to `cargo fmt`, return without formatting. - if let Some(arg) = env::args() - .skip(2) - .take_while(|a| a != "--") - .find(|a| !a.starts_with('-')) - { - print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg)); - return failure; + let mut is_package_arg = false; + for arg in env::args().skip(2).take_while(|a| a != "--") { + if arg.starts_with("-") { + is_package_arg = arg.starts_with("--package"); + } else if !is_package_arg { + print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg)); + return failure; + } else { + is_package_arg = false; + } } let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) { @@ -82,9 +87,9 @@ fn execute() -> i32 { return success; } - let workspace_hitlist = WorkspaceHitlist::from_matches(&matches); + let strategy = CargoFmtStrategy::from_matches(&matches); - match format_crate(verbosity, &workspace_hitlist) { + match format_crate(verbosity, &strategy) { Err(e) => { print_usage_to_stderr(&opts, &e.to_string()); failure @@ -125,17 +130,16 @@ pub enum Verbosity { fn format_crate( verbosity: Verbosity, - workspace_hitlist: &WorkspaceHitlist, + strategy: &CargoFmtStrategy, ) -> Result { - let targets = get_targets(workspace_hitlist)?; + let targets = get_targets(strategy)?; // Currently only bin and lib files get formatted let files: Vec<_> = targets .into_iter() - .filter(|t| t.kind.should_format()) .inspect(|t| { if verbosity == Verbosity::Verbose { - println!("[{:?}] {:?}", t.kind, t.path) + println!("[{}] {:?}", t.kind, t.path) } }) .map(|t| t.path) @@ -149,223 +153,157 @@ fn get_fmt_args() -> Vec { env::args().skip_while(|a| a != "--").skip(1).collect() } +/// Target uses a `path` field for equality and hashing. #[derive(Debug)] -enum TargetKind { - Lib, // dylib, staticlib, lib - Bin, // bin - Example, // example file - Test, // test file - Bench, // bench file - CustomBuild, // build script - ProcMacro, // a proc macro implementation - Other, // plugin,... +pub struct Target { + /// A path to the main source file of the target. + path: PathBuf, + /// A kind of target (e.g. lib, bin, example, ...). + kind: String, } -impl TargetKind { - fn should_format(&self) -> bool { - match *self { - TargetKind::Lib - | TargetKind::Bin - | TargetKind::Example - | TargetKind::Test - | TargetKind::Bench - | TargetKind::CustomBuild - | TargetKind::ProcMacro => true, - _ => false, +impl Target { + pub fn from_target(target: &cargo_metadata::Target) -> Self { + let path = PathBuf::from(&target.src_path); + let canonicalized = fs::canonicalize(&path).unwrap_or(path); + + Target { + path: canonicalized, + kind: target.kind[0].clone(), } } } -#[derive(Debug)] -pub struct Target { - path: PathBuf, - kind: TargetKind, +impl PartialEq for Target { + fn eq(&self, other: &Target) -> bool { + self.path == other.path + } } -impl Target { - pub fn from_json(json_val: &Value) -> Option { - let jtarget = json_val.as_object()?; - let path = PathBuf::from(jtarget.get("src_path")?.as_str()?); - let kinds = jtarget.get("kind")?.as_array()?; - let kind = match kinds[0].as_str()? { - "bin" => TargetKind::Bin, - "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib, - "test" => TargetKind::Test, - "example" => TargetKind::Example, - "bench" => TargetKind::Bench, - "custom-build" => TargetKind::CustomBuild, - "proc-macro" => TargetKind::ProcMacro, - _ => TargetKind::Other, - }; +impl Eq for Target {} - Some(Target { - path: path, - kind: kind, - }) +impl Hash for Target { + fn hash(&self, state: &mut H) { + self.path.hash(state); } } #[derive(Debug, PartialEq, Eq)] -pub enum WorkspaceHitlist { +pub enum CargoFmtStrategy { + /// Format every packages and dependencies. All, + /// Format pacakges that are specified by the command line argument. Some(Vec), - None, + /// Format the root packages only. + Root, } -impl WorkspaceHitlist { - pub fn get_some(&self) -> Option<&[String]> { - if let WorkspaceHitlist::Some(ref hitlist) = *self { - Some(hitlist) - } else { - None - } - } - - pub fn from_matches(matches: &Matches) -> WorkspaceHitlist { +impl CargoFmtStrategy { + pub fn from_matches(matches: &Matches) -> CargoFmtStrategy { match (matches.opt_present("all"), matches.opt_present("p")) { - (false, false) => WorkspaceHitlist::None, - (true, _) => WorkspaceHitlist::All, - (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")), + (false, false) => CargoFmtStrategy::Root, + (true, _) => CargoFmtStrategy::All, + (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")), } } } -fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option { - json::from_str(str::from_utf8(v).ok()?).ok() -} +/// Based on the specified CargoFmtStrategy, returns a set of main source files. +fn get_targets(strategy: &CargoFmtStrategy) -> Result, io::Error> { + let mut targets = HashSet::new(); -fn get_json_array_with<'a>(v: &'a Value, key: &str) -> Option<&'a Vec> { - v.as_object()?.get(key)?.as_array() -} - -// `cargo metadata --no-deps | jq '.["packages"]'` -fn get_packages(v: &[u8]) -> Result, io::Error> { - let e = io::Error::new( - io::ErrorKind::NotFound, - String::from("`cargo metadata` returned json without a 'packages' key"), - ); - match get_cargo_metadata_from_utf8(v) { - Some(ref json_obj) => get_json_array_with(json_obj, "packages").cloned().ok_or(e), - None => Err(e), + match *strategy { + CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?, + CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?, + CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?, } -} -fn extract_target_from_package(package: &Value) -> Option> { - let jtargets = get_json_array_with(package, "targets")?; - let mut targets: Vec = vec![]; - for jtarget in jtargets { - targets.push(Target::from_json(jtarget)?); - } - Some(targets) -} - -fn filter_packages_with_hitlist( - packages: Vec, - workspace_hitlist: &WorkspaceHitlist, -) -> Result, &String> { - let some_hitlist: Option> = - workspace_hitlist.get_some().map(HashSet::from_iter); - if some_hitlist.is_none() { - return Ok(packages); - } - let mut hitlist = some_hitlist.unwrap(); - let members: Vec = packages - .into_iter() - .filter(|member| { - member - .as_object() - .and_then(|member_obj| { - member_obj - .get("name") - .and_then(Value::as_str) - .map(|member_name| { - hitlist.take(&member_name.to_string()).is_some() - }) - }) - .unwrap_or(false) - }) - .collect(); - if hitlist.is_empty() { - Ok(members) - } else { - Err(hitlist.into_iter().next().unwrap()) - } -} - -fn get_dependencies_from_package(package: &Value) -> Option> { - let jdependencies = get_json_array_with(package, "dependencies")?; - let root_path = env::current_dir().ok()?; - let mut dependencies: Vec = vec![]; - for jdep in jdependencies { - let jdependency = jdep.as_object()?; - if !jdependency.get("source")?.is_null() { - continue; - } - let name = jdependency.get("name")?.as_str()?; - let mut path = root_path.clone(); - path.push(&name); - dependencies.push(path); - } - Some(dependencies) -} - -// Returns a vector of local dependencies under this crate -fn get_path_to_local_dependencies(packages: &[Value]) -> Vec { - let mut local_dependencies: Vec = vec![]; - for package in packages { - if let Some(mut d) = get_dependencies_from_package(package) { - local_dependencies.append(&mut d); - } - } - local_dependencies -} - -// Returns a vector of all compile targets of a crate -fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result, io::Error> { - let output = Command::new("cargo") - .args(&["metadata", "--no-deps", "--format-version=1"]) - .output()?; - if output.status.success() { - let cur_dir = env::current_dir()?; - let mut targets: Vec = vec![]; - let packages = get_packages(&output.stdout)?; - - // If we can find any local dependencies, we will try to get targets from those as well. - if *workspace_hitlist == WorkspaceHitlist::All { - for path in get_path_to_local_dependencies(&packages) { - match env::set_current_dir(path) { - Ok(..) => match get_targets(workspace_hitlist) { - Ok(ref mut t) => targets.append(t), - Err(..) => continue, - }, - Err(..) => continue, - } - } - } - - env::set_current_dir(cur_dir)?; - match filter_packages_with_hitlist(packages, workspace_hitlist) { - Ok(packages) => { - for package in packages { - if let Some(mut target) = extract_target_from_package(&package) { - targets.append(&mut target); - } - } - Ok(targets) - } - Err(package) => { - // Mimick cargo of only outputting one spec. - Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("package `{}` is not a member of the workspace", package), - )) - } - } - } else { + if targets.is_empty() { Err(io::Error::new( - io::ErrorKind::NotFound, - str::from_utf8(&output.stderr).unwrap(), + io::ErrorKind::Other, + format!("Failed to find targets"), )) + } else { + Ok(targets) + } +} + +fn get_targets_root_only(targets: &mut HashSet) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(None)?; + + for package in metadata.packages { + for target in package.targets { + if target.name == package.name { + targets.insert(Target::from_target(&target)); + } + } + } + + Ok(()) +} + +fn get_targets_recursive( + manifest_path: Option<&Path>, + mut targets: &mut HashSet, + visited: &mut HashSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(manifest_path)?; + + for package in metadata.packages { + add_targets(&package.targets, &mut targets); + + // Look for local dependencies. + for dependency in package.dependencies { + if dependency.source.is_some() || visited.contains(&dependency.name) { + continue; + } + + let mut manifest_path = PathBuf::from(&package.manifest_path); + + manifest_path.pop(); + manifest_path.push(&dependency.name); + manifest_path.push("Cargo.toml"); + + if manifest_path.exists() { + visited.insert(dependency.name); + get_targets_recursive(Some(&manifest_path), &mut targets, visited)?; + } + } + } + + Ok(()) +} + +fn get_targets_with_hitlist( + hitlist: &[String], + targets: &mut HashSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(None)?; + + let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist); + + for package in metadata.packages { + for target in package.targets { + if workspace_hitlist.remove(&target.name) { + targets.insert(Target::from_target(&target)); + } + } + } + + if workspace_hitlist.is_empty() { + Ok(()) + } else { + let package = workspace_hitlist.iter().next().unwrap(); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("package `{}` is not a member of the workspace", package), + )) + } +} + +fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet) { + for target in target_paths { + targets.insert(Target::from_target(&target)); } } @@ -379,6 +317,7 @@ fn format_files( } else { std::process::Stdio::inherit() }; + if verbosity == Verbosity::Verbose { print!("rustfmt"); for a in fmt_args { @@ -389,6 +328,7 @@ fn format_files( } println!(); } + let mut command = Command::new("rustfmt") .stdout(stdout) .args(files) @@ -401,5 +341,16 @@ fn format_files( ), _ => e, })?; + command.wait() } + +fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result { + match cargo_metadata::metadata(manifest_path) { + Ok(metadata) => Ok(metadata), + Err(..) => Err(io::Error::new( + io::ErrorKind::Other, + "`cargo manifest` failed.", + )), + } +} diff --git a/src/config.rs b/src/config.rs index 0f333337bf30..acf86f42565c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -364,7 +364,7 @@ macro_rules! create_config { self.$i.2 = val; } else { println!("Warning: can't set some features as unstable \ - features are only available in nightly channel."); + features are only available in nightly channel."); } } }