@@ -120,7 +120,7 @@ pub async fn run(args: ApplyArgs) -> i32 {
120120 }
121121
122122 match apply_patches_inner ( & args, & manifest_path) . await {
123- Ok ( ( success, results) ) => {
123+ Ok ( ( success, results, unmatched ) ) => {
124124 let patched_count = results
125125 . iter ( )
126126 . filter ( |r| r. success && !r. files_patched . is_empty ( ) )
@@ -141,6 +141,8 @@ pub async fn run(args: ApplyArgs) -> i32 {
141141 "patchesApplied" : patched_count,
142142 "alreadyPatched" : already_patched_count,
143143 "failed" : failed_count,
144+ "unmatchedPatches" : unmatched. len( ) ,
145+ "unmatchedPurls" : unmatched,
144146 "dryRun" : args. dry_run,
145147 "results" : results. iter( ) . map( result_to_json) . collect:: <Vec <_>>( ) ,
146148 } ) ) . unwrap( ) ) ;
@@ -237,7 +239,7 @@ pub async fn run(args: ApplyArgs) -> i32 {
237239async fn apply_patches_inner (
238240 args : & ApplyArgs ,
239241 manifest_path : & Path ,
240- ) -> Result < ( bool , Vec < ApplyResult > ) , String > {
242+ ) -> Result < ( bool , Vec < ApplyResult > , Vec < String > ) , String > {
241243 let manifest = read_manifest ( manifest_path)
242244 . await
243245 . map_err ( |e| e. to_string ( ) ) ?
@@ -260,7 +262,7 @@ async fn apply_patches_inner(
260262 ) ;
261263 eprintln ! ( "Run \" socket-patch repair\" to download missing blobs." ) ;
262264 }
263- return Ok ( ( false , Vec :: new ( ) ) ) ;
265+ return Ok ( ( false , Vec :: new ( ) , Vec :: new ( ) ) ) ;
264266 }
265267
266268 if !args. silent && !args. json {
@@ -278,7 +280,7 @@ async fn apply_patches_inner(
278280 if !args. silent && !args. json {
279281 eprintln ! ( "Some blobs could not be downloaded. Cannot apply patches." ) ;
280282 }
281- return Ok ( ( false , Vec :: new ( ) ) ) ;
283+ return Ok ( ( false , Vec :: new ( ) , Vec :: new ( ) ) ) ;
282284 }
283285 }
284286
@@ -287,6 +289,11 @@ async fn apply_patches_inner(
287289 let partitioned =
288290 partition_purls ( & manifest_purls, args. ecosystems . as_deref ( ) ) ;
289291
292+ let target_manifest_purls: HashSet < String > = partitioned
293+ . values ( )
294+ . flat_map ( |purls| purls. iter ( ) . cloned ( ) )
295+ . collect ( ) ;
296+
290297 let crawler_options = CrawlerOptions {
291298 cwd : args. cwd . clone ( ) ,
292299 global : args. global ,
@@ -307,14 +314,20 @@ async fn apply_patches_inner(
307314 eprintln ! ( "No package directories found" ) ;
308315 }
309316 }
310- return Ok ( ( false , Vec :: new ( ) ) ) ;
317+ return Ok ( ( false , Vec :: new ( ) , Vec :: new ( ) ) ) ;
311318 }
312319
313320 if all_packages. is_empty ( ) {
314321 if !args. silent && !args. json {
315- println ! ( "No packages found that match available patches" ) ;
322+ eprintln ! ( "Warning: No packages found that match available patches" ) ;
323+ eprintln ! (
324+ " {} targeted manifest patch(es) were in scope, but no matching packages were found on disk." ,
325+ target_manifest_purls. len( )
326+ ) ;
327+ eprintln ! ( " Check that packages are installed and --cwd points to the right directory." ) ;
316328 }
317- return Ok ( ( true , Vec :: new ( ) ) ) ;
329+ let unmatched: Vec < String > = target_manifest_purls. iter ( ) . cloned ( ) . collect ( ) ;
330+ return Ok ( ( false , Vec :: new ( ) , unmatched) ) ;
318331 }
319332
320333 // Apply patches
@@ -334,6 +347,7 @@ async fn apply_patches_inner(
334347 }
335348
336349 let mut applied_base_purls: HashSet < String > = HashSet :: new ( ) ;
350+ let mut matched_manifest_purls: HashSet < String > = HashSet :: new ( ) ;
337351
338352 for ( purl, pkg_path) in & all_packages {
339353 if Ecosystem :: from_purl ( purl) == Some ( Ecosystem :: Pypi ) {
@@ -378,6 +392,7 @@ async fn apply_patches_inner(
378392 applied = true ;
379393 applied_base_purls. insert ( base_purl. clone ( ) ) ;
380394 results. push ( result) ;
395+ matched_manifest_purls. insert ( variant_purl. clone ( ) ) ;
381396 break ;
382397 } else {
383398 results. push ( result) ;
@@ -418,9 +433,46 @@ async fn apply_patches_inner(
418433 }
419434 }
420435 results. push ( result) ;
436+ matched_manifest_purls. insert ( purl. clone ( ) ) ;
437+ }
438+ }
439+
440+ // Check if targeted manifest entries had no matches
441+ let unmatched: Vec < String > = target_manifest_purls
442+ . iter ( )
443+ . filter ( |p| !matched_manifest_purls. contains ( * p) )
444+ . cloned ( )
445+ . collect ( ) ;
446+
447+ if !unmatched. is_empty ( ) && !args. silent && !args. json {
448+ eprintln ! ( "\n Warning: {} manifest patch(es) had no matching installed package:" , unmatched. len( ) ) ;
449+ for purl in & unmatched {
450+ eprintln ! ( " - {}" , purl) ;
421451 }
422452 }
423453
454+ if !target_manifest_purls. is_empty ( ) && matched_manifest_purls. is_empty ( ) && !all_packages. is_empty ( ) {
455+ if !args. silent && !args. json {
456+ eprintln ! ( "Warning: None of the targeted manifest patches matched installed packages." ) ;
457+ }
458+ has_errors = true ;
459+ }
460+
461+ // Post-apply summary
462+ if !args. silent && !args. json {
463+ let applied_count = results. iter ( ) . filter ( |r| r. success && !r. files_patched . is_empty ( ) ) . count ( ) ;
464+ let already_count = results. iter ( ) . filter ( |r| {
465+ r. files_verified . iter ( ) . all ( |f| f. status == VerifyStatus :: AlreadyPatched )
466+ } ) . count ( ) ;
467+ println ! (
468+ "\n Summary: {}/{} targeted patches applied, {} already patched, {} not found on disk" ,
469+ applied_count,
470+ target_manifest_purls. len( ) ,
471+ already_count,
472+ unmatched. len( )
473+ ) ;
474+ }
475+
424476 // Clean up unused blobs
425477 if !args. silent && !args. json {
426478 if let Ok ( cleanup_result) = cleanup_unused_blobs ( & manifest, & blobs_path, args. dry_run ) . await {
@@ -430,5 +482,5 @@ async fn apply_patches_inner(
430482 }
431483 }
432484
433- Ok ( ( !has_errors, results) )
485+ Ok ( ( !has_errors, results, unmatched ) )
434486}
0 commit comments