diff --git a/src/bin/cargo-fmt.rs b/src/bin/cargo-fmt.rs index 6e18588da81..5fe8c9cdf52 100644 --- a/src/bin/cargo-fmt.rs +++ b/src/bin/cargo-fmt.rs @@ -18,16 +18,16 @@ 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(); @@ -251,142 +251,101 @@ impl WorkspaceHitlist { } } -fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option { - json::from_str(str::from_utf8(v).ok()?).ok() -} +fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> 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 *workspace_hitlist { + WorkspaceHitlist::None => get_targets_root_only(&mut targets)?, + WorkspaceHitlist::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?, + WorkspaceHitlist::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( + target_names: &[String], + targets: &mut HashSet, +) -> Result<(), io::Error> { + let metadata = get_cargo_metadata(None)?; + + let mut hitlist: HashSet<&String> = HashSet::from_iter(target_names); + + for package in metadata.packages { + for target in package.targets { + if hitlist.remove(&target.name) { + targets.insert(Target::from_target(&target)); + } + } + } + + if hitlist.is_empty() { + Ok(()) + } else { + let package = 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)); } } @@ -424,3 +383,13 @@ fn format_files( })?; 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.", + )), + } +}