]> git.lizzy.rs Git - rust.git/blob - src/bin/cargo-fmt.rs
cargo-fmt: don't return zero on failure
[rust.git] / src / bin / cargo-fmt.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)
12
13 #![cfg(not(test))]
14 #![cfg(feature="cargo-fmt")]
15 #![deny(warnings)]
16
17 extern crate getopts;
18 extern crate rustc_serialize;
19
20 use std::env;
21 use std::io::Write;
22 use std::path::PathBuf;
23 use std::process::{Command, ExitStatus};
24 use std::str;
25
26 use getopts::Options;
27 use rustc_serialize::json::Json;
28
29 fn main() {
30     let exit_status = execute();
31     std::io::stdout().flush().unwrap();
32     std::process::exit(exit_status);
33 }
34
35 fn execute() -> i32 {
36     let success = 0;
37     let failure = 1;
38
39     let mut opts = getopts::Options::new();
40     opts.optflag("h", "help", "show this message");
41     opts.optflag("q", "quiet", "no output printed to stdout");
42     opts.optflag("v", "verbose", "use verbose output");
43
44     let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
45         Ok(m) => m,
46         Err(e) => {
47             print_usage(&opts, &e.to_string());
48             return failure;
49         }
50     };
51
52     let (verbose, quiet) = (matches.opt_present("v"), matches.opt_present("q"));
53
54     if verbose && quiet {
55         print_usage(&opts, "quiet mode and verbose mode are not compatible");
56         return failure;
57     }
58
59     if matches.opt_present("h") {
60         print_usage(&opts, "");
61         return success;
62     }
63
64     match format_crate(verbose, quiet) {
65         Err(e) => {
66             print_usage(&opts, &e.to_string());
67             failure
68         }
69         Ok(status) => {
70             if status.success() {
71                 success
72             } else {
73                 status.code().unwrap_or(failure)
74             }
75         }
76     }
77 }
78
79 fn print_usage(opts: &Options, reason: &str) {
80     let msg = format!("{}\nusage: cargo fmt [options]", reason);
81     println!("{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
82               Arguments after `--` are passed to rustfmt.",
83              opts.usage(&msg));
84 }
85
86 fn format_crate(verbose: bool, quiet: bool) -> Result<ExitStatus, std::io::Error> {
87     let targets = try!(get_targets());
88
89     // Currently only bin and lib files get formatted
90     let files: Vec<_> = targets.into_iter()
91                                .filter(|t| t.kind.is_lib() | t.kind.is_bin())
92                                .inspect(|t| {
93                                    if verbose {
94                                        println!("[{:?}] {:?}", t.kind, t.path)
95                                    }
96                                })
97                                .map(|t| t.path)
98                                .collect();
99
100     format_files(&files, &get_fmt_args(), verbose, quiet)
101 }
102
103 fn get_fmt_args() -> Vec<String> {
104     // All arguments after -- are passed to rustfmt
105     env::args().skip_while(|a| a != "--").skip(1).collect()
106 }
107
108 #[derive(Debug)]
109 enum TargetKind {
110     Lib, // dylib, staticlib, lib
111     Bin, // bin
112     Other, // test, plugin,...
113 }
114
115 impl TargetKind {
116     fn is_lib(&self) -> bool {
117         match self {
118             &TargetKind::Lib => true,
119             _ => false,
120         }
121     }
122
123     fn is_bin(&self) -> bool {
124         match self {
125             &TargetKind::Bin => true,
126             _ => false,
127         }
128     }
129 }
130
131 #[derive(Debug)]
132 pub struct Target {
133     path: PathBuf,
134     kind: TargetKind,
135 }
136
137 // Returns a vector of all compile targets of a crate
138 fn get_targets() -> Result<Vec<Target>, std::io::Error> {
139     let mut targets: Vec<Target> = vec![];
140     let output = try!(Command::new("cargo").arg("read-manifest").output());
141     if output.status.success() {
142         // None of the unwraps should fail if output of `cargo read-manifest` is correct
143         let data = &String::from_utf8(output.stdout).unwrap();
144         let json = Json::from_str(data).unwrap();
145         let jtargets = json.find("targets").unwrap().as_array().unwrap();
146         for jtarget in jtargets {
147             targets.push(target_from_json(jtarget));
148         }
149
150         Ok(targets)
151     } else {
152         // This happens when cargo-fmt is not used inside a crate
153         Err(std::io::Error::new(std::io::ErrorKind::NotFound,
154                                 str::from_utf8(&output.stderr).unwrap()))
155     }
156 }
157
158 fn target_from_json(jtarget: &Json) -> Target {
159     let jtarget = jtarget.as_object().unwrap();
160     let path = PathBuf::from(jtarget.get("src_path").unwrap().as_string().unwrap());
161     let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
162     let kind = match kinds[0].as_string().unwrap() {
163         "bin" => TargetKind::Bin,
164         "lib" | "dylib" | "staticlib" => TargetKind::Lib,
165         _ => TargetKind::Other,
166     };
167
168     Target {
169         path: path,
170         kind: kind,
171     }
172 }
173
174 fn format_files(files: &Vec<PathBuf>,
175                 fmt_args: &Vec<String>,
176                 verbose: bool,
177                 quiet: bool)
178                 -> Result<ExitStatus, std::io::Error> {
179     let stdout = if quiet {
180         std::process::Stdio::null()
181     } else {
182         std::process::Stdio::inherit()
183     };
184     if verbose {
185         print!("rustfmt");
186         for a in fmt_args.iter() {
187             print!(" {}", a);
188         }
189         for f in files.iter() {
190             print!(" {}", f.display());
191         }
192         println!("");
193     }
194     let mut command = try!(Command::new("rustfmt")
195                                .stdout(stdout)
196                                .args(files)
197                                .args(fmt_args)
198                                .spawn());
199     command.wait()
200 }