11use std:: cell:: RefCell ;
22use std:: collections:: hash_map:: { Entry , HashMap } ;
3- use std:: collections:: { BTreeMap , HashSet } ;
3+ use std:: collections:: { BTreeMap , BTreeSet , HashSet } ;
44use std:: path:: { Path , PathBuf } ;
5+ use std:: rc:: Rc ;
56use std:: slice;
67
78use glob:: glob;
@@ -11,7 +12,7 @@ use url::Url;
1112use crate :: core:: features:: Features ;
1213use crate :: core:: registry:: PackageRegistry ;
1314use crate :: core:: resolver:: features:: RequestedFeatures ;
14- use crate :: core:: { Dependency , PackageId , PackageIdSpec } ;
15+ use crate :: core:: { Dependency , InternedString , PackageId , PackageIdSpec } ;
1516use crate :: core:: { EitherManifest , Package , SourceId , VirtualManifest } ;
1617use crate :: ops;
1718use crate :: sources:: PathSource ;
@@ -877,60 +878,154 @@ impl<'cfg> Workspace<'cfg> {
877878 . map ( |m| ( m, RequestedFeatures :: new_all ( true ) ) )
878879 . collect ( ) ) ;
879880 }
880- if self . config ( ) . cli_unstable ( ) . package_features {
881- if specs. len ( ) > 1 && !requested_features. features . is_empty ( ) {
882- anyhow:: bail!( "cannot specify features for more than one package" ) ;
881+ if self . config ( ) . cli_unstable ( ) . package_features
882+ || self . config ( ) . cli_unstable ( ) . package_features2
883+ {
884+ self . members_with_features_pf ( specs, requested_features)
885+ } else {
886+ self . members_with_features_stable ( specs, requested_features)
887+ }
888+ }
889+
890+ /// New command-line feature selection with -Zpackage-features or -Zpackage-features2.
891+ fn members_with_features_pf (
892+ & self ,
893+ specs : & [ PackageIdSpec ] ,
894+ requested_features : & RequestedFeatures ,
895+ ) -> CargoResult < Vec < ( & Package , RequestedFeatures ) > > {
896+ let pf2 = self . config ( ) . cli_unstable ( ) . package_features2 ;
897+ if specs. len ( ) > 1 && !requested_features. features . is_empty ( ) && !pf2 {
898+ anyhow:: bail!( "cannot specify features for more than one package" ) ;
899+ }
900+ // Keep track of which features matched *any* member, to produce an error
901+ // if any of them did not match anywhere.
902+ let mut found: BTreeSet < InternedString > = BTreeSet :: new ( ) ;
903+
904+ // Returns the requested features for the given member.
905+ // This filters out any named features that the member does not have.
906+ let mut matching_features = |member : & Package | -> RequestedFeatures {
907+ // This new behavior is only enabled for -Zpackage-features2
908+ if !pf2 {
909+ return requested_features. clone ( ) ;
910+ }
911+ if requested_features. features . is_empty ( ) || requested_features. all_features {
912+ return requested_features. clone ( ) ;
883913 }
884- let members: Vec < ( & Package , RequestedFeatures ) > = self
914+ // Only include features this member defines.
915+ let summary = member. summary ( ) ;
916+ let member_features = summary. features ( ) ;
917+ let mut features = BTreeSet :: new ( ) ;
918+
919+ // Checks if a member contains the given feature.
920+ let contains = |feature : InternedString | -> bool {
921+ member_features. contains_key ( & feature)
922+ || summary
923+ . dependencies ( )
924+ . iter ( )
925+ . any ( |dep| dep. is_optional ( ) && dep. name_in_toml ( ) == feature)
926+ } ;
927+
928+ for feature in requested_features. features . iter ( ) {
929+ let mut split = feature. splitn ( 2 , '/' ) ;
930+ let split = ( split. next ( ) . unwrap ( ) , split. next ( ) ) ;
931+ if let ( pkg, Some ( pkg_feature) ) = split {
932+ let pkg = InternedString :: new ( pkg) ;
933+ let pkg_feature = InternedString :: new ( pkg_feature) ;
934+ if summary
935+ . dependencies ( )
936+ . iter ( )
937+ . any ( |dep| dep. name_in_toml ( ) == pkg)
938+ {
939+ // pkg/feat for a dependency.
940+ // Will rely on the dependency resolver to validate `feat`.
941+ features. insert ( * feature) ;
942+ found. insert ( * feature) ;
943+ } else if pkg == member. name ( ) && contains ( pkg_feature) {
944+ // member/feat where "feat" is a feature in member.
945+ features. insert ( pkg_feature) ;
946+ found. insert ( * feature) ;
947+ }
948+ } else if contains ( * feature) {
949+ // feature exists in this member.
950+ features. insert ( * feature) ;
951+ found. insert ( * feature) ;
952+ }
953+ }
954+ RequestedFeatures {
955+ features : Rc :: new ( features) ,
956+ all_features : false ,
957+ uses_default_features : requested_features. uses_default_features ,
958+ }
959+ } ;
960+
961+ let members: Vec < ( & Package , RequestedFeatures ) > = self
962+ . members ( )
963+ . filter ( |m| specs. iter ( ) . any ( |spec| spec. matches ( m. package_id ( ) ) ) )
964+ . map ( |m| ( m, matching_features ( m) ) )
965+ . collect ( ) ;
966+ if members. is_empty ( ) {
967+ // `cargo build -p foo`, where `foo` is not a member.
968+ // Do not allow any command-line flags (defaults only).
969+ if !( requested_features. features . is_empty ( )
970+ && !requested_features. all_features
971+ && requested_features. uses_default_features )
972+ {
973+ anyhow:: bail!( "cannot specify features for packages outside of workspace" ) ;
974+ }
975+ // Add all members from the workspace so we can ensure `-p nonmember`
976+ // is in the resolve graph.
977+ return Ok ( self
885978 . members ( )
886- . filter ( |m| specs. iter ( ) . any ( |spec| spec. matches ( m. package_id ( ) ) ) )
887- . map ( |m| ( m, requested_features. clone ( ) ) )
979+ . map ( |m| ( m, RequestedFeatures :: new_all ( false ) ) )
980+ . collect ( ) ) ;
981+ }
982+ if pf2 && * requested_features. features != found {
983+ let missing: Vec < _ > = requested_features
984+ . features
985+ . difference ( & found)
986+ . copied ( )
888987 . collect ( ) ;
889- if members. is_empty ( ) {
890- // `cargo build -p foo`, where `foo` is not a member.
891- // Do not allow any command-line flags (defaults only).
892- if !( requested_features. features . is_empty ( )
893- && !requested_features. all_features
894- && requested_features. uses_default_features )
895- {
896- anyhow:: bail!( "cannot specify features for packages outside of workspace" ) ;
988+ // TODO: typo suggestions would be good here.
989+ anyhow:: bail!(
990+ "none of the selected packages contains these features: {}" ,
991+ missing. join( ", " )
992+ ) ;
993+ }
994+ Ok ( members)
995+ }
996+
997+ /// This is the current "stable" behavior for command-line feature selection.
998+ fn members_with_features_stable (
999+ & self ,
1000+ specs : & [ PackageIdSpec ] ,
1001+ requested_features : & RequestedFeatures ,
1002+ ) -> CargoResult < Vec < ( & Package , RequestedFeatures ) > > {
1003+ let ms = self . members ( ) . filter_map ( |member| {
1004+ let member_id = member. package_id ( ) ;
1005+ match self . current_opt ( ) {
1006+ // The features passed on the command-line only apply to
1007+ // the "current" package (determined by the cwd).
1008+ Some ( current) if member_id == current. package_id ( ) => {
1009+ Some ( ( member, requested_features. clone ( ) ) )
8971010 }
898- // Add all members from the workspace so we can ensure `-p nonmember`
899- // is in the resolve graph.
900- return Ok ( self
901- . members ( )
902- . map ( |m| ( m, RequestedFeatures :: new_all ( false ) ) )
903- . collect ( ) ) ;
904- }
905- Ok ( members)
906- } else {
907- let ms = self . members ( ) . filter_map ( |member| {
908- let member_id = member. package_id ( ) ;
909- match self . current_opt ( ) {
910- // The features passed on the command-line only apply to
911- // the "current" package (determined by the cwd).
912- Some ( current) if member_id == current. package_id ( ) => {
913- Some ( ( member, requested_features. clone ( ) ) )
914- }
915- _ => {
916- // Ignore members that are not enabled on the command-line.
917- if specs. iter ( ) . any ( |spec| spec. matches ( member_id) ) {
918- // -p for a workspace member that is not the
919- // "current" one, don't use the local
920- // `--features`, only allow `--all-features`.
921- Some ( (
922- member,
923- RequestedFeatures :: new_all ( requested_features. all_features ) ,
924- ) )
925- } else {
926- // This member was not requested on the command-line, skip.
927- None
928- }
1011+ _ => {
1012+ // Ignore members that are not enabled on the command-line.
1013+ if specs. iter ( ) . any ( |spec| spec. matches ( member_id) ) {
1014+ // -p for a workspace member that is not the
1015+ // "current" one, don't use the local
1016+ // `--features`, only allow `--all-features`.
1017+ Some ( (
1018+ member,
1019+ RequestedFeatures :: new_all ( requested_features. all_features ) ,
1020+ ) )
1021+ } else {
1022+ // This member was not requested on the command-line, skip.
1023+ None
9291024 }
9301025 }
931- } ) ;
932- Ok ( ms . collect ( ) )
933- }
1026+ }
1027+ } ) ;
1028+ Ok ( ms . collect ( ) )
9341029 }
9351030}
9361031
0 commit comments