Implemented support for workspaces
closes rust-lang-nursery/rustfmt#1244
This commit is contained in:
parent
603f26d8f1
commit
6971b4b5c9
1 changed files with 96 additions and 16 deletions
|
@ -21,8 +21,10 @@ use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, ExitStatus};
|
use std::process::{Command, ExitStatus};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use getopts::Options;
|
use getopts::{Options, Matches};
|
||||||
use rustc_serialize::json::Json;
|
use rustc_serialize::json::Json;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -39,6 +41,11 @@ fn execute() -> i32 {
|
||||||
opts.optflag("h", "help", "show this message");
|
opts.optflag("h", "help", "show this message");
|
||||||
opts.optflag("q", "quiet", "no output printed to stdout");
|
opts.optflag("q", "quiet", "no output printed to stdout");
|
||||||
opts.optflag("v", "verbose", "use verbose output");
|
opts.optflag("v", "verbose", "use verbose output");
|
||||||
|
opts.optmulti("p",
|
||||||
|
"package",
|
||||||
|
"specify package to format (only usable in workspaces)",
|
||||||
|
"<package>");
|
||||||
|
opts.optflag("", "all", "format all packages (only usable in workspaces)");
|
||||||
|
|
||||||
let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
|
let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
@ -63,7 +70,9 @@ fn execute() -> i32 {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
match format_crate(verbosity) {
|
let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);
|
||||||
|
|
||||||
|
match format_crate(verbosity, workspace_hitlist) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_usage(&opts, &e.to_string());
|
print_usage(&opts, &e.to_string());
|
||||||
failure
|
failure
|
||||||
|
@ -92,8 +101,10 @@ pub enum Verbosity {
|
||||||
Quiet,
|
Quiet,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_crate(verbosity: Verbosity) -> Result<ExitStatus, std::io::Error> {
|
fn format_crate(verbosity: Verbosity,
|
||||||
let targets = try!(get_targets());
|
workspace_hitlist: WorkspaceHitlist)
|
||||||
|
-> Result<ExitStatus, std::io::Error> {
|
||||||
|
let targets = try!(get_targets(workspace_hitlist));
|
||||||
|
|
||||||
// Currently only bin and lib files get formatted
|
// Currently only bin and lib files get formatted
|
||||||
let files: Vec<_> = targets
|
let files: Vec<_> = targets
|
||||||
|
@ -140,25 +151,94 @@ pub struct Target {
|
||||||
kind: TargetKind,
|
kind: TargetKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum WorkspaceHitlist {
|
||||||
|
All,
|
||||||
|
Some(Vec<String>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkspaceHitlist {
|
||||||
|
pub fn get_some<'a>(&'a self) -> Option<&'a [String]> {
|
||||||
|
if let &WorkspaceHitlist::Some(ref hitlist) = self {
|
||||||
|
Some(&hitlist)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
|
||||||
|
match (matches.opt_present("all"), matches.opt_present("p")) {
|
||||||
|
(false, false) => WorkspaceHitlist::None,
|
||||||
|
(true, _) => WorkspaceHitlist::All,
|
||||||
|
(false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a vector of all compile targets of a crate
|
// Returns a vector of all compile targets of a crate
|
||||||
fn get_targets() -> Result<Vec<Target>, std::io::Error> {
|
fn get_targets(workspace_hitlist: WorkspaceHitlist) -> Result<Vec<Target>, std::io::Error> {
|
||||||
let mut targets: Vec<Target> = vec![];
|
let mut targets: Vec<Target> = vec![];
|
||||||
let output = try!(Command::new("cargo").arg("read-manifest").output());
|
if workspace_hitlist == WorkspaceHitlist::None {
|
||||||
|
let output = try!(Command::new("cargo").arg("read-manifest").output());
|
||||||
|
if output.status.success() {
|
||||||
|
// None of the unwraps should fail if output of `cargo read-manifest` is correct
|
||||||
|
let data = &String::from_utf8(output.stdout).unwrap();
|
||||||
|
let json = Json::from_str(data).unwrap();
|
||||||
|
let jtargets = json.find("targets").unwrap().as_array().unwrap();
|
||||||
|
for jtarget in jtargets {
|
||||||
|
targets.push(target_from_json(jtarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(targets);
|
||||||
|
}
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::NotFound,
|
||||||
|
str::from_utf8(&output.stderr).unwrap()));
|
||||||
|
}
|
||||||
|
// This happens when cargo-fmt is not used inside a crate or
|
||||||
|
// is used inside a workspace.
|
||||||
|
// To ensure backward compatability, we only use `cargo metadata` for workspaces.
|
||||||
|
// TODO: Is it possible only use metadata or read-manifest
|
||||||
|
let output = Command::new("cargo").arg("metadata")
|
||||||
|
.arg("--no-deps")
|
||||||
|
.output()?;
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
// None of the unwraps should fail if output of `cargo read-manifest` is correct
|
|
||||||
let data = &String::from_utf8(output.stdout).unwrap();
|
let data = &String::from_utf8(output.stdout).unwrap();
|
||||||
let json = Json::from_str(data).unwrap();
|
let json = Json::from_str(data).unwrap();
|
||||||
let jtargets = json.find("targets").unwrap().as_array().unwrap();
|
let mut hitlist: HashSet<&String> = if workspace_hitlist != WorkspaceHitlist::All {
|
||||||
for jtarget in jtargets {
|
HashSet::from_iter(workspace_hitlist.get_some().unwrap())
|
||||||
targets.push(target_from_json(jtarget));
|
} else {
|
||||||
|
HashSet::new() // Unused
|
||||||
|
};
|
||||||
|
let members: Vec<&Json> = json.find("packages")
|
||||||
|
.unwrap()
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|member| if workspace_hitlist == WorkspaceHitlist::All {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let member_name = member.find("name").unwrap().as_string().unwrap();
|
||||||
|
hitlist.take(&member_name.to_string()).is_some()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if hitlist.len() != 0 {
|
||||||
|
// Mimick cargo of only outputting one <package> spec.
|
||||||
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput,
|
||||||
|
format!("package `{}` is not a member of the workspace",
|
||||||
|
hitlist.iter().next().unwrap())));
|
||||||
}
|
}
|
||||||
|
for member in members {
|
||||||
Ok(targets)
|
let jtargets = member.find("targets").unwrap().as_array().unwrap();
|
||||||
} else {
|
for jtarget in jtargets {
|
||||||
// This happens when cargo-fmt is not used inside a crate
|
targets.push(target_from_json(jtarget));
|
||||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound,
|
}
|
||||||
str::from_utf8(&output.stderr).unwrap()))
|
}
|
||||||
|
return Ok(targets);
|
||||||
}
|
}
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::NotFound,
|
||||||
|
str::from_utf8(&output.stderr).unwrap()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn target_from_json(jtarget: &Json) -> Target {
|
fn target_from_json(jtarget: &Json) -> Target {
|
||||||
|
|
Loading…
Reference in a new issue