Skip to content

Commit

Permalink
Print the total number of feature flag combinations and progress
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Apr 23, 2020
1 parent 785ab52 commit 679a71b
Show file tree
Hide file tree
Showing 5 changed files with 402 additions and 389 deletions.
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ pub(crate) fn args(coloring: &mut Option<Coloring>) -> Result<Option<Args>> {
if no_dev_deps && remove_dev_deps {
bail!("--no-dev-deps may not be used together with --remove-dev-deps");
}
if each_feature && feature_powerset {
bail!("--each-feature may not be used together with --feature-powerset");
}

if subcommand.is_none() {
if leading.iter().any(|a| a == "--list") {
Expand Down
115 changes: 115 additions & 0 deletions src/features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::borrow::Borrow;

use super::*;

pub(crate) fn features(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> {
Features { args, package, line: line.clone(), total: 1, count: 0 }.run()
}

struct Features<'a> {
args: &'a Args,
package: &'a Package,
line: ProcessBuilder,
total: usize,
count: usize,
}

impl Features<'_> {
fn run(&mut self) -> Result<()> {
if (!self.args.each_feature && !self.args.feature_powerset)
|| self.package.features.is_empty()
{
// run with default features
return self.exec_cargo(None, self.args.each_feature || self.args.feature_powerset);
}

self.total += 1;
let pre: fn(&mut Self) -> Result<()> = |this| {
// run with default features
this.exec_cargo(None, true)?;

this.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.
this.exec_cargo(None, true)
};

if self.args.each_feature {
self.each_feature(pre)
} else if self.args.feature_powerset {
self.feature_powerset(pre)
} else {
unreachable!()
}
}

fn each_feature(&mut self, pre: fn(&mut Self) -> Result<()>) -> Result<()> {
let features: Vec<_> = self
.package
.features
.keys()
.filter(|k| (*k != "default" && !self.args.skip.contains(k)))
.collect();
self.total += features.len();

pre(self)?;

features.into_iter().try_for_each(|f| self.exec_cargo_with_features(Some(f)))
}

fn feature_powerset(&mut self, pre: fn(&mut Self) -> Result<()>) -> Result<()> {
let powerset: Vec<Vec<&String>> = powerset(
self.package
.features
.keys()
.filter(|k| (*k != "default" && !self.args.skip.contains(k))),
);
self.total += powerset.len() - 1;

pre(self)?;

// The first element of a powerset is `[]` so it should be skipped.
powerset.into_iter().skip(1).try_for_each(|f| self.exec_cargo_with_features(f))
}

fn exec_cargo(&mut self, line: Option<&ProcessBuilder>, show_progress: bool) -> Result<()> {
let line = line.unwrap_or(&self.line);
self.count += 1;

if show_progress {
// <package>: running `<command>` (<count>/<total>)
info!(
self.args.color,
"{}: running {} ({}/{})", self.package.name, line, self.count, self.total,
);
} else {
// <package>: running `<command>`
info!(self.args.color, "{}: running {}", self.package.name, line,);
}

line.exec()
}

fn exec_cargo_with_features(
&mut self,
features: impl IntoIterator<Item = impl AsRef<str>>,
) -> Result<()> {
let mut line = self.line.clone();
line.append_features(features);
self.exec_cargo(Some(&line), true)
}
}

fn powerset<T: Clone>(s: impl Iterator<Item = impl Borrow<T>>) -> Vec<Vec<T>> {
s.fold(vec![vec![]], |mut acc, elem| {
let ext = acc.clone().into_iter().map(|mut curr| {
curr.push(elem.borrow().clone());
curr
});
acc.extend(ext);
acc
})
}
72 changes: 3 additions & 69 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
mod term;

mod cli;
mod features;
mod manifest;
mod metadata;
mod process;
Expand Down Expand Up @@ -140,85 +141,18 @@ fn no_dev_deps(
})?;

if args.subcommand.is_some() {
features(args, package, line)?;
features::features(args, package, line)?;
}

handle.done()?;
} else if args.subcommand.is_some() {
features(args, package, line)?;
features::features(args, package, line)?;
}

Ok(())
}

fn features(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> {
// run with default features
exec_cargo(args, package, line)?;

if (!args.each_feature && !args.feature_powerset) || 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(args, package, &line)?;

if args.each_feature {
each_feature(args, package, &line)
} else if args.feature_powerset {
feature_powerset(args, package, &line)
} else {
Ok(())
}
}

fn each_feature(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> {
package
.features
.iter()
.filter(|(k, _)| (*k != "default" && !args.skip.contains(k)))
.try_for_each(|(feature, _)| {
let mut line = line.clone();
line.append_features(&[feature]);
exec_cargo(args, package, &line)
})
}

fn feature_powerset(args: &Args, package: &Package, line: &ProcessBuilder) -> Result<()> {
let features: Vec<&String> =
package.features.keys().filter(|k| (*k != "default" && !args.skip.contains(k))).collect();
let powerset = powerset(&features);

// The first element of a powerset is `[]` so it should be skipped.
powerset.into_iter().skip(1).try_for_each(|features| {
let mut line = line.clone();
line.append_features(features);
exec_cargo(args, package, &line)
})
}

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 {
env::var_os("CARGO_HACK_CARGO_SRC")
.unwrap_or_else(|| env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")))
}

fn powerset<T: Clone>(s: &[T]) -> Vec<Vec<T>> {
s.iter().fold(vec![vec![]], |mut acc, elem| {
let ext = acc.clone().into_iter().map(|mut curr| {
curr.push(elem.clone());
curr
});
acc.extend(ext);
acc
})
}
26 changes: 14 additions & 12 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,20 @@ impl ProcessBuilder {
/// (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
}
}))
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", package.name, f,
);
false
}
}),
)
} else if !args.features.is_empty() {
self.append_features(&args.features);
}
Expand Down
Loading

0 comments on commit 679a71b

Please sign in to comment.