1
Fork 0

Finish the improvements I planned.

- No more manual args manipulation -- getopts used for everything.
  As a result, options can be in any position, now, even before the
  subcommand.
- The additional options for test, bench, and dist now appear in the
  help output.
- No more single-letter variable bindings used internally for large
  scopes.
- Don't output the time measurement when just invoking 'x.py'
- Logic is now much more linear.  We build strings up, and then print
  them.
This commit is contained in:
Nathan Stocks 2017-04-01 15:48:03 -06:00
parent 5ba579e7f4
commit aa4bd0ec0e
3 changed files with 113 additions and 125 deletions

View file

@ -591,9 +591,10 @@ def bootstrap():
def main(): def main():
start_time = time() start_time = time()
help_triggered = ('-h' in sys.argv) or ('--help' in sys.argv) or (len(sys.argv) == 1)
try: try:
bootstrap() bootstrap()
if ('-h' not in sys.argv) and ('--help' not in sys.argv): if not help_triggered:
print("Build completed successfully in %s" % format_build_time(time() - start_time)) print("Build completed successfully in %s" % format_build_time(time() - start_time))
except (SystemExit, KeyboardInterrupt) as e: except (SystemExit, KeyboardInterrupt) as e:
if hasattr(e, 'code') and isinstance(e.code, int): if hasattr(e, 'code') and isinstance(e.code, int):
@ -601,7 +602,7 @@ def main():
else: else:
exit_code = 1 exit_code = 1
print(e) print(e)
if ('-h' not in sys.argv) and ('--help' not in sys.argv): if not help_triggered:
print("Build completed unsuccessfully in %s" % format_build_time(time() - start_time)) print("Build completed unsuccessfully in %s" % format_build_time(time() - start_time))
sys.exit(exit_code) sys.exit(exit_code)

View file

@ -18,7 +18,7 @@ use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
use getopts::{Matches, Options}; use getopts::{Options};
use Build; use Build;
use config::Config; use config::Config;
@ -75,7 +75,22 @@ pub enum Subcommand {
impl Flags { impl Flags {
pub fn parse(args: &[String]) -> Flags { pub fn parse(args: &[String]) -> Flags {
let mut extra_help = String::new();
let mut subcommand_help = format!("\
Usage: x.py <subcommand> [options] [<paths>...]
Subcommands:
build Compile either the compiler or libraries
test Build and run some test suites
bench Build and run some benchmarks
doc Build documentation
clean Clean out build directories
dist Build and/or install distribution artifacts
To learn more about a subcommand, run `./x.py <subcommand> -h`");
let mut opts = Options::new(); let mut opts = Options::new();
// Options common to all subcommands
opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)"); opts.optflagmulti("v", "verbose", "use verbose output (-vv for very verbose)");
opts.optflag("i", "incremental", "use incremental compilation"); opts.optflag("i", "incremental", "use incremental compilation");
opts.optopt("", "config", "TOML configuration file for build", "FILE"); opts.optopt("", "config", "TOML configuration file for build", "FILE");
@ -89,26 +104,38 @@ impl Flags {
opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS"); opts.optopt("j", "jobs", "number of jobs to run in parallel", "JOBS");
opts.optflag("h", "help", "print this help message"); opts.optflag("h", "help", "print this help message");
let usage = |exit_code, opts: &Options| -> ! { // fn usage()
let subcommand_help = format!("\ let usage = |exit_code: i32, opts: &Options, subcommand_help: &str, extra_help: &str| -> ! {
Usage: x.py <subcommand> [options] [<paths>...] println!("{}", opts.usage(subcommand_help));
if !extra_help.is_empty() {
println!("{}", extra_help);
}
process::exit(exit_code);
};
Subcommands: // Get subcommand
build Compile either the compiler or libraries let matches = opts.parse(&args[..]).unwrap_or_else(|e| {
test Build and run some test suites // Invalid argument/option format
bench Build and run some benchmarks println!("\n{}\n", e);
doc Build documentation usage(1, &opts, &subcommand_help, &extra_help);
clean Clean out build directories });
dist Build and/or install distribution artifacts let subcommand = match matches.free.get(0) {
Some(s) => { s },
None => {
// No subcommand -- lets only show the general usage and subcommand help in this case.
println!("{}\n", subcommand_help);
process::exit(0);
}
};
To learn more about a subcommand, run `./x.py <subcommand> -h`"); // Get any optional paths which occur after the subcommand
let cwd = t!(env::current_dir());
let paths = matches.free[1..].iter().map(|p| cwd.join(p)).collect::<Vec<_>>();
println!("{}", opts.usage(&subcommand_help)); // Some subcommands have specific arguments help text
match subcommand.as_str() {
let subcommand = args.get(0).map(|s| &**s); "build" => {
match subcommand { subcommand_help.push_str("\n
Some("build") => {
println!("\
Arguments: Arguments:
This subcommand accepts a number of paths to directories to the crates This subcommand accepts a number of paths to directories to the crates
and/or artifacts to compile. For example: and/or artifacts to compile. For example:
@ -125,12 +152,11 @@ Arguments:
For a quick build with a usable compile, you can pass: For a quick build with a usable compile, you can pass:
./x.py build --stage 1 src/libtest ./x.py build --stage 1 src/libtest");
"); }
} "test" => {
opts.optmulti("", "test-args", "extra arguments", "ARGS");
Some("test") => { subcommand_help.push_str("\n
println!("\
Arguments: Arguments:
This subcommand accepts a number of paths to directories to tests that This subcommand accepts a number of paths to directories to tests that
should be compiled and run. For example: should be compiled and run. For example:
@ -143,12 +169,13 @@ Arguments:
compiled and tested. compiled and tested.
./x.py test ./x.py test
./x.py test --stage 1 ./x.py test --stage 1");
"); }
} "bench" => {
opts.optmulti("", "test-args", "extra arguments", "ARGS");
Some("doc") => { }
println!("\ "doc" => {
subcommand_help.push_str("\n
Arguments: Arguments:
This subcommand accepts a number of paths to directories of documentation This subcommand accepts a number of paths to directories of documentation
to build. For example: to build. For example:
@ -160,111 +187,74 @@ Arguments:
If no arguments are passed then everything is documented: If no arguments are passed then everything is documented:
./x.py doc ./x.py doc
./x.py doc --stage 1 ./x.py doc --stage 1");
");
}
_ => {}
} }
"dist" => {
opts.optflag("", "install", "run installer as well");
if let Some(subcommand) = subcommand {
if subcommand == "build" ||
subcommand == "test" ||
subcommand == "bench" ||
subcommand == "doc" ||
subcommand == "clean" ||
subcommand == "dist" {
if args.iter().any(|a| a == "-v") {
let flags = Flags::parse(&["build".to_string()]);
let mut config = Config::default();
config.build = flags.build.clone();
let mut build = Build::new(flags, config);
metadata::build(&mut build);
step::build_rules(&build).print_help(subcommand);
} else {
println!("Run `./x.py {} -h -v` to see a list of available paths.",
subcommand);
}
println!("");
}
} }
_ => { }
process::exit(exit_code);
}; };
if args.len() == 0 {
println!("a subcommand must be passed"); // All subcommands can have an optional "Available paths" section
usage(1, &opts); if matches.opt_present("verbose") {
let flags = Flags::parse(&["build".to_string()]);
let mut config = Config::default();
config.build = flags.build.clone();
let mut build = Build::new(flags, config);
metadata::build(&mut build);
let maybe_rules_help = step::build_rules(&build).get_help(subcommand);
if maybe_rules_help.is_some() {
extra_help.push_str(maybe_rules_help.unwrap().as_str());
}
} else {
extra_help.push_str(format!("Run `./x.py {} -h -v` to see a list of available paths.",
subcommand).as_str());
} }
let parse = |opts: &Options| {
let m = opts.parse(&args[1..]).unwrap_or_else(|e| {
println!("failed to parse options: {}", e);
usage(1, opts);
});
if m.opt_present("h") {
usage(0, opts);
}
return m
};
let cwd = t!(env::current_dir()); // User passed in -h/--help?
let remaining_as_path = |m: &Matches| { if matches.opt_present("help") {
m.free.iter().map(|p| cwd.join(p)).collect::<Vec<_>>() usage(0, &opts, &subcommand_help, &extra_help);
}; }
// TODO: Parse subcommand nicely up at top, so options can occur before the subcommand.
// TODO: Get the subcommand-specific options below into the help output
let m: Matches; let cmd = match subcommand.as_str() {
let cmd = match &args[0][..] {
"build" => { "build" => {
m = parse(&opts); Subcommand::Build { paths: paths }
Subcommand::Build { paths: remaining_as_path(&m) }
} }
"test" => { "test" => {
opts.optmulti("", "test-args", "extra arguments", "ARGS");
m = parse(&opts);
Subcommand::Test { Subcommand::Test {
paths: remaining_as_path(&m), paths: paths,
test_args: m.opt_strs("test-args"), test_args: matches.opt_strs("test-args"),
} }
} }
"bench" => { "bench" => {
opts.optmulti("", "test-args", "extra arguments", "ARGS");
m = parse(&opts);
Subcommand::Bench { Subcommand::Bench {
paths: remaining_as_path(&m), paths: paths,
test_args: m.opt_strs("test-args"), test_args: matches.opt_strs("test-args"),
} }
} }
"doc" => { "doc" => {
m = parse(&opts); Subcommand::Doc { paths: paths }
Subcommand::Doc { paths: remaining_as_path(&m) }
} }
"clean" => { "clean" => {
m = parse(&opts); if matches.free.len() > 0 {
if m.free.len() > 0 { println!("\nclean takes no arguments\n");
println!("clean takes no arguments"); usage(1, &opts, &subcommand_help, &extra_help);
usage(1, &opts);
} }
Subcommand::Clean Subcommand::Clean
} }
"dist" => { "dist" => {
opts.optflag("", "install", "run installer as well");
m = parse(&opts);
Subcommand::Dist { Subcommand::Dist {
paths: remaining_as_path(&m), paths: paths,
install: m.opt_present("install"), install: matches.opt_present("install"),
} }
} }
"--help" => usage(0, &opts),
_ => { _ => {
usage(1, &opts); usage(1, &opts, &subcommand_help, &extra_help);
} }
}; };
let cfg_file = m.opt_str("config").map(PathBuf::from).or_else(|| { let cfg_file = matches.opt_str("config").map(PathBuf::from).or_else(|| {
if fs::metadata("config.toml").is_ok() { if fs::metadata("config.toml").is_ok() {
Some(PathBuf::from("config.toml")) Some(PathBuf::from("config.toml"))
} else { } else {
@ -272,31 +262,29 @@ Arguments:
} }
}); });
let mut stage = m.opt_str("stage").map(|j| j.parse().unwrap()); let mut stage = matches.opt_str("stage").map(|j| j.parse().unwrap());
let incremental = m.opt_present("i"); if matches.opt_present("incremental") {
if incremental {
if stage.is_none() { if stage.is_none() {
stage = Some(1); stage = Some(1);
} }
} }
Flags { Flags {
verbose: m.opt_count("v"), verbose: matches.opt_count("verbose"),
stage: stage, stage: stage,
on_fail: m.opt_str("on-fail"), on_fail: matches.opt_str("on-fail"),
keep_stage: m.opt_str("keep-stage").map(|j| j.parse().unwrap()), keep_stage: matches.opt_str("keep-stage").map(|j| j.parse().unwrap()),
build: m.opt_str("build").unwrap_or_else(|| { build: matches.opt_str("build").unwrap_or_else(|| {
env::var("BUILD").unwrap() env::var("BUILD").unwrap()
}), }),
host: split(m.opt_strs("host")), host: split(matches.opt_strs("host")),
target: split(m.opt_strs("target")), target: split(matches.opt_strs("target")),
config: cfg_file, config: cfg_file,
src: m.opt_str("src").map(PathBuf::from), src: matches.opt_str("src").map(PathBuf::from),
jobs: m.opt_str("jobs").map(|j| j.parse().unwrap()), jobs: matches.opt_str("jobs").map(|j| j.parse().unwrap()),
cmd: cmd, cmd: cmd,
incremental: incremental, incremental: matches.opt_present("incremental"),
} }
} }
} }

View file

@ -978,26 +978,25 @@ invalid rule dependency graph detected, was a rule added and maybe typo'd?
} }
} }
pub fn print_help(&self, command: &str) { pub fn get_help(&self, command: &str) -> Option<String> {
let kind = match command { let kind = match command {
"build" => Kind::Build, "build" => Kind::Build,
"doc" => Kind::Doc, "doc" => Kind::Doc,
"test" => Kind::Test, "test" => Kind::Test,
"bench" => Kind::Bench, "bench" => Kind::Bench,
"dist" => Kind::Dist, "dist" => Kind::Dist,
_ => return, _ => return None,
}; };
let rules = self.rules.values().filter(|r| r.kind == kind); let rules = self.rules.values().filter(|r| r.kind == kind);
let rules = rules.filter(|r| !r.path.contains("nowhere")); let rules = rules.filter(|r| !r.path.contains("nowhere"));
let mut rules = rules.collect::<Vec<_>>(); let mut rules = rules.collect::<Vec<_>>();
rules.sort_by_key(|r| r.path); rules.sort_by_key(|r| r.path);
println!("Available paths:\n"); let mut help_string = String::from("Available paths:\n");
for rule in rules { for rule in rules {
print!(" ./x.py {} {}", command, rule.path); help_string.push_str(format!(" ./x.py {} {}\n", command, rule.path).as_str());
println!("");
} }
Some(help_string)
} }
/// Construct the top-level build steps that we're going to be executing, /// Construct the top-level build steps that we're going to be executing,