From 131de911f4c891f376effed21a9c2b191107bd5e Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Mon, 17 Oct 2016 07:54:59 +0100 Subject: [PATCH 1/6] Initial work on supporting workspace in multi. --- Cargo.lock | 33 ++++++++++++++++++++ Cargo.toml | 6 ++-- src/main.rs | 87 +++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 104 insertions(+), 22 deletions(-) mode change 100644 => 100755 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6b38ef3..6e51102 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ dependencies = [ "clap 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.76 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -118,6 +119,14 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-normalization" version = "0.1.2" @@ -152,3 +161,27 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[metadata] +"checksum ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1f46cd5b1d660c938e3f92dfe7a73d832b3281479363dd0cd9c1c2fbf60f7962" +"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" +"checksum clap 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a488faa74c761a4f817e330e69c70a3b2e8b8de5915a54f17d5b326831edce7b" +"checksum clippy 0.0.76 (registry+https://github.com/rust-lang/crates.io-index)" = "7130cfdc84f3d31fab7fe87d690ff038146733b061832279f9b53484f15b7d13" +"checksum clippy_lints 0.0.76 (registry+https://github.com/rust-lang/crates.io-index)" = "d8af9a676d2e986fb169d76db46e29f1e1d6959d1b6904a984fa9c8a0a352150" +"checksum itertools 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ac6e56e7cfd710efcf4c4f614bd101794845d9fe5f406b87ac5108b9153d033f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "97def9dc7ce1d8e153e693e3a33020bc69972181adb2f871e87e888876feae49" +"checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" +"checksum nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d1b06a35295796400a1db7382054f93713bf3924e7c268af94c5357b9fbf4cb6" +"checksum quine-mc_cluskey 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6683b0e23d80813b1a535841f0048c1537d3f86d63c999e8373b39a9b0eb74a" +"checksum regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "baa04823ba7be7ed0bed3d0704c7b923019d9c4e4931c5af2804c7c7a0e3d00b" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d5b7638a1f03815d94e88cb3b3c08e87f0db4d683ef499d1836aaf70a45623f" +"checksum strsim 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0d5f575d5ced6634a5c4cb842163dab907dc7e9148b28dc482d81b8855cbe985" +"checksum toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "0590d72182e50e879c4da3b11c6488dae18fccb1ae0c7a3eda18e16795844796" +"checksum toml 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" +"checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" +"checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e" +"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" +"checksum walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad450634b9022aeb0e8e7f1c79c1ded92d0fc5bee831033d148479771bd218d" +"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index d466901..65acc5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [package] -authors = ["Cyril Plisko ", - "Alexander Goldberg "] +authors = ["Cyril Plisko ", "Alexander Goldberg "] description = "Extends cargo to run the given command on multiple crates similar to 'git multi'" homepage = "https://github.com/imp/cargo-multi" keywords = ["cargo", "subcommand", "multi"] @@ -12,11 +11,12 @@ version = "0.4.5" [dependencies] clap = "2.6" itertools = "0.4" +toml = "0.2.1" walkdir = "0.1" [dependencies.clippy] -version = "0.0.*" optional = true +version = "0.0.*" [features] default = [] diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 index ba9d688..4e6b025 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,13 @@ extern crate clap; extern crate itertools; extern crate walkdir; +extern crate toml; use std::env; -use std::process::{Command, Output}; +use std::fs::File; +use std::io::Read; +use std::process::{exit, Command, Output}; use clap::{App, SubCommand, AppSettings}; -use itertools::Itertools; use walkdir::{DirEntry, WalkDirIterator}; @@ -27,12 +29,13 @@ fn print_ident(buf: Vec) { } } -fn report_output(output: Output) { +fn report_output(output: Output) -> std::process::ExitStatus { if output.status.success() { print_ident(output.stdout); } else { print_ident(output.stderr); - } + }; + // I am still not sure what is more idiomatic - the 'if' above or the 'match' below // // match output.status.success() { @@ -40,6 +43,8 @@ fn report_output(output: Output) { // false => print_ident(output.stderr), // } println!(""); + + output.status } const CARGO: &'static str = "cargo"; @@ -72,20 +77,64 @@ fn main() { } } - let is_crate = |e: &DirEntry| e.path().join("Cargo.toml").exists(); - let display_path = |e: &DirEntry| println!("{}:", e.file_name().to_string_lossy()); - let execute = |e: DirEntry| cargo_cmd.current_dir(e.path()).output().ok(); - - if let Ok(cwd) = env::current_dir() { - announce(&banner); - walkdir::WalkDir::new(cwd) - .min_depth(MIN_DEPTH) - .max_depth(MAX_DEPTH) - .into_iter() - .filter_entry(is_crate) - .filter_map(|e| e.ok()) - .inspect(display_path) - .filter_map(execute) - .foreach(report_output); + announce(&banner); + let built_workspace = match File::open("Cargo.toml") { + Ok(mut file) => { + let mut toml = String::new(); + match file.read_to_string(&mut toml) { + Ok(_) => { + let value: toml::Value = toml.parse().expect("Failed to parse Cargo.toml"); + match value.lookup("workspace.members") { + Some(member_values) => { + let failed_commands = member_values + .as_slice() + .unwrap() + .iter() + .map(|ref x| x.as_str().unwrap()) + .inspect(|x| println!("{}:", x)) + .filter_map(|x| cargo_cmd.current_dir(x).output().ok()) + .map(report_output) + .filter(|x| !x.success()) + .collect::>(); + + // If there are any failed commands, return the error code of the + // first of them. + if failed_commands.len() > 0 { + exit(failed_commands[0].code().unwrap()); + } + true + } + None => false, + } + } + Err(_) => false, + } + } + Err(_) => false, + }; + + if !built_workspace { + let is_crate = |e: &DirEntry| e.path().join("Cargo.toml").exists(); + let display_path = |e: &DirEntry| println!("{}:", e.file_name().to_string_lossy()); + let execute = |e: DirEntry| cargo_cmd.current_dir(e.path()).output().ok(); + if let Ok(cwd) = env::current_dir() { + let failed_commands = walkdir::WalkDir::new(cwd) + .min_depth(MIN_DEPTH) + .max_depth(MAX_DEPTH) + .into_iter() + .filter_entry(is_crate) + .filter_map(|e| e.ok()) + .inspect(display_path) + .filter_map(execute) + .map(report_output) + .filter(|x| !x.success()) + .collect::>(); + + // If there are any failed commands, return the error code of the + // first of them. + if failed_commands.len() > 0 { + exit(failed_commands[0].code().unwrap()); + } + } } } From 5a3e9d713336259760ea6d6a5e31896c12c5f7f6 Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Tue, 18 Oct 2016 05:57:18 +0100 Subject: [PATCH 2/6] Moving to use common code for workspace and individual crate directories. --- src/main.rs | 89 +++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4e6b025..fae0aee 100755 --- a/src/main.rs +++ b/src/main.rs @@ -78,63 +78,64 @@ fn main() { } announce(&banner); - let built_workspace = match File::open("Cargo.toml") { + let is_crate = |e: &DirEntry| e.path().join("Cargo.toml").exists(); + let display_path = |p: &String| println!("{}:", p); + let execute = |p: String| cargo_cmd.current_dir(p).output().ok(); + + let mut workspace_members = match File::open("Cargo.toml") { Ok(mut file) => { let mut toml = String::new(); match file.read_to_string(&mut toml) { Ok(_) => { let value: toml::Value = toml.parse().expect("Failed to parse Cargo.toml"); + match value.lookup("workspace.members") { - Some(member_values) => { - let failed_commands = member_values - .as_slice() - .unwrap() - .iter() - .map(|ref x| x.as_str().unwrap()) - .inspect(|x| println!("{}:", x)) - .filter_map(|x| cargo_cmd.current_dir(x).output().ok()) - .map(report_output) - .filter(|x| !x.success()) - .collect::>(); - - // If there are any failed commands, return the error code of the - // first of them. - if failed_commands.len() > 0 { - exit(failed_commands[0].code().unwrap()); - } - true + Some(members) => { + Some(members.as_slice() + .expect("Failed to read workspace members") + .into_iter() + .map(|m| m.as_str().unwrap().to_string()) + .collect::>()) } - None => false, + None => None, } } - Err(_) => false, + Err(_) => None, } } - Err(_) => false, + Err(_) => None, }; - - if !built_workspace { - let is_crate = |e: &DirEntry| e.path().join("Cargo.toml").exists(); - let display_path = |e: &DirEntry| println!("{}:", e.file_name().to_string_lossy()); - let execute = |e: DirEntry| cargo_cmd.current_dir(e.path()).output().ok(); - if let Ok(cwd) = env::current_dir() { - let failed_commands = walkdir::WalkDir::new(cwd) - .min_depth(MIN_DEPTH) - .max_depth(MAX_DEPTH) - .into_iter() - .filter_entry(is_crate) - .filter_map(|e| e.ok()) - .inspect(display_path) - .filter_map(execute) - .map(report_output) - .filter(|x| !x.success()) - .collect::>(); - - // If there are any failed commands, return the error code of the - // first of them. - if failed_commands.len() > 0 { - exit(failed_commands[0].code().unwrap()); +/* + if workspace_members.is_none() { + workspace_members = match env::current_dir() { + Ok(cwd) => { + Some(walkdir::WalkDir::new(cwd) + .min_depth(MIN_DEPTH) + .max_depth(MAX_DEPTH) + .into_iter() + .filter_entry(is_crate) + .filter_map(|e| e.ok()) + .map(|m| m.file_name().to_string_lossy())) } + Err(_) => None, } } +*/ + let failed_commands = match workspace_members { + Some(members) => { + members.into_iter() + .inspect(display_path) + .filter_map(execute) + .map(report_output) + .filter(|x| !x.success()) + .collect::>() + } + None => Vec::new(), + }; + + // If there are any failed commands, return the error code of the + // first of them. + if failed_commands.len() > 0 { + exit(failed_commands[0].code().unwrap()); + } } From 78c93e5fafc9f6f84af220093b3e7c730efdefff Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Tue, 18 Oct 2016 06:02:05 +0100 Subject: [PATCH 3/6] Add back in support for multi in crate directories. --- src/main.rs | 62 ++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) mode change 100755 => 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs old mode 100755 new mode 100644 index fae0aee..b3df7a8 --- a/src/main.rs +++ b/src/main.rs @@ -54,23 +54,23 @@ const MAX_DEPTH: usize = 1; fn main() { let matches = App::new(CARGO) - .bin_name(CARGO) - .version(crate_version!()) - .about("Run cargo command on multiple crates") - .setting(AppSettings::SubcommandRequired) - .setting(AppSettings::ArgRequiredElseHelp) - .subcommand(SubCommand::with_name("multi") - .version(crate_version!()) - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::TrailingVarArg) - .arg_from_usage("... 'cargo command to run'")) - .get_matches(); + .bin_name(CARGO) + .version(crate_version!()) + .about("Run cargo command on multiple crates") + .setting(AppSettings::SubcommandRequired) + .setting(AppSettings::ArgRequiredElseHelp) + .subcommand(SubCommand::with_name("multi") + .version(crate_version!()) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::TrailingVarArg) + .arg_from_usage("... 'cargo command to run'")) + .get_matches(); let mut cargo_cmd = Command::new(CARGO); let mut banner = String::from("Executing ") + CARGO; if let Some(arg_cmd) = matches.subcommand_matches("multi") - .and_then(|m| m.values_of("cmd")) { + .and_then(|m| m.values_of("cmd")) { for arg in arg_cmd { cargo_cmd.arg(arg); banner = banner + " " + arg; @@ -82,6 +82,7 @@ fn main() { let display_path = |p: &String| println!("{}:", p); let execute = |p: String| cargo_cmd.current_dir(p).output().ok(); + // First check if there is a Cargo.toml file with a workspace section in. let mut workspace_members = match File::open("Cargo.toml") { Ok(mut file) => { let mut toml = String::new(); @@ -92,10 +93,10 @@ fn main() { match value.lookup("workspace.members") { Some(members) => { Some(members.as_slice() - .expect("Failed to read workspace members") - .into_iter() - .map(|m| m.as_str().unwrap().to_string()) - .collect::>()) + .expect("Failed to read workspace members") + .into_iter() + .map(|m| m.as_str().unwrap().to_string()) + .collect::>()) } None => None, } @@ -105,30 +106,33 @@ fn main() { } Err(_) => None, }; -/* + + // If there was no workspace members present, add each crate directory + // present. if workspace_members.is_none() { workspace_members = match env::current_dir() { Ok(cwd) => { Some(walkdir::WalkDir::new(cwd) - .min_depth(MIN_DEPTH) - .max_depth(MAX_DEPTH) - .into_iter() - .filter_entry(is_crate) - .filter_map(|e| e.ok()) - .map(|m| m.file_name().to_string_lossy())) + .min_depth(MIN_DEPTH) + .max_depth(MAX_DEPTH) + .into_iter() + .filter_entry(is_crate) + .filter_map(|e| e.ok()) + .map(|m| m.file_name().to_string_lossy().to_string()) + .collect::>()) } Err(_) => None, } } -*/ + let failed_commands = match workspace_members { Some(members) => { members.into_iter() - .inspect(display_path) - .filter_map(execute) - .map(report_output) - .filter(|x| !x.success()) - .collect::>() + .inspect(display_path) + .filter_map(execute) + .map(report_output) + .filter(|x| !x.success()) + .collect::>() } None => Vec::new(), }; From 576751994da3410321a3b465e0d8e3c37b94b6e0 Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Wed, 19 Oct 2016 05:16:38 +0100 Subject: [PATCH 4/6] Switch to using PathBuf instead of a string. --- src/main.rs | 61 +++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 30 deletions(-) mode change 100644 => 100755 src/main.rs diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 index b3df7a8..20d1cd7 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ extern crate toml; use std::env; use std::fs::File; use std::io::Read; +use std::path::PathBuf; use std::process::{exit, Command, Output}; use clap::{App, SubCommand, AppSettings}; use walkdir::{DirEntry, WalkDirIterator}; @@ -54,23 +55,23 @@ const MAX_DEPTH: usize = 1; fn main() { let matches = App::new(CARGO) - .bin_name(CARGO) - .version(crate_version!()) - .about("Run cargo command on multiple crates") - .setting(AppSettings::SubcommandRequired) - .setting(AppSettings::ArgRequiredElseHelp) - .subcommand(SubCommand::with_name("multi") - .version(crate_version!()) - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::TrailingVarArg) - .arg_from_usage("... 'cargo command to run'")) - .get_matches(); + .bin_name(CARGO) + .version(crate_version!()) + .about("Run cargo command on multiple crates") + .setting(AppSettings::SubcommandRequired) + .setting(AppSettings::ArgRequiredElseHelp) + .subcommand(SubCommand::with_name("multi") + .version(crate_version!()) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::TrailingVarArg) + .arg_from_usage("... 'cargo command to run'")) + .get_matches(); let mut cargo_cmd = Command::new(CARGO); let mut banner = String::from("Executing ") + CARGO; if let Some(arg_cmd) = matches.subcommand_matches("multi") - .and_then(|m| m.values_of("cmd")) { + .and_then(|m| m.values_of("cmd")) { for arg in arg_cmd { cargo_cmd.arg(arg); banner = banner + " " + arg; @@ -79,8 +80,8 @@ fn main() { announce(&banner); let is_crate = |e: &DirEntry| e.path().join("Cargo.toml").exists(); - let display_path = |p: &String| println!("{}:", p); - let execute = |p: String| cargo_cmd.current_dir(p).output().ok(); + let display_path = |p: &PathBuf| println!("{}:", p.to_string_lossy()); + let execute = |p: PathBuf| cargo_cmd.current_dir(p).output().ok(); // First check if there is a Cargo.toml file with a workspace section in. let mut workspace_members = match File::open("Cargo.toml") { @@ -93,10 +94,10 @@ fn main() { match value.lookup("workspace.members") { Some(members) => { Some(members.as_slice() - .expect("Failed to read workspace members") - .into_iter() - .map(|m| m.as_str().unwrap().to_string()) - .collect::>()) + .expect("Failed to read workspace members") + .into_iter() + .map(|m| PathBuf::from(m.as_str().unwrap())) + .collect::>()) } None => None, } @@ -113,13 +114,13 @@ fn main() { workspace_members = match env::current_dir() { Ok(cwd) => { Some(walkdir::WalkDir::new(cwd) - .min_depth(MIN_DEPTH) - .max_depth(MAX_DEPTH) - .into_iter() - .filter_entry(is_crate) - .filter_map(|e| e.ok()) - .map(|m| m.file_name().to_string_lossy().to_string()) - .collect::>()) + .min_depth(MIN_DEPTH) + .max_depth(MAX_DEPTH) + .into_iter() + .filter_entry(is_crate) + .filter_map(|e| e.ok()) + .map(|m| m.path().to_path_buf()) + .collect::>()) } Err(_) => None, } @@ -128,11 +129,11 @@ fn main() { let failed_commands = match workspace_members { Some(members) => { members.into_iter() - .inspect(display_path) - .filter_map(execute) - .map(report_output) - .filter(|x| !x.success()) - .collect::>() + .inspect(display_path) + .filter_map(execute) + .map(report_output) + .filter(|x| !x.success()) + .collect::>() } None => Vec::new(), }; From f5e676b22f54852c7ba37b711618d94af1089019 Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Wed, 19 Oct 2016 05:19:36 +0100 Subject: [PATCH 5/6] Revert to origin indentation. --- src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 20d1cd7..709c7b7 100755 --- a/src/main.rs +++ b/src/main.rs @@ -61,10 +61,10 @@ fn main() { .setting(AppSettings::SubcommandRequired) .setting(AppSettings::ArgRequiredElseHelp) .subcommand(SubCommand::with_name("multi") - .version(crate_version!()) - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::TrailingVarArg) - .arg_from_usage("... 'cargo command to run'")) + .version(crate_version!()) + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::TrailingVarArg) + .arg_from_usage("... 'cargo command to run'")) .get_matches(); let mut cargo_cmd = Command::new(CARGO); From ab20f2bce2ad79f1de8f94a368dca8866dc06bff Mon Sep 17 00:00:00 2001 From: Chris Swindle Date: Wed, 19 Oct 2016 10:30:00 +0100 Subject: [PATCH 6/6] Ensure that stderr is always reported so that warnings are visible from builds. --- src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 709c7b7..fbf7b53 100755 --- a/src/main.rs +++ b/src/main.rs @@ -33,9 +33,10 @@ fn print_ident(buf: Vec) { fn report_output(output: Output) -> std::process::ExitStatus { if output.status.success() { print_ident(output.stdout); - } else { - print_ident(output.stderr); - }; + } + + // Always print stderr as warnings from cargo are sent to stderr. + print_ident(output.stderr); // I am still not sure what is more idiomatic - the 'if' above or the 'match' below //