Skip to content

Commit 7eaa19f

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 7eaa19f

File tree

3 files changed

+48
-26
lines changed

3 files changed

+48
-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: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -616,28 +616,26 @@ 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<_> =
627+
to_remove.union(&orphaned.extra_features).collect();
630628
self.common_attrs
631629
.crate_features
632-
.retain(|f| !extra_removed.contains(f));
630+
.retain(|f| !all_removed.contains(f));
633631

634632
self.common_attrs
635633
.deps
636-
.retain(|d| !orphaned_deps.contains(&d.id.name));
634+
.retain(|d| !orphaned.deps.contains(&d.id.name));
637635
if let Some(ref mut build_script) = self.build_script_attrs {
638636
build_script
639637
.deps
640-
.retain(|d| !orphaned_deps.contains(&d.id.name));
638+
.retain(|d| !orphaned.deps.contains(&d.id.name));
641639
}
642640
}
643641

@@ -924,6 +922,10 @@ impl CrateContext {
924922
}
925923
}
926924

925+
/// Transitively expands a set of removed features by cascading through
926+
/// reverse dependencies in the feature map. A feature is additionally
927+
/// removed if all of its parent features (features that enable it) are
928+
/// themselves in the removed set.
927929
fn expand_removed(
928930
feature_map: &BTreeMap<String, Vec<String>>,
929931
removed: &BTreeSet<String>,
@@ -962,6 +964,10 @@ fn expand_removed(
962964
full_removed
963965
}
964966

967+
/// Collects the set of optional dependency names activated by the given
968+
/// features. Recognizes both explicit `dep:name` entries and implicit
969+
/// optional dependency activation (where a feature name matches an
970+
/// optional dependency).
965971
fn collect_activated_deps(
966972
features: &BTreeSet<String>,
967973
feature_map: &BTreeMap<String, Vec<String>>,
@@ -980,29 +986,43 @@ fn collect_activated_deps(
980986
.collect()
981987
}
982988

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

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

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

10001019
let deps_surviving = collect_activated_deps(&surviving, feature_map, &optional_deps);
10011020
let deps_removed = collect_activated_deps(&full_removed, feature_map, &optional_deps);
10021021

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)
1022+
OrphanedDeps {
1023+
deps: deps_removed.difference(&deps_surviving).cloned().collect(),
1024+
extra_features: full_removed.difference(removed).cloned().collect(),
1025+
}
10061026
}
10071027

10081028
#[cfg(test)]
@@ -1667,36 +1687,36 @@ mod test {
16671687
remaining_features.insert(f.to_owned(), None);
16681688
}
16691689

1670-
let (orphaned_deps, extra_removed) =
1690+
let orphaned =
16711691
compute_orphaned_deps(&feature_map, &dependencies, &removed, &remaining_features);
16721692

16731693
assert!(
1674-
orphaned_deps.contains("onig"),
1694+
orphaned.deps.contains("onig"),
16751695
"onig dep should be orphaned"
16761696
);
16771697
assert!(
1678-
!orphaned_deps.contains("fancy-regex"),
1698+
!orphaned.deps.contains("fancy-regex"),
16791699
"fancy-regex dep survives"
16801700
);
16811701
assert!(
1682-
!orphaned_deps.contains("plist"),
1702+
!orphaned.deps.contains("plist"),
16831703
"plist dep survives via parsing"
16841704
);
16851705
assert!(
1686-
!orphaned_deps.contains("yaml-rust"),
1706+
!orphaned.deps.contains("yaml-rust"),
16871707
"yaml-rust dep survives via parsing"
16881708
);
16891709

16901710
assert!(
1691-
extra_removed.contains("onig"),
1711+
orphaned.extra_features.contains("onig"),
16921712
"onig feature transitively removed"
16931713
);
16941714
assert!(
1695-
extra_removed.contains("html"),
1715+
orphaned.extra_features.contains("html"),
16961716
"html feature transitively removed"
16971717
);
16981718
assert!(
1699-
!extra_removed.contains("parsing"),
1719+
!orphaned.extra_features.contains("parsing"),
17001720
"parsing feature survives"
17011721
);
17021722
}

0 commit comments

Comments
 (0)