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 ;
@@ -878,59 +879,143 @@ impl<'cfg> Workspace<'cfg> {
878
879
. collect ( ) ) ;
879
880
}
880
881
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" ) ;
882
+ self . members_with_features_pf ( specs, requested_features)
883
+ } else {
884
+ self . members_with_features_stable ( specs, requested_features)
885
+ }
886
+ }
887
+
888
+ /// New command-line feature selection with -Zpackage-features.
889
+ fn members_with_features_pf (
890
+ & self ,
891
+ specs : & [ PackageIdSpec ] ,
892
+ requested_features : & RequestedFeatures ,
893
+ ) -> CargoResult < Vec < ( & Package , RequestedFeatures ) > > {
894
+ // Keep track of which features matched *any* member, to produce an error
895
+ // if any of them did not match anywhere.
896
+ let mut found: BTreeSet < InternedString > = BTreeSet :: new ( ) ;
897
+
898
+ // Returns the requested features for the given member.
899
+ // This filters out any named features that the member does not have.
900
+ let mut matching_features = |member : & Package | -> RequestedFeatures {
901
+ if requested_features. features . is_empty ( ) || requested_features. all_features {
902
+ return requested_features. clone ( ) ;
903
+ }
904
+ // Only include features this member defines.
905
+ let summary = member. summary ( ) ;
906
+ let member_features = summary. features ( ) ;
907
+ let mut features = BTreeSet :: new ( ) ;
908
+
909
+ // Checks if a member contains the given feature.
910
+ let contains = |feature : InternedString | -> bool {
911
+ member_features. contains_key ( & feature)
912
+ || summary
913
+ . dependencies ( )
914
+ . iter ( )
915
+ . any ( |dep| dep. is_optional ( ) && dep. name_in_toml ( ) == feature)
916
+ } ;
917
+
918
+ for feature in requested_features. features . iter ( ) {
919
+ let mut split = feature. splitn ( 2 , '/' ) ;
920
+ let split = ( split. next ( ) . unwrap ( ) , split. next ( ) ) ;
921
+ if let ( pkg, Some ( pkg_feature) ) = split {
922
+ let pkg = InternedString :: new ( pkg) ;
923
+ let pkg_feature = InternedString :: new ( pkg_feature) ;
924
+ if summary
925
+ . dependencies ( )
926
+ . iter ( )
927
+ . any ( |dep| dep. name_in_toml ( ) == pkg)
928
+ {
929
+ // pkg/feat for a dependency.
930
+ // Will rely on the dependency resolver to validate `feat`.
931
+ features. insert ( * feature) ;
932
+ found. insert ( * feature) ;
933
+ } else if pkg == member. name ( ) && contains ( pkg_feature) {
934
+ // member/feat where "feat" is a feature in member.
935
+ features. insert ( pkg_feature) ;
936
+ found. insert ( * feature) ;
937
+ }
938
+ } else if contains ( * feature) {
939
+ // feature exists in this member.
940
+ features. insert ( * feature) ;
941
+ found. insert ( * feature) ;
942
+ }
943
+ }
944
+ RequestedFeatures {
945
+ features : Rc :: new ( features) ,
946
+ all_features : false ,
947
+ uses_default_features : requested_features. uses_default_features ,
948
+ }
949
+ } ;
950
+
951
+ let members: Vec < ( & Package , RequestedFeatures ) > = self
952
+ . members ( )
953
+ . filter ( |m| specs. iter ( ) . any ( |spec| spec. matches ( m. package_id ( ) ) ) )
954
+ . map ( |m| ( m, matching_features ( m) ) )
955
+ . collect ( ) ;
956
+ if members. is_empty ( ) {
957
+ // `cargo build -p foo`, where `foo` is not a member.
958
+ // Do not allow any command-line flags (defaults only).
959
+ if !( requested_features. features . is_empty ( )
960
+ && !requested_features. all_features
961
+ && requested_features. uses_default_features )
962
+ {
963
+ anyhow:: bail!( "cannot specify features for packages outside of workspace" ) ;
883
964
}
884
- let members: Vec < ( & Package , RequestedFeatures ) > = self
965
+ // Add all members from the workspace so we can ensure `-p nonmember`
966
+ // is in the resolve graph.
967
+ return Ok ( self
885
968
. members ( )
886
- . filter ( |m| specs. iter ( ) . any ( |spec| spec. matches ( m. package_id ( ) ) ) )
887
- . map ( |m| ( m, requested_features. clone ( ) ) )
969
+ . map ( |m| ( m, RequestedFeatures :: new_all ( false ) ) )
970
+ . collect ( ) ) ;
971
+ }
972
+ if * requested_features. features != found {
973
+ let missing: Vec < _ > = requested_features
974
+ . features
975
+ . difference ( & found)
976
+ . copied ( )
888
977
. 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" ) ;
978
+ // TODO: typo suggestions would be good here.
979
+ anyhow:: bail!(
980
+ "none of the selected packages contains these features: {}" ,
981
+ missing. join( ", " )
982
+ ) ;
983
+ }
984
+ Ok ( members)
985
+ }
986
+
987
+ /// This is the current "stable" behavior for command-line feature selection.
988
+ fn members_with_features_stable (
989
+ & self ,
990
+ specs : & [ PackageIdSpec ] ,
991
+ requested_features : & RequestedFeatures ,
992
+ ) -> CargoResult < Vec < ( & Package , RequestedFeatures ) > > {
993
+ let ms = self . members ( ) . filter_map ( |member| {
994
+ let member_id = member. package_id ( ) ;
995
+ match self . current_opt ( ) {
996
+ // The features passed on the command-line only apply to
997
+ // the "current" package (determined by the cwd).
998
+ Some ( current) if member_id == current. package_id ( ) => {
999
+ Some ( ( member, requested_features. clone ( ) ) )
897
1000
}
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
- }
1001
+ _ => {
1002
+ // Ignore members that are not enabled on the command-line.
1003
+ if specs. iter ( ) . any ( |spec| spec. matches ( member_id) ) {
1004
+ // -p for a workspace member that is not the
1005
+ // "current" one, don't use the local
1006
+ // `--features`, only allow `--all-features`.
1007
+ Some ( (
1008
+ member,
1009
+ RequestedFeatures :: new_all ( requested_features. all_features ) ,
1010
+ ) )
1011
+ } else {
1012
+ // This member was not requested on the command-line, skip.
1013
+ None
929
1014
}
930
1015
}
931
- } ) ;
932
- Ok ( ms . collect ( ) )
933
- }
1016
+ }
1017
+ } ) ;
1018
+ Ok ( ms . collect ( ) )
934
1019
}
935
1020
}
936
1021
0 commit comments