Port bench and build to clap
authorAleksey Kladov <aleksey.kladov@gmail.com>
Tue, 6 Mar 2018 21:01:03 +0000 (00:01 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Thu, 8 Mar 2018 20:30:46 +0000 (23:30 +0300)
Cargo.toml
src/bin/cargo.rs
src/bin/cli/bench.rs [new file with mode: 0644]
src/bin/cli/build.rs [new file with mode: 0644]
src/bin/cli/mod.rs [new file with mode: 0644]

index 477a044f688db5c6ad85bedc4b54e2d0f3c118af..69d1c5c4b5d66cf3fcd37e35118d3a6eaf571567 100644 (file)
@@ -54,6 +54,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"] }
index 1f282f82d3e4cb639bae9839445042c3f39c8eda..2caac7cea12fdcfb3e72a34bcaf8220f5f935591 100644 (file)
@@ -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 (file)
index 0000000..b11266c
--- /dev/null
@@ -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 (file)
index 0000000..63796ba
--- /dev/null
@@ -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 (file)
index 0000000..f92eb69
--- /dev/null
@@ -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<String> = 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<Workspace<'a>> {
+        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<CompileOptions<'a>> {
+        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 <command>' 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;