From 9c0cd38940004ebe974ee7b407bdf5be45f31e72 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 10 Nov 2019 19:51:09 +0900 Subject: [PATCH] Use cargo metadata instead of manual manifest validation --- .github/workflows/ci.yml | 1 + Cargo.lock | 55 +++- Cargo.toml | 3 +- README.md | 32 +-- src/cli.rs | 111 +++++--- src/main.rs | 241 ++++++------------ src/manifest.rs | 83 +----- src/metadata.rs | 74 ++++++ src/process.rs | 140 +++++++--- src/workspace.rs | 56 ---- .../.cargo/config | 0 .../Cargo.toml | 0 .../member1/Cargo.toml | 0 .../member1}/src/main.rs | 0 .../member2/Cargo.toml | 0 .../member2}/src/main.rs | 0 tests/fixtures/real/Cargo.toml | 1 + .../member1 => real/member3}/Cargo.toml | 5 +- .../member1 => real/member3}/src/main.rs | 0 tests/fixtures/virtual/Cargo.toml | 1 + .../dir/not_find_manifest}/Cargo.toml | 2 +- .../dir/not_find_manifest}/src/main.rs | 0 tests/fixtures/virtual/member2/Cargo.toml | 1 + .../windows_not_find_manifest/Cargo.toml | 5 - .../windows_package_collision/.cargo/config | 2 - tests/test.rs | 225 +++++++++++----- 26 files changed, 592 insertions(+), 446 deletions(-) create mode 100644 src/metadata.rs delete mode 100644 src/workspace.rs rename tests/fixtures/{windows_not_find_manifest => package_collision}/.cargo/config (100%) rename tests/fixtures/{windows_package_collision => package_collision}/Cargo.toml (100%) rename tests/fixtures/{windows_package_collision => package_collision}/member1/Cargo.toml (100%) rename tests/fixtures/{windows_not_find_manifest/dir/member2 => package_collision/member1}/src/main.rs (100%) rename tests/fixtures/{windows_not_find_manifest/dir => package_collision}/member2/Cargo.toml (100%) rename tests/fixtures/{windows_not_find_manifest/member1 => package_collision/member2}/src/main.rs (100%) rename tests/fixtures/{windows_not_find_manifest/member1 => real/member3}/Cargo.toml (76%) rename tests/fixtures/{windows_package_collision/member1 => real/member3}/src/main.rs (100%) rename tests/fixtures/{windows_package_collision/member2 => virtual/dir/not_find_manifest}/Cargo.toml (89%) rename tests/fixtures/{windows_package_collision/member2 => virtual/dir/not_find_manifest}/src/main.rs (100%) delete mode 100644 tests/fixtures/windows_not_find_manifest/Cargo.toml delete mode 100644 tests/fixtures/windows_package_collision/.cargo/config diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31639b68..05801994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: run: | cargo test - name: cargo hack + shell: bash run: | cargo run -- hack check --each-feature --no-dev-deps cargo run -- hack --remove-dev-deps diff --git a/Cargo.lock b/Cargo.lock index d9c6072b..e81df737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,8 @@ version = "0.2.1" dependencies = [ "anyhow 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "easy-ext 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "toml_edit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -60,7 +61,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -69,12 +70,9 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "indexmap" -version = "1.3.0" +name = "itoa" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "libc" @@ -129,9 +127,42 @@ name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -230,7 +261,7 @@ dependencies = [ "checksum combine 3.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" "checksum easy-ext 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed79d40452cc72aa676b63ddde7f415865b412984738a1ba11096615ab0b262c" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" @@ -239,7 +270,11 @@ dependencies = [ "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" +"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" +"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" +"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum toml_edit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87f53b1aca7d5fe2e17498a38cac0e1f5a33234d5b980fb36b9402bb93b98ae4" diff --git a/Cargo.toml b/Cargo.toml index 202afe03..7b52094f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,10 @@ A tool to work around some limitations on cargo. [dependencies] anyhow = "1.0.19" +serde = { version = "1.0.102", features = ["derive"] } +serde_json = "1.0.41" termcolor = "1.0.5" toml_edit = "0.1.5" -indexmap = "1.3.0" [dev-dependencies] easy-ext = "0.1.6" diff --git a/README.md b/README.md index 27bfd7ac..0a4455fd 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,15 @@ cargo install cargo-hack To install the current cargo-hack requires Rust 1.36 or later. -*Note: cargo-hack is currently only tested on Linux and macOS. It may not work well on other platforms. See [#3] for Windows support.* - ## Usage `cargo-hack` is basically wrapper of `cargo` that propagates subcommand and most of the passed flags to `cargo`, but provides additional flags and changes the behavior of some existing flags. * **`--each-feature`** - Perform for each feature *which includes `--no-default-features` and default features* of the package. + Perform for each feature which includes default features and `--no-default-features` of the package. - This is useful to check that each feature is working properly. (*When used for this purpose, it is recommended to use with `--no-dev-deps`.*) + This is useful to check that each feature is working properly. (When used for this purpose, it is recommended to use with `--no-dev-deps` to avoid [cargo#4866].) ```sh cargo hack check --each-feature --no-dev-deps @@ -48,20 +46,22 @@ To install the current cargo-hack requires Rust 1.36 or later. This is a workaround for an issue that dev-dependencies leaking into normal build ([cargo#4866]). + Also, this can be used as a workaround for an issue that `cargo` does not allow publishing a package with cyclic dev-dependencies. ([cargo#4242]) + + ```sh + cargo hack publish --no-dev-deps --dry-run --allow-dirty + ``` + + Currently, using `--no-dev-deps` flag removes dev-dependencies from real manifest while cargo-hack is running and restores it when finished. See [cargo#4242] for why this is necessary. + Also, this behavior may change in the future on some subcommands. See also [#15]. + * **`--remove-dev-deps`** Equivalent to `--no-dev-deps` except for does not restore the original `Cargo.toml` after execution. This is useful to know what Cargo.toml that cargo-hack is actually using with `--no-dev-deps`. - Also, this can be used as a workaround for an issue that `cargo` does not allow publishing a package with cyclic dev-dependencies. ([cargo#4242]) - - ```sh - # This flag also works without subcommands. - cargo hack --remove-dev-deps - cargo publish --dry-run --allow-dirty - # Equivalent to `cargo hack publish --no-dev-deps --dry-run --allow-dirty` - ``` + *This flag also works without subcommands.* * **`--ignore-private`** @@ -79,7 +79,7 @@ To install the current cargo-hack requires Rust 1.36 or later. * **`--features`**, **`--no-default-features`** - Unlike `cargo` ([cargo#3620], [cargo#4106], [cargo#4463], [cargo#4753], [cargo#5015], [cargo#5364], [cargo#6195]), it can also be applied to sub-crate. + Unlike `cargo` ([cargo#3620], [cargo#4106], [cargo#4463], [cargo#4753], [cargo#5015], [cargo#5364], [cargo#6195]), it can also be applied to sub-crates. * **`--all`**, **`--workspace`** @@ -92,13 +92,14 @@ To install the current cargo-hack requires Rust 1.36 or later. members=("foo" "bar") for member in ${members[@]}; do - cargo check --manifest-path "${member}" + cargo check --manifest-path "${member}" done ``` - *Note that there is currently no guarantee in which order workspace members will be performed.* (This means that `cargo hack publish --all --ignore-private` does not necessarily function as you intended.) + Workspace members will be performed according to the order of the 'packages' fields of [cargo-metadata]. [#3]: https://github.com/taiki-e/cargo-hack/issues/3 +[#15]: https://github.com/taiki-e/cargo-hack/issues/15 [cargo#3620]: https://github.com/rust-lang/cargo/issues/3620 [cargo#4106]: https://github.com/rust-lang/cargo/issues/4106 [cargo#4463]: https://github.com/rust-lang/cargo/issues/4463 @@ -108,6 +109,7 @@ To install the current cargo-hack requires Rust 1.36 or later. [cargo#5364]: https://github.com/rust-lang/cargo/issues/5364 [cargo#6195]: https://github.com/rust-lang/cargo/issues/6195 [cargo#4242]: https://github.com/rust-lang/cargo/issues/4242 +[cargo-metadata]: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html ## License diff --git a/src/cli.rs b/src/cli.rs index b518c0ba..c1bb0d10 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,13 +1,13 @@ -use std::{env, fs, path::PathBuf, str::FromStr}; +use std::{env, fs, path::PathBuf, rc::Rc, str::FromStr}; use anyhow::{bail, format_err, Error, Result}; use termcolor::ColorChoice; -pub(crate) fn print_version() { +fn print_version() { println!("{0} {1}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"),) } -pub(crate) fn print_help() { +fn print_help() { println!( "\ {0} {1} @@ -45,6 +45,7 @@ OPTIONS: -v, --verbose Use verbose output (this flag will be propagated to cargo) --color Coloring: auto, always, never + (this flag will be propagated to cargo) -h, --help Prints help information -V, --version Prints version information @@ -63,27 +64,39 @@ Some common cargo commands are (see all commands with --list): #[derive(Debug)] pub(crate) struct Args { - pub(crate) first: Vec, - pub(crate) second: Vec, + pub(crate) leading_args: Rc<[String]>, + pub(crate) trailing_args: Rc<[String]>, pub(crate) subcommand: Option, + /// --manifest-path pub(crate) manifest_path: Option, - // canonicalized target-dir + /// (canonicalized) --target-dir pub(crate) target_dir: Option, + /// --features ... + pub(crate) features: Vec, + /// -p, --package ... pub(crate) package: Vec, + /// --exclude ... pub(crate) exclude: Vec, - pub(crate) features: Vec, - - pub(crate) workspace: Option, + /// --all, --workspace + pub(crate) workspace: bool, + /// --each-feature pub(crate) each_feature: bool, + /// --no-dev-deps pub(crate) no_dev_deps: bool, + /// --remove-dev-deps pub(crate) remove_dev_deps: bool, + /// --ignore-private pub(crate) ignore_private: bool, + /// --ignore-unknown-features, (--ignore-non-exist-features) pub(crate) ignore_unknown_features: bool, + // flags that will be propagated to cargo + /// --color pub(crate) color: Option, + /// -v, --verbose, -vv pub(crate) verbose: bool, } @@ -102,6 +115,14 @@ impl Coloring { Some(Coloring::Never) => ColorChoice::Never, } } + + pub(crate) fn as_str(self) -> &'static str { + match self { + Coloring::Auto => "auto", + Coloring::Always => "always", + Coloring::Never => "never", + } + } } impl FromStr for Coloring { @@ -123,12 +144,15 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { let _ = args.next(); // executable name match &args.next() { Some(a) if a == "hack" => {} - Some(_) | None => return Ok(None), + Some(_) | None => { + print_help(); + return Ok(None); + } } - let mut first = Vec::new(); - + let mut leading = Vec::new(); let mut subcommand: Option = None; + let mut target_dir = None; let mut manifest_path = None; let mut color = None; @@ -162,7 +186,7 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { if subcommand.is_none() { subcommand = Some(arg.clone()); } - first.push(arg); + leading.push(arg); continue; } @@ -177,8 +201,8 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { Some(next) => { if $propagate { $ident = Some(next.clone()); - first.push(arg); - first.push(next); + leading.push(arg); + leading.push(next); } else { $ident = Some(next); } @@ -189,12 +213,12 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { if $ident.is_some() { return Err(multi_arg($help, subcommand.as_ref())); } - match arg.splitn(2, '=').nth(1).map(|s| s.to_string()) { + match arg.splitn(2, '=').nth(1).map(ToString::to_string) { None => return Err(req_arg($help, subcommand.as_ref())), arg @ Some(_) => $ident = arg, } if $propagate { - first.push(arg); + leading.push(arg); } continue; } @@ -205,7 +229,7 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { if arg == $pat1 { if let Some(arg) = args.next() { if $allow_split { - $ident.extend(arg.split(',').map(|s| s.to_string())); + $ident.extend(arg.split(',').map(ToString::to_string)); } else { $ident.push(arg); } @@ -216,7 +240,7 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { } else if arg.starts_with($pat2) { if let Some(arg) = arg.splitn(2, '=').nth(1) { if $allow_split { - $ident.extend(arg.split(',').map(|s| s.to_string())); + $ident.extend(arg.split(',').map(ToString::to_string)); } else { $ident.push(arg.to_string()); } @@ -292,7 +316,7 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { } ignore_non_exist_features = true; } - _ => first.push(arg), + _ => leading.push(arg), } } @@ -302,14 +326,19 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { let color = color.map(|c| c.parse()).transpose()?; *coloring = color; - if first.is_empty() && !remove_dev_deps - || subcommand.is_none() && first.iter().any(|a| a == "--help" || a == "-h") + if leading.is_empty() && !remove_dev_deps + || subcommand.is_none() && leading.iter().any(|a| a == "--help" || a == "-h") { + print_help(); + return Ok(None); + } + if leading.iter().any(|a| a == "--version" || a == "-V" || a == "-vV") { + print_version(); return Ok(None); } let target_dir = target_dir.map(fs::canonicalize).transpose()?; - let verbose = first.iter().any(|a| a == "--verbose" || a == "-v" || a == "-vv"); + let verbose = leading.iter().any(|a| a == "--verbose" || a == "-v" || a == "-vv"); if ignore_non_exist_features { warn!( color, @@ -328,32 +357,52 @@ pub(crate) fn args(coloring: &mut Option) -> Result> { } } } - if let Some(pos) = first.iter().position(|a| match a.as_str() { + if let Some(pos) = leading.iter().position(|a| match a.as_str() { "--example" | "--examples" | "--test" | "--tests" | "--bench" | "--benches" | "--all-targets" => true, _ => false, }) { if remove_dev_deps { - bail!("--remove-dev-deps may not be used together with {}", first[pos]) + bail!("--remove-dev-deps may not be used together with {}", leading[pos]) } else if no_dev_deps { - bail!("--no-dev-deps may not be used together with {}", first[pos]) + bail!("--no-dev-deps may not be used together with {}", leading[pos]) + } + } + + if subcommand.is_none() { + if leading.iter().any(|a| a == "--list") { + let mut line = crate::ProcessBuilder::new(crate::cargo_binary()); + line.arg("--list"); + line.exec()?; + return Ok(None); + } else if !remove_dev_deps { + // TODO: improve this + bail!( + "\ +No subcommand or valid flag specified. + +USAGE: + cargo hack [OPTIONS] [SUBCOMMAND] + +For more information try --help +" + ); } } res.map(|()| { Some(Args { - first, - second: args.collect(), + leading_args: leading.into(), + // shared_from_iter requires Rust 1.37 + trailing_args: args.collect::>().into(), subcommand, manifest_path, target_dir, - package, exclude, features, - - workspace, + workspace: workspace.is_some(), each_feature, no_dev_deps, remove_dev_deps, diff --git a/src/main.rs b/src/main.rs index 194a107a..2e0c40a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,18 +7,18 @@ mod term; mod cli; mod manifest; +mod metadata; mod process; -mod workspace; -use std::{env, ffi::OsString, fs}; +use std::{env, ffi::OsString, fs, path::Path}; use anyhow::{bail, Context, Result}; use crate::{ cli::{Args, Coloring}, manifest::{find_root_manifest_for_wd, Manifest}, + metadata::{Metadata, Package}, process::ProcessBuilder, - workspace::Workspace, }; fn main() { @@ -31,97 +31,92 @@ fn main() { fn try_main(coloring: &mut Option) -> Result<()> { let args = match cli::args(coloring)? { - None => { - cli::print_help(); - return Ok(()); - } + None => return Ok(()), Some(args) => args, }; - if args.first.iter().any(|a| a == "--version" || a == "-V") { - cli::print_version(); - return Ok(()); - } - if args.subcommand.is_none() { - if args.first.iter().any(|a| a == "--list") { - let mut line = ProcessBuilder::new(cargo_binary()); - line.arg("--list"); - line.exec()?; - return Ok(()); - } else if !args.remove_dev_deps { - // TODO: improve this - bail!( - "\ -No subcommand or valid flag specified. - -USAGE: - cargo hack [OPTIONS] [SUBCOMMAND] - -For more information try --help -" - ); - } - } - - let current_dir = &env::current_dir()?; + let metadata = Metadata::new(&args)?; let current_manifest = match &args.manifest_path { - Some(path) => Manifest::with_manifest_path(path)?, - None => Manifest::new(&find_root_manifest_for_wd(¤t_dir)?)?, + Some(path) => Manifest::new(Path::new(path))?, + None => Manifest::new(find_root_manifest_for_wd(&env::current_dir()?)?)?, }; - let workspace = Workspace::new(¤t_manifest)?; - exec_on_workspace(&args, &workspace) + exec_on_workspace(&args, ¤t_manifest, &metadata) } -fn exec_on_workspace(args: &Args, workspace: &Workspace<'_>) -> Result<()> { - if args.workspace.is_some() { +fn exec_on_workspace(args: &Args, current_manifest: &Manifest, metadata: &Metadata) -> Result<()> { + let mut line = ProcessBuilder::from_args(cargo_binary(), &args); + + line.arg("--target-dir"); + line.arg(args.target_dir.as_ref().unwrap_or(&metadata.target_directory)); + + if let Some(color) = args.color { + line.arg("--color"); + line.arg(color.as_str()); + } + + if args.workspace { for spec in &args.exclude { - if !workspace.members.contains_key(spec) { + if !metadata.packages.iter().any(|package| package.name == *spec) { warn!( args.color, "excluded package(s) {} not found in workspace `{}`", spec, - workspace.current_manifest.dir().display() + metadata.workspace_root.display() ); } } - for (_, manifest) in - workspace.members.iter().filter(|(spec, _)| !args.exclude.contains(spec)) + for package in + metadata.packages.iter().filter(|package| !args.exclude.contains(&package.name)) { - exec_on_package(manifest, args)?; + exec_on_package(args, package, &line)?; } } else if !args.package.is_empty() { for spec in &args.package { - if let Some(manifest) = workspace.members.get(spec) { - exec_on_package(manifest, args)?; - } else { + if !metadata.packages.iter().any(|package| package.name == *spec) { bail!("package ID specification `{}` matched no packages", spec); } } - } else if workspace.current_manifest.is_virtual() { - for (_, manifest) in workspace.members.iter() { - exec_on_package(manifest, args)?; + + for package in + metadata.packages.iter().filter(|package| args.package.contains(&package.name)) + { + exec_on_package(args, package, &line)?; + } + } else if current_manifest.is_virtual() { + for package in &metadata.packages { + exec_on_package(args, package, &line)?; } - } else if !workspace.current_manifest.is_virtual() { - exec_on_package(workspace.current_manifest, args)?; + } else if !current_manifest.is_virtual() { + let current_package = current_manifest.package_name(); + let package = + metadata.packages.iter().find(|package| package.name == *current_package).unwrap(); + exec_on_package(args, package, &line)?; } Ok(()) } -fn exec_on_package(manifest: &Manifest, args: &Args) -> Result<()> { +fn exec_on_package(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> { + let manifest = Manifest::new(&package.manifest_path)?; + if args.ignore_private && manifest.is_private() { - info!(args.color, "skipped running on {}", manifest.package_name_verbose(args)); + info!(args.color, "skipped running on {}", package.name_verbose(args)); } else if args.subcommand.is_some() || args.remove_dev_deps { - no_dev_deps(manifest, args)?; + no_dev_deps(args, package, manifest, line)?; } Ok(()) } -fn no_dev_deps(manifest: &Manifest, args: &Args) -> Result<()> { +fn no_dev_deps( + args: &Args, + package: &Package, + mut manifest: Manifest, + line: &ProcessBuilder, +) -> Result<()> { struct Bomb<'a> { manifest: &'a Manifest, args: &'a Args, @@ -145,139 +140,73 @@ fn no_dev_deps(manifest: &Manifest, args: &Args) -> Result<()> { } } + let f = |args: &Args, package: &Package, line: &ProcessBuilder| { + let mut line = line.clone(); + line.features(args, package); + line.arg("--manifest-path"); + line.arg(&package.manifest_path); + + if args.each_feature { + exec_for_each_feature(args, package, &line) + } else { + exec_cargo(args, package, &line) + } + }; + if args.no_dev_deps || args.remove_dev_deps { let mut res = Ok(()); - let mut bomb = Bomb { manifest, args, done: false, res: &mut res }; + let new = manifest.remove_dev_deps(); + let mut bomb = Bomb { manifest: &manifest, args, done: false, res: &mut res }; - fs::write(&manifest.path, remove_dev_deps(manifest)).with_context(|| { - format!("failed to update manifest file: {}", manifest.path.display()) + fs::write(&package.manifest_path, new).with_context(|| { + format!("failed to update manifest file: {}", package.manifest_path.display()) })?; if args.subcommand.is_some() { - each_feature(manifest, args)?; + f(args, package, line)?; } bomb.done = true; drop(bomb); res?; } else if args.subcommand.is_some() { - each_feature(manifest, args)?; + f(args, package, line)?; } Ok(()) } -fn each_feature(manifest: &Manifest, args: &Args) -> Result<()> { - let mut features = String::new(); - if args.ignore_unknown_features { - let f: Vec<_> = args - .features - .iter() - .filter(|f| { - if manifest.features.contains(f) { - true - } else { - // ignored - info!( - args.color, - "skipped applying unknown `{}` feature to {}", - f, - manifest.package_name_verbose(args) - ); - false - } - }) - .map(String::as_str) - .collect(); - if !f.is_empty() { - features.push_str("--features="); - features.push_str(&f.join(",")); - } - } else if !args.features.is_empty() { - features.push_str("--features="); - features.push_str(&args.features.join(",")); - } - - let features = if features.is_empty() { None } else { Some(&*features) }; - - if args.each_feature { - exec_each_feature(manifest, args, features) - } else { - exec_cargo_command(manifest, args, features, &[]) - } -} - -fn remove_dev_deps(manifest: &Manifest) -> String { - let mut doc = manifest.toml.clone(); - manifest::remove_key_and_target_key(doc.as_table_mut(), "dev-dependencies"); - doc.to_string_in_original_order() -} - -fn exec_each_feature(manifest: &Manifest, args: &Args, features: Option<&str>) -> Result<()> { +fn exec_for_each_feature(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> { // run with default features - exec_cargo_command(manifest, args, features, &[])?; + exec_cargo(args, package, line)?; - if manifest.features.is_empty() { + if package.features.is_empty() { return Ok(()); } + let mut line = line.clone(); + 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_command(manifest, args, features, &["--no-default-features"])?; + exec_cargo(args, package, &line)?; // run with each feature - manifest.features.iter().filter(|&k| k != "default").try_for_each(|feature| { - let features = match features { - Some(features) => String::from(features) + "," + feature, - None => String::from("--features=") + feature, - }; - exec_cargo_command(manifest, args, Some(&*features), &["--no-default-features"]) + package.features.iter().filter(|(k, _)| *k != "default").try_for_each(|(feature, _)| { + let mut line = line.clone(); + line.append_features(&[feature]); + exec_cargo(args, package, &line) }) } -fn exec_cargo_command( - manifest: &Manifest, - args: &Args, - features: Option<&str>, - extra_args: &[&str], -) -> Result<()> { - let mut line = ProcessBuilder::new(cargo_binary()); - - line.args(&args.first); - - if let Some(features) = features { - line.arg(features); - } - - if let Some(target_dir) = args.target_dir.as_ref() { - line.arg("--target-dir"); - line.arg(target_dir); - } - - line.args(extra_args); - - line.args2(&args.second); - - if args.verbose { - line.arg("--manifest-path"); - line.arg(&manifest.path); - - info!(args.color, "running {} on {}", line, manifest.package_name_verbose(args)); - } else { - info!(args.color, "running {} on {}", line, manifest.package_name_verbose(args)); - - // Displaying --manifest-path is redundant. - line.arg("--manifest-path"); - line.arg(&manifest.path); - } - +fn exec_cargo(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> { + info!(args.color, "running {} on {}", line, package.name_verbose(args)); line.exec() } fn cargo_binary() -> OsString { - let cargo_src = env::var_os("CARGO_HACK_CARGO_SRC"); - let cargo = env::var_os("CARGO"); - cargo_src.unwrap_or_else(|| cargo.unwrap_or_else(|| OsString::from("cargo"))) + env::var_os("CARGO_HACK_CARGO_SRC") + .unwrap_or_else(|| env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"))) } diff --git a/src/manifest.rs b/src/manifest.rs index ce829b3c..352a0481 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,69 +1,31 @@ use std::{ - borrow::Cow, fs, ops, path::{Path, PathBuf}, }; use anyhow::{bail, Context, Result}; -use toml_edit::{Array, Document, Item as Value, Table}; - -use crate::Args; +use toml_edit::{Document, Table}; #[derive(Clone, Debug)] pub(crate) struct Manifest { pub(crate) path: PathBuf, pub(crate) raw: String, - pub(crate) toml: Document, - pub(crate) features: Vec, + pub(crate) doc: Document, } impl Manifest { - pub(crate) fn new(path: &Path) -> Result { - let path = path - .canonicalize() - .with_context(|| format!("failed to canonicalize manifest path: {}", path.display()))?; + pub(crate) fn new(path: impl Into) -> Result { + let path = path.into(); let raw = fs::read_to_string(&path) .with_context(|| format!("failed to read manifest from {}", path.display()))?; - let toml: Document = raw + let doc: Document = raw .parse() .with_context(|| format!("failed to parse manifest file: {}", path.display()))?; - let features = toml - .as_table() - .get("features") - .and_then(Value::as_table) - .into_iter() - .flat_map(|f| f.iter().map(|(k, _)| k.to_string())) - .collect::>(); - - let manifest = Self { path, raw, toml, features }; - - if manifest.package().is_none() && manifest.members().is_none() { - // TODO: improve error message - bail!("expected 'package' or 'workspace'"); - } - - Ok(manifest) - } - - pub(crate) fn with_manifest_path(path: &str) -> Result { - if !path.ends_with("Cargo.toml") { - bail!("the manifest-path must be a path to a Cargo.toml file"); - } - - let path = Path::new(path); - if !path.exists() { - bail!("manifest path `{}` does not exist", path.display()); - } - - Self::new(path) - } - - pub(crate) fn dir(&self) -> &Path { - self.path.parent().unwrap() + Ok(Self { path, raw, doc }) } pub(crate) fn package(&self) -> Option<&Table> { - self.toml.as_table().get("package")?.as_table() + self.doc.as_table().get("package")?.as_table() } pub(crate) fn package_name(&self) -> &str { @@ -71,33 +33,22 @@ impl Manifest { (|| self.package()?.get("name")?.as_str())().unwrap() } - pub(crate) fn package_name_verbose(&self, args: &Args) -> Cow<'_, str> { - assert!(!self.is_virtual()); - if args.verbose { - Cow::Owned(format!("{} ({})", self.package_name(), self.dir().display())) - } else { - Cow::Borrowed(self.package_name()) - } - } - pub(crate) fn is_virtual(&self) -> bool { self.package().is_none() } + // `metadata.package.publish` requires Rust 1.39 pub(crate) fn is_private(&self) -> bool { (|| self.package()?.get("publish")?.as_bool())().map_or(false, ops::Not::not) } - pub(crate) fn workspace(&self) -> Option<&Table> { - self.toml.as_table().get("workspace")?.as_table() - } - - pub(crate) fn members(&self) -> Option<&Array> { - (|| self.workspace()?.get("members")?.as_array())() + pub(crate) fn remove_dev_deps(&mut self) -> String { + remove_key_and_target_key(self.doc.as_table_mut(), "dev-dependencies"); + self.doc.to_string_in_original_order() } } -pub(crate) fn remove_key_and_target_key(table: &mut Table, key: &str) { +fn remove_key_and_target_key(table: &mut Table, key: &str) { table.remove(key); if let Some(table) = table.entry("target").as_table_mut() { // `toml_edit::Table` does not have `.iter_mut()`, so collect keys. @@ -122,13 +73,3 @@ pub(crate) fn find_root_manifest_for_wd(cwd: &Path) -> Result { bail!("could not find `Cargo.toml` in `{}` or any parent directory", cwd.display()) } - -/// Returns the path to the `Cargo.toml` in `pwd`, if it exists. -pub(crate) fn find_project_manifest_exact(pwd: &Path) -> Result { - let manifest = pwd.join("Cargo.toml"); - if manifest.exists() { - Ok(manifest) - } else { - bail!("Could not find `Cargo.toml` in `{}`", pwd.display()) - } -} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 00000000..aacbb276 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,74 @@ +use std::{ + borrow::Cow, + collections::BTreeMap, + env, + ffi::OsString, + io::{self, Write}, + path::PathBuf, + process::Command, +}; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +use crate::Args; + +// Refs: +// * https://github.com/oli-obk/cargo_metadata +// * https://github.com/rust-lang/cargo/blob/0.40.0/src/cargo/ops/cargo_output_metadata.rs#L79 +// * https://github.com/rust-lang/cargo/blob/0.40.0/src/cargo/core/package.rs#L57 + +#[derive(Debug, Deserialize)] +pub(crate) struct Metadata { + /// A list of all crates referenced by this crate (and the crate itself) + pub(crate) packages: Vec, + /// Build directory + pub(crate) target_directory: PathBuf, + /// Workspace root + pub(crate) workspace_root: PathBuf, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct Package { + /// Name as given in the `Cargo.toml` + pub(crate) name: String, + /// Features provided by the crate, mapped to the features required by that feature. + pub(crate) features: BTreeMap>, + /// Path containing the `Cargo.toml` + pub(crate) manifest_path: PathBuf, +} + +impl Metadata { + pub(crate) fn new(args: &Args) -> Result { + let cargo = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); + let mut command = Command::new(cargo); + command.args(&["metadata", "--no-deps", "--format-version=1"]); + if let Some(manifest_path) = &args.manifest_path { + command.arg("--manifest-path"); + command.arg(manifest_path); + } + + let output = command.output().context("failed to run 'cargo metadata'")?; + if !output.status.success() { + let _ = io::stderr().write_all(&output.stderr); + let code = output.status.code().unwrap_or(1); + std::process::exit(code); + } + + serde_json::from_slice(&output.stdout).context("failed to parse metadata") + } +} + +impl Package { + pub(crate) fn name_verbose(&self, args: &Args) -> Cow<'_, str> { + if args.verbose { + Cow::Owned(format!( + "{} ({})", + self.name, + self.manifest_path.parent().unwrap().display() + )) + } else { + Cow::Borrowed(&self.name) + } + } +} diff --git a/src/process.rs b/src/process.rs index 3ea5149a..c6426f8d 100644 --- a/src/process.rs +++ b/src/process.rs @@ -4,40 +4,95 @@ use std::{ fmt, path::Path, process::{Command, ExitStatus, Output}, + rc::Rc, str, }; use anyhow::{Context, Result}; +use crate::{Args, Package}; + // Based on https://github.com/rust-lang/cargo/blob/0.39.0/src/cargo/util/process_builder.rs /// A builder object for an external process, similar to `std::process::Command`. #[derive(Clone, Debug)] pub(crate) struct ProcessBuilder { /// The program to execute. - program: OsString, + program: Rc, /// A list of arguments to pass to the program (until '--'). - args: Vec, + leading_args: Rc<[String]>, /// A list of arguments to pass to the program (after '--'). - args2: Vec, + trailing_args: Rc<[String]>, + /// A list of arguments to pass to the program (between `leading_args` and '--'). + args: Vec, + // cargo less than Rust 1.38 cannot handle multiple '--features' flags, so it creates another String. + features: String, + /// Any environment variables that should be set for the program. env: HashMap>, /// The directory to run the program from. cwd: Option, + /// Use verbose output. + verbose: bool, } impl ProcessBuilder { /// Creates a new `ProcessBuilder`. - pub(crate) fn new(cmd: impl Into) -> Self { + pub(crate) fn new(program: OsString) -> Self { + Self { + program: Rc::new(program), + leading_args: Rc::from(&[][..]), + trailing_args: Rc::from(&[][..]), + args: Vec::new(), + features: String::new(), + cwd: None, + env: HashMap::new(), + verbose: false, + } + } + + /// Creates a new `ProcessBuilder` from `Args`. + pub(crate) fn from_args(program: OsString, args: &Args) -> Self { Self { - program: cmd.into(), + program: Rc::new(program), + leading_args: args.leading_args.clone(), + trailing_args: args.trailing_args.clone(), args: Vec::new(), - args2: Vec::new(), + features: String::new(), cwd: None, env: HashMap::new(), + verbose: args.verbose, } } + pub(crate) fn append_features(&mut self, features: impl IntoIterator>) { + for feature in features { + self.features.push_str(feature.as_ref()); + self.features.push(','); + } + } + + /// (chainable) Adds `--features` flag to the args list. + pub(crate) fn features(&mut self, args: &Args, package: &Package) -> &mut Self { + if args.ignore_unknown_features { + self.append_features(args.features.iter().filter(|f| { + if package.features.get(*f).is_some() { + true + } else { + // ignored + info!( + args.color, + "skipped applying unknown `{}` feature to {}", f, package.name + ); + false + } + })) + } else if !args.features.is_empty() { + self.append_features(&args.features); + } + self + } + // /// (chainable) Sets the executable for the process. // pub(crate) fn program(&mut self, program: impl AsRef) -> &mut Self { // self.program = program.as_ref().to_os_string(); @@ -50,11 +105,11 @@ impl ProcessBuilder { self } - /// (chainable) Adds multiple `args` to the args list. - pub(crate) fn args(&mut self, args: &[impl AsRef]) -> &mut Self { - self.args.extend(args.iter().map(|t| t.as_ref().to_os_string())); - self - } + // /// (chainable) Adds multiple `args` to the args list. + // pub(crate) fn args(&mut self, args: &[impl AsRef]) -> &mut Self { + // self.args.extend(args.iter().map(|t| t.as_ref().to_os_string())); + // self + // } // /// (chainable) Replaces the args list with the given `args`. // pub(crate) fn args_replace(&mut self, args: &[impl AsRef]) -> &mut Self { @@ -62,24 +117,6 @@ impl ProcessBuilder { // self // } - // /// (chainable) Adds `arg` to the args2 list. - // pub(crate) fn arg2(&mut self, arg: impl AsRef) -> &mut Self { - // self.args2.push(arg.as_ref().to_os_string()); - // self - // } - - /// (chainable) Adds multiple `args` to the args2 list. - pub(crate) fn args2(&mut self, args: &[impl AsRef]) -> &mut Self { - self.args2.extend(args.iter().map(|t| t.as_ref().to_os_string())); - self - } - - // /// (chainable) Replaces the args2 list with the given `args`. - // pub(crate) fn args2_replace(&mut self, args: &[impl AsRef]) -> &mut Self { - // self.args2 = args.iter().map(|t| t.as_ref().to_os_string()).collect(); - // self - // } - // /// (chainable) Sets the current working directory of the process. // pub(crate) fn cwd(&mut self, path: impl AsRef) -> &mut Self { // self.cwd = Some(path.as_ref().to_os_string()); @@ -167,15 +204,22 @@ impl ProcessBuilder { /// Converts `ProcessBuilder` into a `std::process::Command`, and handles the jobserver, if /// present. fn build_command(&self) -> Command { - let mut command = Command::new(&self.program); + let mut command = Command::new(&*self.program); if let Some(cwd) = self.get_cwd() { command.current_dir(cwd); } + + command.args(&*self.leading_args); command.args(&self.args); - if !self.args2.is_empty() { + if !self.features.is_empty() { + command.arg("--features"); + command.arg(&self.features[..self.features.len() - 1]); + } + if !self.trailing_args.is_empty() { command.arg("--"); - command.args(&self.args2); + command.args(&*self.trailing_args); } + for (k, v) in &self.env { match v { Some(v) => { @@ -194,19 +238,39 @@ impl fmt::Display for ProcessBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "`")?; - write!(f, "{}", Path::new(&self.program).file_stem().unwrap().to_string_lossy())?; + write!(f, "{}", Path::new(&*self.program).file_stem().unwrap().to_string_lossy())?; - for arg in &self.args { - write!(f, " {}", arg.to_string_lossy())?; + for arg in &*self.leading_args { + write!(f, " {}", arg)?; } - if !self.args2.is_empty() { - write!(f, " --")?; - for arg in &self.args2 { + if self.verbose { + for arg in &self.args { + write!(f, " {}", arg.to_string_lossy())?; + } + } else { + let mut args = self.args.iter(); + while let Some(arg) = args.next() { + // Displaying `--target-dir` and `--manifest-path` is redundant. + if arg == "--target-dir" || arg == "--manifest-path" { + let _ = args.next(); + continue; + } write!(f, " {}", arg.to_string_lossy())?; } } + if !self.features.is_empty() { + write!(f, " --features {}", &self.features[..self.features.len() - 1])?; + } + + if !self.trailing_args.is_empty() { + write!(f, " --")?; + for arg in &*self.trailing_args { + write!(f, " {}", arg)?; + } + } + write!(f, "`") } } diff --git a/src/workspace.rs b/src/workspace.rs deleted file mode 100644 index 29ba2586..00000000 --- a/src/workspace.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::path::Path; - -use anyhow::Result; -use indexmap::IndexMap; -use toml_edit::Item as Value; - -use crate::manifest::{find_project_manifest_exact, Manifest}; - -pub(crate) struct Workspace<'a> { - /// This manifest is a manifest to where the current cargo subcommand was - /// invoked from. - pub(crate) current_manifest: &'a Manifest, - - // Map of members in this workspace. Keys are their package names and values - // are their manifests. - pub(crate) members: IndexMap, -} - -impl<'a> Workspace<'a> { - pub(crate) fn new(current_manifest: &'a Manifest) -> Result { - let mut members = IndexMap::new(); - // TODO: The current cargo-hack doesn't try to find the root manifest - // after finding the current package's manifest. - // https://github.com/taiki-e/cargo-hack/issues/11 - let root_dir = current_manifest.dir(); - let mut inserted = false; - - if let Some(workspace) = current_manifest.workspace() { - for mut dir in workspace - .get("members") - .and_then(Value::as_array) - .into_iter() - .flat_map(|v| v.iter().filter_map(|v| v.as_str())) - .map(Path::new) - { - if let Ok(new) = dir.strip_prefix(".") { - dir = new; - } - - let path = find_project_manifest_exact(&root_dir.join(dir))?; - let manifest = Manifest::new(&path)?; - - if current_manifest.path == manifest.path { - inserted = true; - } - members.insert(manifest.package_name().to_string(), manifest); - } - } - - if !current_manifest.is_virtual() && !inserted { - members.insert(current_manifest.package_name().to_string(), current_manifest.clone()); - } - - Ok(Self { current_manifest, members }) - } -} diff --git a/tests/fixtures/windows_not_find_manifest/.cargo/config b/tests/fixtures/package_collision/.cargo/config similarity index 100% rename from tests/fixtures/windows_not_find_manifest/.cargo/config rename to tests/fixtures/package_collision/.cargo/config diff --git a/tests/fixtures/windows_package_collision/Cargo.toml b/tests/fixtures/package_collision/Cargo.toml similarity index 100% rename from tests/fixtures/windows_package_collision/Cargo.toml rename to tests/fixtures/package_collision/Cargo.toml diff --git a/tests/fixtures/windows_package_collision/member1/Cargo.toml b/tests/fixtures/package_collision/member1/Cargo.toml similarity index 100% rename from tests/fixtures/windows_package_collision/member1/Cargo.toml rename to tests/fixtures/package_collision/member1/Cargo.toml diff --git a/tests/fixtures/windows_not_find_manifest/dir/member2/src/main.rs b/tests/fixtures/package_collision/member1/src/main.rs similarity index 100% rename from tests/fixtures/windows_not_find_manifest/dir/member2/src/main.rs rename to tests/fixtures/package_collision/member1/src/main.rs diff --git a/tests/fixtures/windows_not_find_manifest/dir/member2/Cargo.toml b/tests/fixtures/package_collision/member2/Cargo.toml similarity index 100% rename from tests/fixtures/windows_not_find_manifest/dir/member2/Cargo.toml rename to tests/fixtures/package_collision/member2/Cargo.toml diff --git a/tests/fixtures/windows_not_find_manifest/member1/src/main.rs b/tests/fixtures/package_collision/member2/src/main.rs similarity index 100% rename from tests/fixtures/windows_not_find_manifest/member1/src/main.rs rename to tests/fixtures/package_collision/member2/src/main.rs diff --git a/tests/fixtures/real/Cargo.toml b/tests/fixtures/real/Cargo.toml index 1951e3ca..c785720a 100644 --- a/tests/fixtures/real/Cargo.toml +++ b/tests/fixtures/real/Cargo.toml @@ -9,6 +9,7 @@ publish = false members = [ "member1", "member2", + "member3", ".", ] diff --git a/tests/fixtures/windows_not_find_manifest/member1/Cargo.toml b/tests/fixtures/real/member3/Cargo.toml similarity index 76% rename from tests/fixtures/windows_not_find_manifest/member1/Cargo.toml rename to tests/fixtures/real/member3/Cargo.toml index bd3ded4c..9ce5ee5c 100644 --- a/tests/fixtures/windows_not_find_manifest/member1/Cargo.toml +++ b/tests/fixtures/real/member3/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "member1" +name = "member3" version = "0.1.0" authors = ["Taiki Endo "] edition = "2018" +publish = true [dependencies] -# member2 = { path = "../member2" } +member2 = { path = "../member2" } [dev-dependencies] diff --git a/tests/fixtures/windows_package_collision/member1/src/main.rs b/tests/fixtures/real/member3/src/main.rs similarity index 100% rename from tests/fixtures/windows_package_collision/member1/src/main.rs rename to tests/fixtures/real/member3/src/main.rs diff --git a/tests/fixtures/virtual/Cargo.toml b/tests/fixtures/virtual/Cargo.toml index 5a39ea26..17789553 100644 --- a/tests/fixtures/virtual/Cargo.toml +++ b/tests/fixtures/virtual/Cargo.toml @@ -2,4 +2,5 @@ members = [ "member1", "member2", + "dir/not_find_manifest", ] diff --git a/tests/fixtures/windows_package_collision/member2/Cargo.toml b/tests/fixtures/virtual/dir/not_find_manifest/Cargo.toml similarity index 89% rename from tests/fixtures/windows_package_collision/member2/Cargo.toml rename to tests/fixtures/virtual/dir/not_find_manifest/Cargo.toml index 59f10cf7..8d9c1759 100644 --- a/tests/fixtures/windows_package_collision/member2/Cargo.toml +++ b/tests/fixtures/virtual/dir/not_find_manifest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "member2" +name = "not_find_manifest" version = "0.1.0" authors = ["Taiki Endo "] edition = "2018" diff --git a/tests/fixtures/windows_package_collision/member2/src/main.rs b/tests/fixtures/virtual/dir/not_find_manifest/src/main.rs similarity index 100% rename from tests/fixtures/windows_package_collision/member2/src/main.rs rename to tests/fixtures/virtual/dir/not_find_manifest/src/main.rs diff --git a/tests/fixtures/virtual/member2/Cargo.toml b/tests/fixtures/virtual/member2/Cargo.toml index 59f10cf7..21dd6e06 100644 --- a/tests/fixtures/virtual/member2/Cargo.toml +++ b/tests/fixtures/virtual/member2/Cargo.toml @@ -16,3 +16,4 @@ default = ["a"] a = [] b = [] c = [] +f = [] diff --git a/tests/fixtures/windows_not_find_manifest/Cargo.toml b/tests/fixtures/windows_not_find_manifest/Cargo.toml deleted file mode 100644 index 483d0334..00000000 --- a/tests/fixtures/windows_not_find_manifest/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[workspace] -members = [ - "member1", - "dir/member2", -] diff --git a/tests/fixtures/windows_package_collision/.cargo/config b/tests/fixtures/windows_package_collision/.cargo/config deleted file mode 100644 index 88403f3b..00000000 --- a/tests/fixtures/windows_package_collision/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[build] -target-dir = "../../../target" diff --git a/tests/test.rs b/tests/test.rs index cd07d672..c94aa9d9 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -100,11 +100,9 @@ fn test_real() { .assert_success() .assert_stderr_not_contains("running `cargo check` on member1") .assert_stderr_not_contains("running `cargo check` on member2") + .assert_stderr_not_contains("running `cargo check` on member3") .assert_stderr_contains("running `cargo check` on real"); -} -#[test] -fn test_real_all() { let output = cargo_hack() .args(&["hack", "check", "--all"]) .current_dir(test_dir("tests/fixtures/real")) @@ -115,50 +113,80 @@ fn test_real_all() { .assert_success() .assert_stderr_contains("running `cargo check` on member1") .assert_stderr_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on member3") .assert_stderr_contains("running `cargo check` on real"); } #[test] -fn test_real_ignore_private() { +fn test_virtual() { let output = cargo_hack() - .args(&["hack", "check", "--ignore-private"]) - .current_dir(test_dir("tests/fixtures/real")) + .args(&["hack", "check"]) + .current_dir(test_dir("tests/fixtures/virtual")) .output() .unwrap(); output .assert_success() - .assert_stderr_not_contains("running `cargo check` on member1") - .assert_stderr_not_contains("running `cargo check` on member2") - .assert_stderr_not_contains("running `cargo check` on real") - .assert_stderr_not_contains("skipped running on member1") - .assert_stderr_not_contains("skipped running on member2") - .assert_stderr_contains("skipped running on real"); + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2"); + + let output = cargo_hack() + .args(&["hack", "check", "--all"]) + .current_dir(test_dir("tests/fixtures/virtual")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2"); } #[test] -fn test_real_ignore_private_all() { +fn test_real_all_in_subcrate() { let output = cargo_hack() - .args(&["hack", "check", "--all", "--ignore-private"]) - .current_dir(test_dir("tests/fixtures/real")) + .args(&["hack", "check"]) + .current_dir(test_dir("tests/fixtures/real/member2")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_not_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2") + .assert_stderr_not_contains("running `cargo check` on member3") + .assert_stderr_not_contains("running `cargo check` on real"); + + let output = cargo_hack() + .args(&["hack", "check", "--all"]) + .current_dir(test_dir("tests/fixtures/real/member2")) .output() .unwrap(); output .assert_success() .assert_stderr_contains("running `cargo check` on member1") - .assert_stderr_not_contains("running `cargo check` on member2") - .assert_stderr_not_contains("running `cargo check` on real") - .assert_stderr_not_contains("skipped running on member1") - .assert_stderr_contains("skipped running on member2") - .assert_stderr_contains("skipped running on real"); + .assert_stderr_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on member3") + .assert_stderr_contains("running `cargo check` on real"); } #[test] -fn test_virtual() { +fn test_virtual_all_in_subcrate() { let output = cargo_hack() .args(&["hack", "check"]) - .current_dir(test_dir("tests/fixtures/virtual")) + .current_dir(test_dir("tests/fixtures/virtual/member1")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_not_contains("running `cargo check` on member2"); + + let output = cargo_hack() + .args(&["hack", "check", "--all"]) + .current_dir(test_dir("tests/fixtures/virtual/member1")) .output() .unwrap(); @@ -169,17 +197,38 @@ fn test_virtual() { } #[test] -fn test_virtual_all() { +fn test_real_ignore_private() { let output = cargo_hack() - .args(&["hack", "check", "--all"]) - .current_dir(test_dir("tests/fixtures/virtual")) + .args(&["hack", "check", "--ignore-private"]) + .current_dir(test_dir("tests/fixtures/real")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_not_contains("running `cargo check` on member1") + .assert_stderr_not_contains("skipped running on member1") + .assert_stderr_not_contains("running `cargo check` on member2") + .assert_stderr_not_contains("skipped running on member2") + .assert_stderr_not_contains("running `cargo check` on real") + .assert_stderr_contains("skipped running on real"); + + let output = cargo_hack() + .args(&["hack", "check", "--all", "--ignore-private"]) + .current_dir(test_dir("tests/fixtures/real")) .output() .unwrap(); output .assert_success() .assert_stderr_contains("running `cargo check` on member1") - .assert_stderr_contains("running `cargo check` on member2"); + .assert_stderr_not_contains("skipped running on member1") + .assert_stderr_not_contains("running `cargo check` on member2") + .assert_stderr_contains("skipped running on member2") + .assert_stderr_contains("running `cargo check` on member3") + .assert_stderr_not_contains("skipped running on member3") + .assert_stderr_not_contains("running `cargo check` on real") + .assert_stderr_contains("skipped running on real"); } #[test] @@ -196,10 +245,7 @@ fn test_virtual_ignore_private() { .assert_stderr_not_contains("running `cargo check` on member2") .assert_stderr_not_contains("skipped running on member1") .assert_stderr_contains("skipped running on member2"); -} -#[test] -fn test_virtual_ignore_private_all() { let output = cargo_hack() .args(&["hack", "check", "--all", "--ignore-private"]) .current_dir(test_dir("tests/fixtures/virtual")) @@ -271,7 +317,7 @@ fn test_exclude_not_found() { } #[test] -fn test_exclude_not_all() { +fn test_exclude_not_with_all() { let output = cargo_hack() .args(&["hack", "check", "--exclude", "member1"]) .current_dir(test_dir("tests/fixtures/virtual")) @@ -362,7 +408,14 @@ fn test_no_dev_deps_with_devs() { #[test] fn test_ignore_unknown_features() { let output = cargo_hack() - .args(&["hack", "check", "--ignore-unknown-features", "--features=f"]) + .args(&[ + "hack", + "check", + "--ignore-unknown-features", + "--no-default-features", + "--features", + "f", + ]) .current_dir(test_dir("tests/fixtures/virtual")) .output() .unwrap(); @@ -370,7 +423,11 @@ fn test_ignore_unknown_features() { output .assert_success() .assert_stderr_contains("skipped applying unknown `f` feature to member1") - .assert_stderr_contains("skipped applying unknown `f` feature to member2"); + .assert_stderr_contains("running `cargo check --no-default-features` on member1") + .assert_stderr_not_contains("skipped applying unknown `f` feature to member2") + .assert_stderr_contains( + "running `cargo check --no-default-features --features f` on member2", + ); } #[test] @@ -385,7 +442,9 @@ fn test_ignore_non_exist_features() { .assert_success() .assert_stderr_contains("'--ignore-non-exist-features' flag is deprecated, use '--ignore-unknown-features' flag instead") .assert_stderr_contains("skipped applying unknown `f` feature to member1") - .assert_stderr_contains("skipped applying unknown `f` feature to member2"); + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_not_contains("skipped applying unknown `f` feature to member2") + .assert_stderr_contains("running `cargo check --features f` on member2"); } #[test] @@ -400,9 +459,32 @@ fn test_each_feature() { .assert_success() .assert_stderr_contains("running `cargo check` on real") .assert_stderr_contains("running `cargo check --no-default-features` on real") - .assert_stderr_contains("running `cargo check --features=a --no-default-features` on real") - .assert_stderr_contains("running `cargo check --features=b --no-default-features` on real") - .assert_stderr_contains("running `cargo check --features=c --no-default-features` on real"); + .assert_stderr_contains("running `cargo check --no-default-features --features a` on real") + .assert_stderr_contains("running `cargo check --no-default-features --features b` on real") + .assert_stderr_contains("running `cargo check --no-default-features --features c` on real"); +} + +#[test] +fn test_each_feature2() { + let output = cargo_hack() + .args(&["hack", "check", "--each-feature", "--features=a"]) + .current_dir(test_dir("tests/fixtures/real")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_contains("running `cargo check --features a` on real") + .assert_stderr_contains("running `cargo check --no-default-features --features a` on real") + .assert_stderr_contains( + "running `cargo check --no-default-features --features a,a` on real", + ) + .assert_stderr_contains( + "running `cargo check --no-default-features --features a,b` on real", + ) + .assert_stderr_contains( + "running `cargo check --no-default-features --features a,c` on real", + ); } #[test] @@ -421,39 +503,66 @@ fn test_args2() { } #[test] -fn windows_package_collision() { +fn test_package_collision() { let output = cargo_hack() .args(&["hack", "check"]) - .current_dir(test_dir("tests/fixtures/windows_package_collision")) + .current_dir(test_dir("tests/fixtures/package_collision")) .output() .unwrap(); - if cfg!(windows) { - output - .assert_failure() - .assert_stderr_contains("package collision in the lockfile: packages member2"); - } else { - output - .assert_success() - .assert_stderr_contains("running `cargo check` on member1") - .assert_stderr_contains("running `cargo check` on member2"); - } + output + .assert_success() + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2"); } #[test] -fn windows_not_find_manifest() { +fn test_not_find_manifest() { let output = cargo_hack() .args(&["hack", "check"]) - .current_dir(test_dir("tests/fixtures/windows_not_find_manifest")) + .current_dir(test_dir("tests/fixtures/virtual/dir/not_find_manifest")) .output() .unwrap(); - if cfg!(windows) { - output.assert_failure().assert_stderr_contains("Could not find `Cargo.toml` in `"); - } else { - output - .assert_success() - .assert_stderr_contains("running `cargo check` on member1") - .assert_stderr_contains("running `cargo check` on member2"); - } + output + .assert_success() + .assert_stderr_not_contains("running `cargo check` on member1") + .assert_stderr_not_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on not_find_manifest"); + + let output = cargo_hack() + .args(&["hack", "check", "--all"]) + .current_dir(test_dir("tests/fixtures/virtual/dir/not_find_manifest")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on not_find_manifest"); + + let output = cargo_hack() + .args(&["hack", "check", "--manifest-path", "dir/not_find_manifest/Cargo.toml"]) + .current_dir(test_dir("tests/fixtures/virtual")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_not_contains("running `cargo check` on member1") + .assert_stderr_not_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on not_find_manifest"); + + let output = cargo_hack() + .args(&["hack", "check", "--all", "--manifest-path", "dir/not_find_manifest/Cargo.toml"]) + .current_dir(test_dir("tests/fixtures/virtual")) + .output() + .unwrap(); + + output + .assert_success() + .assert_stderr_contains("running `cargo check` on member1") + .assert_stderr_contains("running `cargo check` on member2") + .assert_stderr_contains("running `cargo check` on not_find_manifest"); }