1
1
use std:: cell:: RefCell ;
2
2
use std:: collections:: hash_map:: { Entry , HashMap } ;
3
- use std:: collections:: { BTreeMap , HashSet } ;
3
+ use std:: collections:: { BTreeMap , BTreeSet , HashSet } ;
4
4
use std:: path:: { Path , PathBuf } ;
5
+ use std:: rc:: Rc ;
5
6
use std:: slice;
6
7
7
8
use glob:: glob;
@@ -11,7 +12,7 @@ use url::Url;
11
12
use crate :: core:: features:: Features ;
12
13
use crate :: core:: registry:: PackageRegistry ;
13
14
use crate :: core:: resolver:: features:: RequestedFeatures ;
14
- use crate :: core:: { Dependency , PackageId , PackageIdSpec } ;
15
+ use crate :: core:: { Dependency , InternedString , PackageId , PackageIdSpec } ;
15
16
use crate :: core:: { EitherManifest , Package , SourceId , VirtualManifest } ;
16
17
use crate :: ops;
17
18
use crate :: sources:: PathSource ;
@@ -877,60 +878,154 @@ impl<'cfg> Workspace<'cfg> {
877
878
. map ( |m| ( m, RequestedFeatures :: new_all ( true ) ) )
878
879
. collect ( ) ) ;
879
880
}
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 ( ) ;
883
913
}
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
885
978
. 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 ( )
888
987
. 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 ( ) ) )
897
1010
}
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
929
1024
}
930
1025
}
931
- } ) ;
932
- Ok ( ms . collect ( ) )
933
- }
1026
+ }
1027
+ } ) ;
1028
+ Ok ( ms . collect ( ) )
934
1029
}
935
1030
}
936
1031
0 commit comments