@@ -10,7 +10,10 @@ use std::collections::{HashMap, HashSet};
1010/// Uses RESOLVED features from the dependency graph, not declared features.
1111/// This ensures we capture features enabled transitively and provides the
1212/// TRUE feature union across the workspace.
13- pub fn collect_dependencies ( metadata : & WorkspaceMetadata ) -> Vec < DependencyInstance > {
13+ ///
14+ /// The `use_all_features` parameter indicates whether metadata was collected with --all-features,
15+ /// which affects how we classify unidentified feature sources.
16+ pub fn collect_dependencies ( metadata : & WorkspaceMetadata , use_all_features : bool ) -> Vec < DependencyInstance > {
1417 let mut instances = Vec :: new ( ) ;
1518
1619 for pkg in metadata. list_crates ( ) {
@@ -40,7 +43,7 @@ pub fn collect_dependencies(metadata: &WorkspaceMetadata) -> Vec<DependencyInsta
4043 } ;
4144
4245 // Track feature provenance: WHY is each feature enabled?
43- let feature_provenance = determine_feature_provenance ( & features, dep, & pkg. name , metadata) ;
46+ let feature_provenance = determine_feature_provenance ( & features, dep, & pkg. name , metadata, use_all_features ) ;
4447
4548 // Detect if this is a proc-macro crate
4649 // Proc-macros are build-time only and have different optimization strategies
@@ -127,32 +130,45 @@ fn get_member_workspace_deps(manifest_path: &cargo_metadata::camino::Utf8Path) -
127130/// 1. Direct declaration in member's Cargo.toml
128131/// 2. Default features
129132/// 3. Target-specific
130- /// 4. Transitive or --all-features
133+ /// 4. Transitive or --all-features (depending on use_all_features parameter)
131134fn determine_feature_provenance (
132135 resolved_features : & [ String ] ,
133136 dep : & cargo_metadata:: Dependency ,
134137 member_name : & str ,
135138 metadata : & WorkspaceMetadata ,
139+ use_all_features : bool ,
136140) -> HashMap < String , FeatureSource > {
137141 let mut provenance = HashMap :: new ( ) ;
138142
139143 // Get the actual package metadata for this dependency
140144 let dep_pkg = metadata. get_package ( & dep. name ) ;
141145
146+ // Build a set of "root" features that could transitively enable other features:
147+ // 1. Features explicitly declared in Cargo.toml
148+ // 2. Default features (if enabled)
149+ let mut root_features = dep. features . to_vec ( ) ;
150+ if dep. uses_default_features {
151+ root_features. push ( "default" . to_string ( ) ) ;
152+ }
153+
142154 for feature in resolved_features {
143- let source = determine_single_feature_source ( feature, dep, member_name, dep_pkg) ;
155+ let source = determine_single_feature_source ( feature, dep, member_name, dep_pkg, & root_features , use_all_features ) ;
144156 provenance. insert ( feature. clone ( ) , source) ;
145157 }
146158
147159 provenance
148160}
149161
150162/// Determine the source of a single feature
163+ ///
164+ /// `root_features` are the features that were explicitly enabled (from Cargo.toml + default if enabled)
151165fn determine_single_feature_source (
152166 feature : & str ,
153167 dep : & cargo_metadata:: Dependency ,
154168 member_name : & str ,
155169 dep_pkg : Option < & cargo_metadata:: Package > ,
170+ root_features : & [ String ] ,
171+ use_all_features : bool ,
156172) -> FeatureSource {
157173 // 1. Check if declared directly in this member's Cargo.toml
158174 if dep. features . contains ( & feature. to_string ( ) ) {
@@ -166,7 +182,7 @@ fn determine_single_feature_source(
166182 && let Some ( pkg) = dep_pkg
167183 && let Some ( default_features) = pkg. features . get ( "default" )
168184 {
169- // Default features can reference other features
185+ // Default features can reference other features directly
170186 if default_features. iter ( ) . any ( |f| f. trim_start_matches ( "dep:" ) == feature) {
171187 return FeatureSource :: Default ;
172188 }
@@ -179,8 +195,52 @@ fn determine_single_feature_source(
179195 } ;
180196 }
181197
182- // 4. Otherwise it's either transitive or from --all-features
183- // For now, we classify it as AllFeatures since we're using --all-features metadata
184- // In a future enhancement, we could trace the actual dependency chain
185- FeatureSource :: AllFeatures
198+ // 4. Check if it's enabled transitively through a feature dependency chain
199+ // We need to trace from root_features (explicitly enabled) to this feature
200+ if let Some ( pkg) = dep_pkg {
201+ // Try to find a path from any root feature to this feature
202+ for root_feature in root_features {
203+ if let Some ( chain) = find_feature_chain ( root_feature, feature, pkg) {
204+ return FeatureSource :: Transitive { through : chain } ;
205+ }
206+ }
207+ }
208+
209+ // 5. If metadata was collected with --all-features, classify as such
210+ // Otherwise, it's transitive but we couldn't trace the exact chain
211+ if use_all_features {
212+ FeatureSource :: AllFeatures
213+ } else {
214+ // Transitive dependency - exact chain couldn't be determined
215+ FeatureSource :: Transitive { through : vec ! [ ] }
216+ }
217+ }
218+
219+ /// Find a chain of features from `start` to `target`
220+ ///
221+ /// Returns the feature chain if found (e.g., ["perf", "perf-inline"])
222+ fn find_feature_chain ( start : & str , target : & str , pkg : & cargo_metadata:: Package ) -> Option < Vec < String > > {
223+ // Check if start directly enables target
224+ if let Some ( feature_deps) = pkg. features . get ( start) {
225+ for dep_feature in feature_deps {
226+ let clean_name = dep_feature. trim_start_matches ( "dep:" ) ;
227+ if clean_name == target {
228+ return Some ( vec ! [ start. to_string( ) ] ) ;
229+ }
230+ }
231+
232+ // Check if start enables something that enables target (one level deep)
233+ for dep_feature in feature_deps {
234+ let intermediate = dep_feature. trim_start_matches ( "dep:" ) ;
235+ if let Some ( intermediate_deps) = pkg. features . get ( intermediate) {
236+ for sub_dep in intermediate_deps {
237+ if sub_dep. trim_start_matches ( "dep:" ) == target {
238+ return Some ( vec ! [ start. to_string( ) , intermediate. to_string( ) ] ) ;
239+ }
240+ }
241+ }
242+ }
243+ }
244+
245+ None
186246}
0 commit comments