diff --git a/Cargo.toml b/Cargo.toml index e7d52c3e56b..9a5948f1d4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ tempdir = "0.3" termcolor = "0.3" toml = "0.4" url = "1.1" +clap = "2.27.0" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = { version = "0.5.1", features = ["mac_os_10_7_support"] } diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index 1f282f82d3e..2caac7cea12 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -9,6 +9,7 @@ extern crate log; #[macro_use] extern crate serde_derive; extern crate serde_json; +extern crate clap; use std::collections::BTreeSet; use std::collections::HashMap; @@ -20,6 +21,8 @@ use cargo::core::shell::{Shell, Verbosity}; use cargo::util::{self, CliResult, lev_distance, Config, CargoResult}; use cargo::util::{CliError, ProcessError}; +mod cli; + #[derive(Deserialize)] pub struct Flags { flag_list: bool, @@ -85,17 +88,26 @@ fn main() { } }; - let result = (|| { - let args: Vec<_> = try!(env::args_os() - .map(|s| { - s.into_string().map_err(|s| { - format_err!("invalid unicode in argument: {:?}", s) + let is_clapified = ::std::env::args().any(|arg| match arg.as_ref() { + "build" | "bench" => true, + _ => false + }); + + let result = if is_clapified { + cli::do_main(&mut config) + } else { + (|| { + let args: Vec<_> = try!(env::args_os() + .map(|s| { + s.into_string().map_err(|s| { + format_err!("invalid unicode in argument: {:?}", s) + }) }) - }) - .collect()); - let rest = &args; - cargo::call_main_without_stdin(execute, &mut config, USAGE, rest, true) - })(); + .collect()); + let rest = &args; + cargo::call_main_without_stdin(execute, &mut config, USAGE, rest, true) + })() + }; match result { Err(e) => cargo::exit_with_error(e, &mut *config.shell()), @@ -105,8 +117,8 @@ fn main() { macro_rules! each_subcommand{ ($mac:ident) => { - $mac!(bench); - $mac!(build); +// $mac!(bench); +// $mac!(build); $mac!(check); $mac!(clean); $mac!(doc); diff --git a/src/bin/cli/bench.rs b/src/bin/cli/bench.rs new file mode 100644 index 00000000000..b11266cd676 --- /dev/null +++ b/src/bin/cli/bench.rs @@ -0,0 +1,66 @@ +use super::utils::*; + +pub fn cli() -> App { + subcommand("bench") + .about("Execute all benchmarks of a local package") + .arg( + Arg::with_name("BENCHNAME").help( + "If specified, only run benches containing this string in their names" + ) + ) + .arg( + Arg::with_name("args").help( + "Arguments for the bench binary" + ).multiple(true).last(true) + ) + + .arg_target( + "Benchmark only this package's library", + "Benchmark only the specified binary", + "Benchmark all binaries", + "Benchmark only the specified example", + "Benchmark all examples", + "Benchmark only the specified test target", + "Benchmark all tests", + "Benchmark only the specified bench target", + "Benchmark all benches", + "Benchmark all targets (default)", + ) + + .arg( + opt("no-run", "Compile, but don't run benchmarks") + ) + .arg_package( + "Package to run benchmarks for", + "Benchmark all packages in the workspace", + "Exclude packages from the benchmark", + ) + .arg_jobs() + .arg_features() + .arg_target_triple() + .arg_manifest_path() + .arg_message_format() + .arg( + opt("no-fail-fast", "Run all benchmarks regardless of failure") + ) + .arg_locked() + .after_help("\ +All of the trailing arguments are passed to the benchmark binaries generated +for filtering benchmarks and generally providing options configuring how they +run. + +If the --package argument is given, then SPEC is a package id specification +which indicates which package should be benchmarked. If it is not given, then +the current package is benchmarked. For more information on SPEC and its format, +see the `cargo help pkgid` command. + +All packages in the workspace are benchmarked if the `--all` flag is supplied. The +`--all` flag is automatically assumed for a virtual manifest. +Note that `--exclude` has to be specified in conjunction with the `--all` flag. + +The --jobs argument affects the building of the benchmark executable but does +not affect how many jobs are used when running the benchmarks. + +Compilation can be customized with the `bench` profile in the manifest. +") +} diff --git a/src/bin/cli/build.rs b/src/bin/cli/build.rs new file mode 100644 index 00000000000..63796baf9cf --- /dev/null +++ b/src/bin/cli/build.rs @@ -0,0 +1,47 @@ +use super::utils::*; + +pub fn cli() -> App { + subcommand("build") + .about("Compile a local package and all of its dependencies") + .arg_package( + "Package to build", + "Build all packages in the workspace", + "Exclude packages from the build", + ) + .arg_jobs() + .arg_target( + "Build only this package's library", + "Build only the specified binary", + "Build all binaries", + "Build only the specified example", + "Build all examples", + "Build only the specified test target", + "Build all tests", + "Build only the specified bench target", + "Build all benches", + "Build all targets (lib and bin targets by default)", + ) + .arg( + opt("release", "Build artifacts in release mode, with optimizations") + ) + .arg_features() + .arg_target_triple() + .arg_manifest_path() + .arg_message_format() + .arg_locked() + .after_help("\ +If the --package argument is given, then SPEC is a package id specification +which indicates which package should be built. If it is not given, then the +current package is built. For more information on SPEC and its format, see the +`cargo help pkgid` command. + +All packages in the workspace are built if the `--all` flag is supplied. The +`--all` flag is automatically assumed for a virtual manifest. +Note that `--exclude` has to be specified in conjunction with the `--all` flag. + +Compilation can be configured via the use of profiles which are configured in +the manifest. The default profile for this command is `dev`, but passing +the --release flag will use the `release` profile instead. +") + +} diff --git a/src/bin/cli/mod.rs b/src/bin/cli/mod.rs new file mode 100644 index 00000000000..f92eb69196b --- /dev/null +++ b/src/bin/cli/mod.rs @@ -0,0 +1,319 @@ +extern crate clap; +#[cfg(never)] +extern crate cargo; + +use cargo; + +use clap::AppSettings; +use clap::Arg; +use clap::SubCommand; +use clap::ArgMatches; +use cargo::Config; +use cargo::CargoResult; +use cargo::core::Workspace; +use cargo::util::important_paths::find_root_manifest_for_wd; +use cargo::ops::Packages; +use cargo::ops::CompileOptions; +use cargo::ops; +use std::slice; +use cargo::ops::MessageFormat; +use cargo::CliError; +use cargo::ops::CompileMode; + + +pub fn do_main(config: &mut Config) -> Result<(), CliError> { + let args = cli().get_matches(); + if args.is_present("version") { + let version = cargo::version(); + println!("{}", version); + if args.occurrences_of("verbose") > 0 { + println!("release: {}.{}.{}", + version.major, + version.minor, + version.patch); + if let Some(ref cfg) = version.cfg_info { + if let Some(ref ci) = cfg.commit_info { + println!("commit-hash: {}", ci.commit_hash); + println!("commit-date: {}", ci.commit_date); + } + } + } + return Ok(()); + } + + fn values<'a>(args: &ArgMatches, name: &str) -> &'a [String] { + let owned: Vec = args.values_of(name).unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + let owned = owned.into_boxed_slice(); + let ptr = owned.as_ptr(); + let len = owned.len(); + ::std::mem::forget(owned); + unsafe { + slice::from_raw_parts(ptr, len) + } + } + + fn config_from_args(config: &mut Config, args: &ArgMatches) -> CargoResult<()> { + let color = args.value_of("color").map(|s| s.to_string()); + config.configure( + args.occurrences_of("verbose") as u32, + if args.is_present("quite") { Some(true) } else { None }, + &color, + args.is_present("frozen"), + args.is_present("locked"), + &args.values_of_lossy("unstable-features").unwrap_or_default(), + ) + } + + fn workspace_from_args<'a>(config: &'a Config, args: &ArgMatches) -> CargoResult> { + let manifest_path = args.value_of("manifest-path").map(|s| s.to_string()); + let root = find_root_manifest_for_wd(manifest_path, config.cwd())?; + Workspace::new(&root, config) + } + + fn compile_options_from_args<'a>( + config: &'a Config, + args: &'a ArgMatches<'a>, + mode: CompileMode, + ) -> CargoResult> { + let spec = Packages::from_flags( + args.is_present("all"), + &values(args, "exclude"), + &values(args, "package") + )?; + + let release = mode == CompileMode::Bench || args.is_present("release"); + let message_format = match args.value_of("message-format") { + Some("json") => MessageFormat::Json, + Some("human") => MessageFormat::Human, + f => panic!("Impossible message format: {:?}", f), + }; + + let opts = CompileOptions { + config, + jobs: args.value_of("jobs").and_then(|v| v.parse().ok()), + target: args.value_of("target"), + features: &values(args, "features"), + all_features: args.is_present("all-features"), + no_default_features: args.is_present("no-default-features"), + spec, + mode, + release, + filter: ops::CompileFilter::new(args.is_present("lib"), + values(args, "bin"), args.is_present("bins"), + values(args, "test"), args.is_present("tests"), + values(args, "example"), args.is_present("examples"), + values(args, "bench"), args.is_present("benches"), + args.is_present("all-targets")), + message_format, + target_rustdoc_args: None, + target_rustc_args: None, + }; + Ok(opts) + } + + match args.subcommand() { + ("bench", Some(args)) => { + config_from_args(config, args)?; + let ws = workspace_from_args(config, args)?; + let compile_opts = compile_options_from_args(config, args, CompileMode::Bench)?; + + let ops = ops::TestOptions { + no_run: args.is_present("no-run"), + no_fail_fast: args.is_present("no-fail-fast"), + only_doc: false, + compile_opts, + }; + + let mut bench_args = vec![]; + bench_args.extend(args.value_of("BENCHNAME").into_iter().map(|s| s.to_string())); + bench_args.extend(args.values_of("args").unwrap_or_default().map(|s| s.to_string())); + + let err = ops::run_benches(&ws, &ops, &bench_args)?; + return match err { + None => Ok(()), + Some(err) => { + Err(match err.exit.as_ref().and_then(|e| e.code()) { + Some(i) => CliError::new(format_err!("bench failed"), i), + None => CliError::new(err.into(), 101) + }) + } + }; + } + ("build", Some(args)) => { + config_from_args(config, args)?; + let ws = workspace_from_args(config, args)?; + let compile_opts = compile_options_from_args(config, args, CompileMode::Build)?; + ops::compile(&ws, &compile_opts)?; + return Ok(()); + } + _ => return Ok(()) + } +} + +use self::utils::*; + +fn cli() -> App { + let app = App::new("cargo") + .settings(&[ + AppSettings::DisableVersion, + AppSettings::UnifiedHelpMessage, + AppSettings::DeriveDisplayOrder, + AppSettings::VersionlessSubcommands, + ]) + .about("Rust's package manager") + .arg( + opt("version", "Print version info and exit") + .short("V") + ) + .arg( + opt("list", "List installed commands") + ) + .arg( + opt("explain", "Run `rustc --explain CODE`") + .value_name("CODE") + ) + .arg( + opt("verbose", "Use verbose output (-vv very verbose/build.rs output)") + .short("v").multiple(true).global(true) + ) + .arg( + opt("quite", "No output printed to stdout") + .short("q").global(true) + ) + .arg( + opt("color", "Coloring: auto, always, never") + .value_name("WHEN").global(true) + ) + .arg( + Arg::with_name("unstable-features").help("Unstable (nightly-only) flags to Cargo") + .short("Z").value_name("FLAG").multiple(true).global(true) + ) + .after_help("\ +Some common cargo commands are (see all commands with --list): + build Compile the current project + check Analyze the current project and report errors, but don't build object files + clean Remove the target directory + doc Build this project's and its dependencies' documentation + new Create a new cargo project + init Create a new cargo project in an existing directory + run Build and execute src/main.rs + test Run the tests + bench Run the benchmarks + update Update dependencies listed in Cargo.lock + search Search registry for crates + publish Package and upload this project to the registry + install Install a Rust binary + uninstall Uninstall a Rust binary + +See 'cargo help ' for more information on a specific command. +") + .subcommands(vec![ + bench::cli(), + build::cli(), + ]) + ; + app +} + +mod utils { + use clap::{self, SubCommand, AppSettings}; + pub use clap::Arg; + + pub type App = clap::App<'static, 'static>; + + pub trait CommonArgs: Sized { + fn _arg(self, arg: Arg<'static, 'static>) -> Self; + + fn arg_package(self, package: &'static str, all: &'static str, exclude: &'static str) -> Self { + self._arg(opt("package", package).short("p").value_name("SPEC").multiple(true)) + ._arg(opt("all", all)) + ._arg(opt("exclude", exclude).value_name("SPEC").multiple(true)) + } + + fn arg_jobs(self) -> Self { + self._arg( + opt("jobs", "Number of parallel jobs, defaults to # of CPUs") + .short("j").value_name("N") + ) + } + + fn arg_target( + self, + lib: &'static str, + bin: &'static str, + bins: &'static str, + examle: &'static str, + examles: &'static str, + test: &'static str, + tests: &'static str, + bench: &'static str, + benchs: &'static str, + all: &'static str, + ) -> Self { + self._arg(opt("lib", lib)) + ._arg(opt("bin", bin).value_name("NAME").multiple(true)) + ._arg(opt("bins", bins)) + ._arg(opt("example", examle).value_name("NAME").multiple(true)) + ._arg(opt("examples", examles)) + ._arg(opt("test", test).value_name("NAME").multiple(true)) + ._arg(opt("tests", tests)) + ._arg(opt("bench", bench).value_name("NAME").multiple(true)) + ._arg(opt("benches", benchs)) + ._arg(opt("all-targets", all)) + } + + fn arg_features(self) -> Self { + self._arg( + opt("features", "Space-separated list of features to also build") + .value_name("FEATURES") + ) + ._arg(opt("all-features", "Build all available features")) + ._arg(opt("no-default-features", "Do not build the `default` feature")) + } + + fn arg_target_triple(self) -> Self { + self._arg(opt("target", "Build for the target triple").value_name("TRIPLE")) + } + + fn arg_manifest_path(self) -> Self { + self._arg(opt("manifest-path", "Path to Cargo.toml").value_name("PATH")) + } + + fn arg_message_format(self) -> Self { + self._arg( + opt("message-format", "Error format") + .value_name("FMT").possible_values(&["human", "json"]).default_value("human") + ) + } + + fn arg_locked(self) -> Self { + self._arg(opt("frozen", "Require Cargo.lock and cache are up to date")) + ._arg(opt("locked", "Require Cargo.lock is up to date")) + } + } + + impl CommonArgs for App { + fn _arg(self, arg: Arg<'static, 'static>) -> Self { + self.arg(arg) + } + } + + pub fn opt(name: &'static str, help: &'static str) -> Arg<'static, 'static> { + Arg::with_name(name).long(name).help(help) + } + + pub fn subcommand(name: &'static str) -> App { + SubCommand::with_name(name) + .settings(&[ + AppSettings::UnifiedHelpMessage, + AppSettings::DeriveDisplayOrder, + AppSettings::TrailingVarArg, + AppSettings::DontCollapseArgsInUsage, + ]) + } +} + +mod bench; +mod build;