2424
2525use std:: collections:: { BTreeSet , HashMap , HashSet } ;
2626use std:: hash:: { Hash , Hasher } ;
27- use std:: iter:: FromIterator ;
2827use std:: sync:: Arc ;
2928
3029use crate :: core:: compiler:: standard_lib;
@@ -41,8 +40,11 @@ use crate::core::{PackageId, PackageIdSpec, TargetKind, Workspace};
4140use crate :: ops;
4241use crate :: ops:: resolve:: WorkspaceResolve ;
4342use crate :: util:: config:: Config ;
43+ use crate :: util:: restricted_names:: is_glob_pattern;
4444use crate :: util:: { closest_msg, profile, CargoResult , StableHasher } ;
4545
46+ use anyhow:: Context as _;
47+
4648/// Contains information about how a package should be compiled.
4749///
4850/// Note on distinction between `CompileOptions` and `BuildConfig`:
@@ -116,6 +118,7 @@ impl Packages {
116118 } )
117119 }
118120
121+ /// Converts selected packages from a workspace to `PackageIdSpec`s.
119122 pub fn to_package_id_specs ( & self , ws : & Workspace < ' _ > ) -> CargoResult < Vec < PackageIdSpec > > {
120123 let specs = match self {
121124 Packages :: All => ws
@@ -124,33 +127,40 @@ impl Packages {
124127 . map ( PackageIdSpec :: from_package_id)
125128 . collect ( ) ,
126129 Packages :: OptOut ( opt_out) => {
127- let mut opt_out = BTreeSet :: from_iter ( opt_out. iter ( ) . cloned ( ) ) ;
128- let packages = ws
130+ let ( mut patterns , mut names ) = opt_patterns_and_names ( opt_out) ? ;
131+ let specs = ws
129132 . members ( )
130- . filter ( |pkg| !opt_out. remove ( pkg. name ( ) . as_str ( ) ) )
133+ . filter ( |pkg| {
134+ !names. remove ( pkg. name ( ) . as_str ( ) ) && !match_patterns ( pkg, & mut patterns)
135+ } )
131136 . map ( Package :: package_id)
132137 . map ( PackageIdSpec :: from_package_id)
133138 . collect ( ) ;
134- if !opt_out. is_empty ( ) {
135- ws. config ( ) . shell ( ) . warn ( format ! (
136- "excluded package(s) {} not found in workspace `{}`" ,
137- opt_out
138- . iter( )
139- . map( |x| x. as_ref( ) )
140- . collect:: <Vec <_>>( )
141- . join( ", " ) ,
142- ws. root( ) . display( ) ,
143- ) ) ?;
144- }
145- packages
139+ let warn = |e| ws. config ( ) . shell ( ) . warn ( e) ;
140+ emit_package_not_found ( ws, names, true ) . or_else ( warn) ?;
141+ emit_pattern_not_found ( ws, patterns, true ) . or_else ( warn) ?;
142+ specs
146143 }
147144 Packages :: Packages ( packages) if packages. is_empty ( ) => {
148145 vec ! [ PackageIdSpec :: from_package_id( ws. current( ) ?. package_id( ) ) ]
149146 }
150- Packages :: Packages ( packages) => packages
151- . iter ( )
152- . map ( |p| PackageIdSpec :: parse ( p) )
153- . collect :: < CargoResult < Vec < _ > > > ( ) ?,
147+ Packages :: Packages ( opt_in) => {
148+ let ( mut patterns, packages) = opt_patterns_and_names ( opt_in) ?;
149+ let mut specs = packages
150+ . iter ( )
151+ . map ( |p| PackageIdSpec :: parse ( p) )
152+ . collect :: < CargoResult < Vec < _ > > > ( ) ?;
153+ if !patterns. is_empty ( ) {
154+ let matched_pkgs = ws
155+ . members ( )
156+ . filter ( |pkg| match_patterns ( pkg, & mut patterns) )
157+ . map ( Package :: package_id)
158+ . map ( PackageIdSpec :: from_package_id) ;
159+ specs. extend ( matched_pkgs) ;
160+ }
161+ emit_pattern_not_found ( ws, patterns, false ) ?;
162+ specs
163+ }
154164 Packages :: Default => ws
155165 . default_members ( )
156166 . map ( Package :: package_id)
@@ -170,27 +180,35 @@ impl Packages {
170180 Ok ( specs)
171181 }
172182
183+ /// Gets a list of selected packages from a workspace.
173184 pub fn get_packages < ' ws > ( & self , ws : & ' ws Workspace < ' _ > ) -> CargoResult < Vec < & ' ws Package > > {
174185 let packages: Vec < _ > = match self {
175186 Packages :: Default => ws. default_members ( ) . collect ( ) ,
176187 Packages :: All => ws. members ( ) . collect ( ) ,
177- Packages :: OptOut ( opt_out) => ws
178- . members ( )
179- . filter ( |pkg| !opt_out. iter ( ) . any ( |name| pkg. name ( ) . as_str ( ) == name) )
180- . collect ( ) ,
181- Packages :: Packages ( packages) => packages
182- . iter ( )
183- . map ( |name| {
184- ws. members ( )
185- . find ( |pkg| pkg. name ( ) . as_str ( ) == name)
186- . ok_or_else ( || {
187- anyhow:: format_err!(
188- "package `{}` is not a member of the workspace" ,
189- name
190- )
191- } )
192- } )
193- . collect :: < CargoResult < Vec < _ > > > ( ) ?,
188+ Packages :: OptOut ( opt_out) => {
189+ let ( mut patterns, mut names) = opt_patterns_and_names ( opt_out) ?;
190+ let packages = ws
191+ . members ( )
192+ . filter ( |pkg| {
193+ !names. remove ( pkg. name ( ) . as_str ( ) ) && !match_patterns ( pkg, & mut patterns)
194+ } )
195+ . collect ( ) ;
196+ emit_package_not_found ( ws, names, true ) ?;
197+ emit_pattern_not_found ( ws, patterns, true ) ?;
198+ packages
199+ }
200+ Packages :: Packages ( opt_in) => {
201+ let ( mut patterns, mut names) = opt_patterns_and_names ( opt_in) ?;
202+ let packages = ws
203+ . members ( )
204+ . filter ( |pkg| {
205+ names. remove ( pkg. name ( ) . as_str ( ) ) || match_patterns ( pkg, & mut patterns)
206+ } )
207+ . collect ( ) ;
208+ emit_package_not_found ( ws, names, false ) ?;
209+ emit_pattern_not_found ( ws, patterns, false ) ?;
210+ packages
211+ }
194212 } ;
195213 Ok ( packages)
196214 }
@@ -577,6 +595,13 @@ impl FilterRule {
577595 FilterRule :: Just ( ref targets) => Some ( targets. clone ( ) ) ,
578596 }
579597 }
598+
599+ pub ( crate ) fn contains_glob_patterns ( & self ) -> bool {
600+ match self {
601+ FilterRule :: All => false ,
602+ FilterRule :: Just ( targets) => targets. iter ( ) . any ( is_glob_pattern) ,
603+ }
604+ }
580605}
581606
582607impl CompileFilter {
@@ -706,6 +731,24 @@ impl CompileFilter {
706731 CompileFilter :: Only { .. } => true ,
707732 }
708733 }
734+
735+ pub ( crate ) fn contains_glob_patterns ( & self ) -> bool {
736+ match self {
737+ CompileFilter :: Default { .. } => false ,
738+ CompileFilter :: Only {
739+ bins,
740+ examples,
741+ tests,
742+ benches,
743+ ..
744+ } => {
745+ bins. contains_glob_patterns ( )
746+ || examples. contains_glob_patterns ( )
747+ || tests. contains_glob_patterns ( )
748+ || benches. contains_glob_patterns ( )
749+ }
750+ }
751+ }
709752}
710753
711754/// A proposed target.
@@ -1163,8 +1206,16 @@ fn find_named_targets<'a>(
11631206 is_expected_kind : fn ( & Target ) -> bool ,
11641207 mode : CompileMode ,
11651208) -> CargoResult < Vec < Proposal < ' a > > > {
1166- let filter = |t : & Target | t. name ( ) == target_name && is_expected_kind ( t) ;
1167- let proposals = filter_targets ( packages, filter, true , mode) ;
1209+ let is_glob = is_glob_pattern ( target_name) ;
1210+ let proposals = if is_glob {
1211+ let pattern = build_glob ( target_name) ?;
1212+ let filter = |t : & Target | is_expected_kind ( t) && pattern. matches ( t. name ( ) ) ;
1213+ filter_targets ( packages, filter, true , mode)
1214+ } else {
1215+ let filter = |t : & Target | t. name ( ) == target_name && is_expected_kind ( t) ;
1216+ filter_targets ( packages, filter, true , mode)
1217+ } ;
1218+
11681219 if proposals. is_empty ( ) {
11691220 let targets = packages. iter ( ) . flat_map ( |pkg| {
11701221 pkg. targets ( )
@@ -1173,8 +1224,9 @@ fn find_named_targets<'a>(
11731224 } ) ;
11741225 let suggestion = closest_msg ( target_name, targets, |t| t. name ( ) ) ;
11751226 anyhow:: bail!(
1176- "no {} target named `{}`{}" ,
1227+ "no {} target {} `{}`{}" ,
11771228 target_desc,
1229+ if is_glob { "matches pattern" } else { "named" } ,
11781230 target_name,
11791231 suggestion
11801232 ) ;
@@ -1291,3 +1343,86 @@ fn traverse_and_share(
12911343 new_graph. entry ( new_unit. clone ( ) ) . or_insert ( new_deps) ;
12921344 new_unit
12931345}
1346+
1347+ /// Build `glob::Pattern` with informative context.
1348+ fn build_glob ( pat : & str ) -> CargoResult < glob:: Pattern > {
1349+ glob:: Pattern :: new ( pat) . with_context ( || format ! ( "cannot build glob pattern from `{}`" , pat) )
1350+ }
1351+
1352+ /// Emits "package not found" error.
1353+ ///
1354+ /// > This function should be used only in package selection processes such like
1355+ /// `Packages::to_package_id_specs` and `Packages::get_packages`.
1356+ fn emit_package_not_found (
1357+ ws : & Workspace < ' _ > ,
1358+ opt_names : BTreeSet < & str > ,
1359+ opt_out : bool ,
1360+ ) -> CargoResult < ( ) > {
1361+ if !opt_names. is_empty ( ) {
1362+ anyhow:: bail!(
1363+ "{}package(s) `{}` not found in workspace `{}`" ,
1364+ if opt_out { "excluded " } else { "" } ,
1365+ opt_names. into_iter( ) . collect:: <Vec <_>>( ) . join( ", " ) ,
1366+ ws. root( ) . display( ) ,
1367+ )
1368+ }
1369+ Ok ( ( ) )
1370+ }
1371+
1372+ /// Emits "glob pattern not found" error.
1373+ ///
1374+ /// > This function should be used only in package selection processes such like
1375+ /// `Packages::to_package_id_specs` and `Packages::get_packages`.
1376+ fn emit_pattern_not_found (
1377+ ws : & Workspace < ' _ > ,
1378+ opt_patterns : Vec < ( glob:: Pattern , bool ) > ,
1379+ opt_out : bool ,
1380+ ) -> CargoResult < ( ) > {
1381+ let not_matched = opt_patterns
1382+ . iter ( )
1383+ . filter ( |( _, matched) | !* matched)
1384+ . map ( |( pat, _) | pat. as_str ( ) )
1385+ . collect :: < Vec < _ > > ( ) ;
1386+ if !not_matched. is_empty ( ) {
1387+ anyhow:: bail!(
1388+ "{}package pattern(s) `{}` not found in workspace `{}`" ,
1389+ if opt_out { "excluded " } else { "" } ,
1390+ not_matched. join( ", " ) ,
1391+ ws. root( ) . display( ) ,
1392+ )
1393+ }
1394+ Ok ( ( ) )
1395+ }
1396+
1397+ /// Checks whether a package matches any of a list of glob patterns generated
1398+ /// from `opt_patterns_and_names`.
1399+ ///
1400+ /// > This function should be used only in package selection processes such like
1401+ /// `Packages::to_package_id_specs` and `Packages::get_packages`.
1402+ fn match_patterns ( pkg : & Package , patterns : & mut Vec < ( glob:: Pattern , bool ) > ) -> bool {
1403+ patterns. iter_mut ( ) . any ( |( m, matched) | {
1404+ let is_matched = m. matches ( pkg. name ( ) . as_str ( ) ) ;
1405+ * matched |= is_matched;
1406+ is_matched
1407+ } )
1408+ }
1409+
1410+ /// Given a list opt-in or opt-out package selection strings, generates two
1411+ /// collections that represent glob patterns and package names respectively.
1412+ ///
1413+ /// > This function should be used only in package selection processes such like
1414+ /// `Packages::to_package_id_specs` and `Packages::get_packages`.
1415+ fn opt_patterns_and_names (
1416+ opt : & [ String ] ,
1417+ ) -> CargoResult < ( Vec < ( glob:: Pattern , bool ) > , BTreeSet < & str > ) > {
1418+ let mut opt_patterns = Vec :: new ( ) ;
1419+ let mut opt_names = BTreeSet :: new ( ) ;
1420+ for x in opt. iter ( ) {
1421+ if is_glob_pattern ( x) {
1422+ opt_patterns. push ( ( build_glob ( x) ?, false ) ) ;
1423+ } else {
1424+ opt_names. insert ( String :: as_str ( x) ) ;
1425+ }
1426+ }
1427+ Ok ( ( opt_patterns, opt_names) )
1428+ }
0 commit comments