Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/.cspell/rust-dependencies.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pkg-fmt = "tgz"
anyhow = "1.0.47"
cargo-config2 = "0.1.13"
ctrlc = { version = "3.4.4", features = ["termination"] }
fastrand = "2.3.0"
lexopt = "0.3"
same-file = "1.0.1"
serde_json = "1"
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ OPTIONS:
--include-features and there are multiple features, this also includes runs with just
--all-features flag.

--randomize-powerset <seed>
Randomize order of powerset elements..

Run feature powerset in random order with the specified seed.

Zero seed value means unseeded.

--optional-deps [DEPS]...
Use optional dependencies as features.

Expand Down
16 changes: 16 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ pub(crate) struct Args {
// propagated to cargo (as a part of leading_args)
/// --no-default-features
pub(crate) no_default_features: bool,

/// --randomize-powerset
pub(crate) randomize_powerset: Option<u64>,
}

impl Args {
Expand Down Expand Up @@ -177,6 +180,8 @@ impl Args {
let mut exclude_no_default_features = false;
let mut exclude_all_features = false;

let mut randomize_powerset = None;

let mut group_features: Vec<String> = vec![];
let mut mutually_exclusive_features: Vec<String> = vec![];
let mut depth = None;
Expand Down Expand Up @@ -303,6 +308,7 @@ impl Args {
Long("remove-dev-deps") => parse_flag!(remove_dev_deps),
Long("each-feature") => parse_flag!(each_feature),
Long("feature-powerset") => parse_flag!(feature_powerset),
Long("randomize-powerset") => parse_opt!(randomize_powerset, false),
Long("at-least-one-of") => at_least_one_of.push(parser.value()?.parse()?),
Long("no-private") => parse_flag!(no_private),
Long("ignore-private") => parse_flag!(ignore_private),
Expand Down Expand Up @@ -487,6 +493,10 @@ impl Args {
if each_feature && feature_powerset {
conflicts("--each-feature", "--feature-powerset")?;
}

let randomize_powerset =
randomize_powerset.as_deref().map(str::parse::<u64>).transpose()?;

if all_features {
if each_feature {
conflicts("--all-features", "--each-feature")?;
Expand Down Expand Up @@ -644,6 +654,8 @@ impl Args {

no_default_features,
target: target.into_iter().collect(),

randomize_powerset,
})
}
}
Expand Down Expand Up @@ -695,6 +707,10 @@ const HELP: &[HelpText<'_>] = &[
--include-features and there are multiple features, this also includes runs with just \
--all-features flag."
]),
("", "--randomize-powerset", "<seed>", "Randomize order of powerset elements.", &[
"Run feature powerset in random order with the specified seed.",
"Zero seed value means unseeded."
]),
("", "--optional-deps", "[DEPS]...", "Use optional dependencies as features", &[
"If DEPS are not specified, all optional dependencies are considered as features.",
"This flag can only be used together with either --each-feature flag or --feature-powerset \
Expand Down
30 changes: 19 additions & 11 deletions src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,12 @@ pub(crate) fn feature_powerset<'a>(
at_least_one_of: &[Feature],
mutually_exclusive_features: &[Feature],
package_features: &BTreeMap<String, Vec<String>>,
randomize: Option<u64>,
) -> Vec<Vec<&'a Feature>> {
let deps_map = feature_deps(package_features);
let at_least_one_of = at_least_one_of_for_package(at_least_one_of, &deps_map);

powerset(features, depth)
let mut result: Vec<Vec<&'a Feature>> = powerset(features, depth)
.into_iter()
.skip(1) // The first element of a powerset is `[]` so it should be skipped.
.filter(|fs| {
Expand Down Expand Up @@ -249,7 +250,14 @@ pub(crate) fn feature_powerset<'a>(
}
true
})
.collect()
.collect();
if let Some(seed) = randomize {
if seed != 0 {
fastrand::seed(seed);
}
fastrand::shuffle(&mut result);
}
result
}

fn feature_deps(map: &BTreeMap<String, Vec<String>>) -> BTreeMap<&str, BTreeSet<&str>> {
Expand Down Expand Up @@ -366,22 +374,22 @@ mod tests {
let map = map![("a", v![]), ("b", v!["a"]), ("c", v!["b"]), ("d", v!["a", "b"])];

let list = v!["a", "b", "c", "d"];
let filtered = feature_powerset(&list, None, &[], &[], &map);
let filtered = feature_powerset(&list, None, &[], &[], &map, None);
assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]);

let filtered = feature_powerset(&list, None, &["a".into()], &[], &map);
let filtered = feature_powerset(&list, None, &["a".into()], &[], &map, None);
assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]);

let filtered = feature_powerset(&list, None, &["c".into()], &[], &map);
let filtered = feature_powerset(&list, None, &["c".into()], &[], &map, None);
assert_eq!(filtered, vec![vec!["c"], vec!["c", "d"]]);

let filtered = feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map);
let filtered = feature_powerset(&list, None, &["a".into(), "c".into()], &[], &map, None);
assert_eq!(filtered, vec![vec!["c"], vec!["c", "d"]]);

let map = map![("tokio", v![]), ("async-std", v![]), ("a", v![]), ("b", v!["a"])];
let list = v!["a", "b", "tokio", "async-std"];
let mutually_exclusive_features = [Feature::group(["tokio", "async-std"])];
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map);
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None);
assert_eq!(filtered, vec![
vec!["a"],
vec!["b"],
Expand All @@ -395,7 +403,7 @@ mod tests {

let mutually_exclusive_features =
[Feature::group(["tokio", "a"]), Feature::group(["tokio", "async-std"])];
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map);
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None);
assert_eq!(filtered, vec![
vec!["a"],
vec!["b"],
Expand All @@ -413,7 +421,7 @@ mod tests {
];
let list = v!["a", "b", "tokio", "async-std"];
let mutually_exclusive_features = [Feature::group(["tokio", "async-std"])];
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map);
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None);
assert_eq!(filtered, vec![
vec!["a"],
vec!["b"],
Expand All @@ -427,7 +435,7 @@ mod tests {
let map = map![("a", v![]), ("b", v!["a"]), ("c", v![]), ("d", v!["b"])];
let list = v!["a", "b", "c", "d"];
let mutually_exclusive_features = [Feature::group(["a", "c"])];
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map);
let filtered = feature_powerset(&list, None, &[], &mutually_exclusive_features, &map, None);
assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"]]);
}

Expand Down Expand Up @@ -461,7 +469,7 @@ mod tests {
vec!["b", "c", "d"],
vec!["a", "b", "c", "d"],
]);
let filtered = feature_powerset(&list, None, &[], &[], &map);
let filtered = feature_powerset(&list, None, &[], &[], &map, None);
assert_eq!(filtered, vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["c", "d"]]);
}

Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ fn determine_kind<'a>(
&cx.at_least_one_of,
&cx.mutually_exclusive_features,
&package.features,
cx.randomize_powerset,
);

if (pkg_features.normal().is_empty() && pkg_features.optional_deps().is_empty()
Expand Down
7 changes: 7 additions & 0 deletions tests/long-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ OPTIONS:
--include-features and there are multiple features, this also includes runs with just
--all-features flag.

--randomize-powerset <seed>
Randomize order of powerset elements..

Run feature powerset in random order with the specified seed.

Zero seed value means unseeded.

--optional-deps [DEPS]...
Use optional dependencies as features.

Expand Down
1 change: 1 addition & 0 deletions tests/short-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ OPTIONS:
-F, --features <FEATURES>... Space or comma separated list of features to activate
--each-feature Perform for each feature of the package
--feature-powerset Perform for the feature powerset of the package
--randomize-powerset <seed> Randomize order of powerset elements.
--optional-deps [DEPS]... Use optional dependencies as features
--skip <FEATURES>... Alias for --exclude-features
--exclude-features <FEATURES>... Space or comma separated list of features to exclude
Expand Down
Loading