Repository: taiki-e/cargo-hack (rust)

Issue:
Provide a way to skip run of just `--no-default-feature`
Currently, `--each-feature` and `feature-powerset` don't work well with a crate that has [cfg like this](https://github.com/rust-lang/regex/blob/f98eea5d9eb97e4fc9efcf31963364eb54f98610/src/lib.rs#L620-L621):

```rust
#[cfg(not(feature = "std"))]
compile_error!("`std` feature is currently required to build this crate")
```

Pull request:
Add --skip-no-default-features flag
```text
--skip-no-default-features
    Skip run of just --no-default-features flag.

    This flag can only be used with either --each-feature flag or
    --feature-powerset flag.
```

Closes #38

Patch:
diff --git a/src/cli.rs b/src/cli.rs
index a460873..66df025 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -43,6 +43,12 @@ const HELP: &[(&str, &str, &str, &[&str])] = &[
             "This flag can only be used with either --each-feature flag or --feature-powerset flag.",
         ],
     ),
+    (
+        "",
+        "--skip-no-default-features",
+        "Skip run of just --no-default-features flag",
+        &["This flag can only be used with either --each-feature flag or --feature-powerset flag."],
+    ),
     (
         "",
         "--no-dev-deps",
@@ -195,6 +201,8 @@ pub(crate) struct Args {
     pub(crate) ignore_unknown_features: bool,
     /// --optional-deps
     pub(crate) optional_deps: bool,
+    /// --skip-no-default-features
+    pub(crate) skip_no_default_features: bool,
 
     // flags that will be propagated to cargo
     /// --features <FEATURES>...
@@ -274,6 +282,7 @@ pub(crate) fn args(coloring: &mut Option<Coloring>) -> Result<Option<Args>> {
     let mut ignore_unknown_features = false;
     let mut ignore_non_exist_features = false;
     let mut optional_deps = false;
+    let mut skip_no_default_features = false;
 
     let res = (|| -> Result<()> {
         while let Some(arg) = args.next() {
@@ -398,6 +407,11 @@ pub(crate) fn args(coloring: &mut Option<Coloring>) -> Result<Option<Args>> {
                         return Err(multi_arg(&arg, subcommand.as_ref()));
                     }
                 }
+                "--skip-no-default-features" => {
+                    if mem::replace(&mut skip_no_default_features, true) {
+                        return Err(multi_arg(&arg, subcommand.as_ref()));
+                    }
+                }
                 "--ignore-unknown-features" => {
                     if ignore_unknown_features || ignore_non_exist_features {
                         return Err(multi_arg(&arg, subcommand.as_ref()));
@@ -438,11 +452,18 @@ pub(crate) fn args(coloring: &mut Option<Coloring>) -> Result<Option<Args>> {
     if !exclude.is_empty() && workspace.is_none() {
         bail!("--exclude can only be used together with --workspace");
     }
-    if !skip.is_empty() && (!each_feature && !feature_powerset) {
-        bail!("--skip can only be used with either --each-feature or --feature-powerset");
-    }
-    if optional_deps && (!each_feature && !feature_powerset) {
-        bail!("--optional-deps can only be used with either --each-feature or --feature-powerset");
+    if !each_feature && !feature_powerset {
+        if !skip.is_empty() {
+            bail!("--skip can only be used with either --each-feature or --feature-powerset");
+        } else if optional_deps {
+            bail!(
+                "--optional-deps can only be used with either --each-feature or --feature-powerset"
+            );
+        } else if skip_no_default_features {
+            bail!(
+                "--skip-no-default-features can only be used with either --each-feature or --feature-powerset"
+            );
+        }
     }
 
     if let Some(subcommand) = &subcommand {
@@ -527,6 +548,7 @@ For more information try --help
         ignore_private,
         ignore_unknown_features: ignore_unknown_features || ignore_non_exist_features,
         optional_deps,
+        skip_no_default_features,
 
         features,
         color,
diff --git a/src/package.rs b/src/package.rs
index 52a9567..bb4115e 100644
--- a/src/package.rs
+++ b/src/package.rs
@@ -78,11 +78,13 @@ impl<'a> Kind<'a> {
                 *total += 1;
                 Kind::Nomal { show_progress: true }
             } else {
-                // +1: no default features
-                *total += features.len() + 1;
+                *total += features.len();
                 if !args.skip.iter().any(|x| x == "default") {
                     *total += 1;
                 }
+                if !args.skip_no_default_features {
+                    *total += 1;
+                }
                 Kind::Each { features }
             }
         } else if args.feature_powerset {
@@ -96,12 +98,14 @@ impl<'a> Kind<'a> {
                 *total += 1;
                 Kind::Nomal { show_progress: true }
             } else {
-                // +1: no default features
                 // -1: the first element of a powerset is `[]`
-                *total += features.len();
+                *total += features.len() - 1;
                 if !args.skip.iter().any(|x| x == "default") {
                     *total += 1;
                 }
+                if !args.skip_no_default_features {
+                    *total += 1;
+                }
                 Kind::Powerset { features }
             }
         } else {
@@ -135,11 +139,13 @@ pub(crate) fn exec(
 
     line.arg("--no-default-features");
 
-    // run with no default features if the package has other features
-    //
-    // `default` is not skipped because `cfg(feature = "default")` is work
-    // if `default` feature specified.
-    exec_cargo(args, package, &line, info, true)?;
+    if !args.skip_no_default_features {
+        // run with no default features if the package has other features
+        //
+        // `default` is not skipped because `cfg(feature = "default")` is work
+        // if `default` feature specified.
+        exec_cargo(args, package, &line, info, true)?;
+    }
 
     match &package.kind {
         Kind::Each { features } => features
diff --git a/src/remove_dev_deps.rs b/src/remove_dev_deps.rs
index f47bc7a..fb88153 100644
--- a/src/remove_dev_deps.rs
+++ b/src/remove_dev_deps.rs
@@ -63,11 +63,11 @@ pub(crate) fn remove_dev_deps(bytes: &str) -> String {
         }
 
         prev = pos;
-        // pos + 0 = '['
-        // pos + 1 = part of table name (or '[')
-        // pos + 2 = ']' (or part of table name)
-        // pos + 3 = '\n' or eof (or part of table name or ']')
-        // pos + 4 = start of next table or eof (or part of this table)
+        // pos + 0: '['
+        // pos + 1: part of table name (or '[')
+        // pos + 2: ']' (or part of table name)
+        // pos + 3: '\n' or eof (or part of table name or ']')
+        // pos + 4: start of next table or eof (or part of this table)
         pos += 4;
         next = bytes.get(pos..).and_then(|s| s.find('[')).map(|n| pos + n);
         continue;


Test patch:
diff --git a/tests/test.rs b/tests/test.rs
index 8d90071..b424913 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -639,6 +639,27 @@ fn skip_default() {
         );
 }
 
+#[test]
+fn skip_no_default_features() {
+    cargo_hack()
+        .args(&["check", "--each-feature", "--skip-no-default-features"])
+        .current_dir(test_dir("tests/fixtures/real"))
+        .output()
+        .unwrap()
+        .assert_success()
+        .assert_stderr_contains("running `cargo check` on real (1/4)")
+        .assert_stderr_not_contains("running `cargo check --no-default-features` on real")
+        .assert_stderr_contains(
+            "running `cargo check --no-default-features --features a` on real (2/4)",
+        )
+        .assert_stderr_contains(
+            "running `cargo check --no-default-features --features b` on real (3/4)",
+        )
+        .assert_stderr_contains(
+            "running `cargo check --no-default-features --features c` on real (4/4)",
+        );
+}
+
 #[test]
 fn each_feature_all() {
     cargo_hack()