@@ -418,6 +418,10 @@ impl ManifestAnalyzer {
418418 ///
419419 /// Used when mixed default-features are detected or intersection is empty.
420420 /// Includes ALL dep kinds (Normal, Dev, Build) per design doc requirements.
421+ ///
422+ /// **IMPORTANT**: Only includes features from UNCONDITIONAL usages (no target constraint).
423+ /// Features from target-specific usages (e.g., `[target.'cfg(linux)'.dependencies]`)
424+ /// are excluded because they may not be valid on all platforms and should stay local.
421425 pub fn compute_union ( & self , dep : & DepKey ) -> BTreeSet < String > {
422426 let Some ( usages) = self . usage_index . get ( dep) else {
423427 return BTreeSet :: new ( ) ;
@@ -428,36 +432,69 @@ impl ManifestAnalyzer {
428432 return BTreeSet :: new ( ) ;
429433 }
430434
431- // Union all features from all usage sites
435+ // Union features from UNCONDITIONAL usages only
436+ // Target-specific features stay local to avoid platform incompatibilities
432437 let mut union = BTreeSet :: new ( ) ;
433438 for usage in usages {
434- union. extend ( usage. unconditional_features . iter ( ) . cloned ( ) ) ;
439+ if usage. target . is_none ( ) {
440+ union. extend ( usage. unconditional_features . iter ( ) . cloned ( ) ) ;
441+ }
435442 }
436443
437444 union
438445 }
439446
447+ /// Compute features that are ONLY declared with target constraints
448+ ///
449+ /// These features should stay local (in member Cargo.toml) because they may
450+ /// have platform-specific requirements (like cfg flags or OS restrictions).
451+ /// Returns the set of features that appear only in target-constrained usages.
452+ pub fn compute_target_local_features ( & self , dep : & DepKey ) -> BTreeSet < String > {
453+ let Some ( usages) = self . usage_index . get ( dep) else {
454+ return BTreeSet :: new ( ) ;
455+ } ;
456+
457+ // Collect features from target-constrained usages
458+ let mut target_features = BTreeSet :: new ( ) ;
459+ for usage in usages {
460+ if usage. target . is_some ( ) {
461+ target_features. extend ( usage. unconditional_features . iter ( ) . cloned ( ) ) ;
462+ }
463+ }
464+
465+ // Subtract features that also appear unconditionally
466+ let unconditional = self . compute_union ( dep) ;
467+ target_features. difference ( & unconditional) . cloned ( ) . collect ( )
468+ }
469+
440470 /// Compute the intersection of features used by all packages that depend on this dependency
441471 ///
442472 /// This is the CORE of the minimal feature approach:
443473 /// Only features that are ALWAYS enabled go into [workspace.dependencies].
444474 /// Members that need more can add local features.
445475 /// Includes ALL dep kinds (Normal, Dev, Build) per design doc requirements.
476+ ///
477+ /// **IMPORTANT**: Only considers UNCONDITIONAL usages (no target constraint).
478+ /// Target-specific usages are excluded because their features may have
479+ /// platform-specific requirements.
446480 pub fn compute_intersection ( & self , dep : & DepKey ) -> BTreeSet < String > {
447481 let Some ( usages) = self . usage_index . get ( dep) else {
448482 return BTreeSet :: new ( ) ;
449483 } ;
450484
485+ // Filter to unconditional usages only
486+ let unconditional_usages: Vec < _ > = usages. iter ( ) . filter ( |u| u. target . is_none ( ) ) . collect ( ) ;
487+
451488 // Include ALL dep kinds - workspace deps serve all usage contexts
452- if usages . len ( ) < 2 {
453- return BTreeSet :: new ( ) ; // Not enough uses to unify
489+ if unconditional_usages . len ( ) < 2 {
490+ return BTreeSet :: new ( ) ; // Not enough unconditional uses to unify
454491 }
455492
456- // Start with the first usage's features
457- let mut intersection = usages [ 0 ] . unconditional_features . clone ( ) ;
493+ // Start with the first unconditional usage's features
494+ let mut intersection = unconditional_usages [ 0 ] . unconditional_features . clone ( ) ;
458495
459- // Intersect with all other usages
460- for usage in & usages [ 1 ..] {
496+ // Intersect with all other unconditional usages
497+ for usage in & unconditional_usages [ 1 ..] {
461498 intersection = intersection
462499 . intersection ( & usage. unconditional_features )
463500 . cloned ( )
@@ -484,34 +521,44 @@ impl ManifestAnalyzer {
484521 /// Check if a dependency has mixed default-features settings
485522 ///
486523 /// Includes ALL dep kinds (Normal, Dev, Build) per design doc requirements.
524+ ///
525+ /// **IMPORTANT**: Only considers UNCONDITIONAL usages (no target constraint).
487526 pub fn has_mixed_defaults ( & self , dep : & DepKey ) -> bool {
488527 let Some ( usages) = self . usage_index . get ( dep) else {
489528 return false ;
490529 } ;
491530
531+ // Filter to unconditional usages only
532+ let unconditional_usages: Vec < _ > = usages. iter ( ) . filter ( |u| u. target . is_none ( ) ) . collect ( ) ;
533+
492534 // Include ALL dep kinds - workspace deps serve all usage contexts
493- if usages . len ( ) < 2 {
535+ if unconditional_usages . len ( ) < 2 {
494536 return false ;
495537 }
496538
497- // Check if all have the same default-features setting
498- let first_default = usages [ 0 ] . default_features ;
499- !usages . iter ( ) . all ( |u| u. default_features == first_default)
539+ // Check if all unconditional usages have the same default-features setting
540+ let first_default = unconditional_usages [ 0 ] . default_features ;
541+ !unconditional_usages . iter ( ) . all ( |u| u. default_features == first_default)
500542 }
501543
502544 /// Determine the default-features policy for a dependency
503545 ///
504546 /// Includes ALL dep kinds (Normal, Dev, Build) per design doc requirements.
547+ ///
548+ /// **IMPORTANT**: Only considers UNCONDITIONAL usages (no target constraint).
505549 pub fn default_features_policy ( & self , dep : & DepKey ) -> Option < bool > {
506550 let usages = self . usage_index . get ( dep) ?;
507551
552+ // Filter to unconditional usages only
553+ let unconditional_usages: Vec < _ > = usages. iter ( ) . filter ( |u| u. target . is_none ( ) ) . collect ( ) ;
554+
508555 // Include ALL dep kinds - workspace deps serve all usage contexts
509- if usages . is_empty ( ) {
556+ if unconditional_usages . is_empty ( ) {
510557 return None ;
511558 }
512559
513- // If any usage has default-features = false, we must use false at root
514- if usages . iter ( ) . any ( |u| !u. default_features ) {
560+ // If any unconditional usage has default-features = false, we must use false at root
561+ if unconditional_usages . iter ( ) . any ( |u| !u. default_features ) {
515562 Some ( false )
516563 } else {
517564 Some ( true )
0 commit comments