Skip to content

Commit 3b78e1b

Browse files
refactor: improve features_to_remove correctness and code quality
Move features_to_remove from _ANNOTATION_SELECT_ATTRS to _ANNOTATION_NORMAL_ATTRS to fix a deserialization bug when used via annotation_select. Refactor compute_orphaned_deps to be self-contained, consolidate two retain passes into one, introduce OrphanedDeps struct for clarity, and add doc comments.
1 parent eb65046 commit 3b78e1b

File tree

3 files changed

+47
-26
lines changed

3 files changed

+47
-26
lines changed

crate_universe/extensions.bzl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,9 @@ _ANNOTATION_NORMAL_ATTRS = {
12941294
"extra_aliased_targets": attr.string_dict(
12951295
doc = "A list of targets to add to the generated aliases in the root crate_universe repository.",
12961296
),
1297+
"features_to_remove": attr.string_list(
1298+
doc = "A list of features to remove from a crate's resolved feature set. Overrides Cargo feature unification for this crate. Dependencies gated exclusively on removed features are also excluded.",
1299+
),
12971300
"gen_all_binaries": attr.bool(
12981301
doc = "If true, generates `rust_binary` targets for all of the crates bins",
12991302
),
@@ -1365,9 +1368,6 @@ _ANNOTATION_SELECT_ATTRS = {
13651368
"crate_features": attr.string_list(
13661369
doc = "A list of strings to add to a crate's `rust_library::crate_features` attribute.",
13671370
),
1368-
"features_to_remove": attr.string_list(
1369-
doc = "A list of features to remove from a crate's resolved feature set. Overrides Cargo feature unification for this crate. Dependencies gated exclusively on removed features are also excluded.",
1370-
),
13711371
"data": _relative_label_list(
13721372
doc = "A list of labels to add to a crate's `rust_library::data` attribute.",
13731373
),

crate_universe/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ pub(crate) struct CrateAnnotations {
365365
/// The crates to use instead of the generated one.
366366
pub(crate) override_targets: Option<BTreeMap<String, Label>>,
367367

368+
/// Features to remove from the resolved feature set. Overrides Cargo feature
369+
/// unification. Dependencies gated exclusively on removed features are also excluded.
368370
pub(crate) features_to_remove: Option<BTreeSet<String>>,
369371
}
370372

crate_universe/src/context/crate_context.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -616,28 +616,25 @@ impl CrateContext {
616616
}
617617

618618
if let Some(to_remove) = &crate_extra.features_to_remove {
619-
self.common_attrs
620-
.crate_features
621-
.retain(|f| !to_remove.contains(f));
622-
623-
let (orphaned_deps, extra_removed) = compute_orphaned_deps(
619+
let orphaned = compute_orphaned_deps(
624620
&package.features,
625621
&package.dependencies,
626622
to_remove,
627623
&self.common_attrs.crate_features,
628624
);
629625

626+
let all_removed: BTreeSet<_> = to_remove.union(&orphaned.extra_features).collect();
630627
self.common_attrs
631628
.crate_features
632-
.retain(|f| !extra_removed.contains(f));
629+
.retain(|f| !all_removed.contains(f));
633630

634631
self.common_attrs
635632
.deps
636-
.retain(|d| !orphaned_deps.contains(&d.id.name));
633+
.retain(|d| !orphaned.deps.contains(&d.id.name));
637634
if let Some(ref mut build_script) = self.build_script_attrs {
638635
build_script
639636
.deps
640-
.retain(|d| !orphaned_deps.contains(&d.id.name));
637+
.retain(|d| !orphaned.deps.contains(&d.id.name));
641638
}
642639
}
643640

@@ -924,6 +921,10 @@ impl CrateContext {
924921
}
925922
}
926923

924+
/// Transitively expands a set of removed features by cascading through
925+
/// reverse dependencies in the feature map. A feature is additionally
926+
/// removed if all of its parent features (features that enable it) are
927+
/// themselves in the removed set.
927928
fn expand_removed(
928929
feature_map: &BTreeMap<String, Vec<String>>,
929930
removed: &BTreeSet<String>,
@@ -962,6 +963,10 @@ fn expand_removed(
962963
full_removed
963964
}
964965

966+
/// Collects the set of optional dependency names activated by the given
967+
/// features. Recognizes both explicit `dep:name` entries and implicit
968+
/// optional dependency activation (where a feature name matches an
969+
/// optional dependency).
965970
fn collect_activated_deps(
966971
features: &BTreeSet<String>,
967972
feature_map: &BTreeMap<String, Vec<String>>,
@@ -980,29 +985,43 @@ fn collect_activated_deps(
980985
.collect()
981986
}
982987

988+
/// Result of computing which dependencies and features become orphaned
989+
/// after removing a set of features.
990+
struct OrphanedDeps {
991+
/// Dependency names to exclude (activated only by removed features).
992+
deps: BTreeSet<String>,
993+
/// Features transitively removed beyond the explicitly requested set.
994+
extra_features: BTreeSet<String>,
995+
}
996+
997+
/// Computes optional dependencies orphaned by removing the given features.
998+
/// Takes the full (unmodified) feature set and handles initial stripping
999+
/// internally.
9831000
fn compute_orphaned_deps(
9841001
feature_map: &BTreeMap<String, Vec<String>>,
9851002
dependencies: &[cargo_metadata::Dependency],
9861003
removed: &BTreeSet<String>,
987-
remaining_features: &Select<BTreeSet<String>>,
988-
) -> (BTreeSet<String>, BTreeSet<String>) {
1004+
all_features: &Select<BTreeSet<String>>,
1005+
) -> OrphanedDeps {
9891006
let optional_deps: BTreeSet<&str> = dependencies
9901007
.iter()
9911008
.filter(|d| d.optional)
9921009
.map(|d| d.name.as_str())
9931010
.collect();
9941011

995-
let remaining: BTreeSet<String> = remaining_features.values().into_iter().collect();
1012+
let all: BTreeSet<String> = all_features.values().into_iter().collect();
1013+
let remaining: BTreeSet<String> = all.difference(removed).cloned().collect();
9961014
let full_removed = expand_removed(feature_map, removed, &remaining);
9971015

998-
let surviving: BTreeSet<String> = remaining.difference(&full_removed).cloned().collect();
1016+
let surviving: BTreeSet<String> = all.difference(&full_removed).cloned().collect();
9991017

10001018
let deps_surviving = collect_activated_deps(&surviving, feature_map, &optional_deps);
10011019
let deps_removed = collect_activated_deps(&full_removed, feature_map, &optional_deps);
10021020

1003-
let orphaned_deps = deps_removed.difference(&deps_surviving).cloned().collect();
1004-
let extra_removed = full_removed.difference(removed).cloned().collect();
1005-
(orphaned_deps, extra_removed)
1021+
OrphanedDeps {
1022+
deps: deps_removed.difference(&deps_surviving).cloned().collect(),
1023+
extra_features: full_removed.difference(removed).cloned().collect(),
1024+
}
10061025
}
10071026

10081027
#[cfg(test)]
@@ -1667,36 +1686,36 @@ mod test {
16671686
remaining_features.insert(f.to_owned(), None);
16681687
}
16691688

1670-
let (orphaned_deps, extra_removed) =
1689+
let orphaned =
16711690
compute_orphaned_deps(&feature_map, &dependencies, &removed, &remaining_features);
16721691

16731692
assert!(
1674-
orphaned_deps.contains("onig"),
1693+
orphaned.deps.contains("onig"),
16751694
"onig dep should be orphaned"
16761695
);
16771696
assert!(
1678-
!orphaned_deps.contains("fancy-regex"),
1697+
!orphaned.deps.contains("fancy-regex"),
16791698
"fancy-regex dep survives"
16801699
);
16811700
assert!(
1682-
!orphaned_deps.contains("plist"),
1701+
!orphaned.deps.contains("plist"),
16831702
"plist dep survives via parsing"
16841703
);
16851704
assert!(
1686-
!orphaned_deps.contains("yaml-rust"),
1705+
!orphaned.deps.contains("yaml-rust"),
16871706
"yaml-rust dep survives via parsing"
16881707
);
16891708

16901709
assert!(
1691-
extra_removed.contains("onig"),
1710+
orphaned.extra_features.contains("onig"),
16921711
"onig feature transitively removed"
16931712
);
16941713
assert!(
1695-
extra_removed.contains("html"),
1714+
orphaned.extra_features.contains("html"),
16961715
"html feature transitively removed"
16971716
);
16981717
assert!(
1699-
!extra_removed.contains("parsing"),
1718+
!orphaned.extra_features.contains("parsing"),
17001719
"parsing feature survives"
17011720
);
17021721
}

0 commit comments

Comments
 (0)