1
+ use std:: collections:: HashMap ;
1
2
use std:: fs:: { self , File } ;
2
3
use std:: io:: prelude:: * ;
3
4
use std:: io:: SeekFrom ;
@@ -439,7 +440,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
439
440
let id = SourceId :: for_path ( & dst) ?;
440
441
let mut src = PathSource :: new ( & dst, id, ws. config ( ) ) ;
441
442
let new_pkg = src. root_package ( ) ?;
442
- let pkg_fingerprint = src . last_modified_file ( & new_pkg ) ?;
443
+ let pkg_fingerprint = hash_all ( & dst ) ?;
443
444
let ws = Workspace :: ephemeral ( new_pkg, config, None , true ) ?;
444
445
445
446
let exec: Arc < dyn Executor > = Arc :: new ( DefaultExecutor ) ;
@@ -465,21 +466,83 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
465
466
) ?;
466
467
467
468
// Check that `build.rs` didn't modify any files in the `src` directory.
468
- let ws_fingerprint = src . last_modified_file ( ws . current ( ) ? ) ?;
469
+ let ws_fingerprint = hash_all ( & dst ) ?;
469
470
if pkg_fingerprint != ws_fingerprint {
470
- let ( _ , path ) = ws_fingerprint;
471
+ let changes = report_hash_difference ( & pkg_fingerprint , & ws_fingerprint) ;
471
472
failure:: bail!(
472
473
"Source directory was modified by build.rs during cargo publish. \
473
- Build scripts should not modify anything outside of OUT_DIR. \
474
- Modified file: {}\n \n \
474
+ Build scripts should not modify anything outside of OUT_DIR.\n \
475
+ {}\n \n \
475
476
To proceed despite this, pass the `--no-verify` flag.",
476
- path . display ( )
477
+ changes
477
478
)
478
479
}
479
480
480
481
Ok ( ( ) )
481
482
}
482
483
484
+ fn hash_all ( path : & Path ) -> CargoResult < HashMap < PathBuf , u64 > > {
485
+ fn wrap ( path : & Path ) -> CargoResult < HashMap < PathBuf , u64 > > {
486
+ let mut result = HashMap :: new ( ) ;
487
+ let walker = walkdir:: WalkDir :: new ( path) . into_iter ( ) ;
488
+ for entry in walker. filter_entry ( |e| {
489
+ !( e. depth ( ) == 1 && ( e. file_name ( ) == "target" || e. file_name ( ) == "Cargo.lock" ) )
490
+ } ) {
491
+ let entry = entry?;
492
+ let file_type = entry. file_type ( ) ;
493
+ if file_type. is_file ( ) {
494
+ let contents = fs:: read ( entry. path ( ) ) ?;
495
+ let hash = util:: hex:: hash_u64 ( & contents) ;
496
+ result. insert ( entry. path ( ) . to_path_buf ( ) , hash) ;
497
+ } else if file_type. is_symlink ( ) {
498
+ let hash = util:: hex:: hash_u64 ( & fs:: read_link ( entry. path ( ) ) ?) ;
499
+ result. insert ( entry. path ( ) . to_path_buf ( ) , hash) ;
500
+ }
501
+ }
502
+ Ok ( result)
503
+ }
504
+ let result = wrap ( path) . chain_err ( || format ! ( "failed to verify output at {:?}" , path) ) ?;
505
+ Ok ( result)
506
+ }
507
+
508
+ fn report_hash_difference (
509
+ orig : & HashMap < PathBuf , u64 > ,
510
+ after : & HashMap < PathBuf , u64 > ,
511
+ ) -> String {
512
+ let mut changed = Vec :: new ( ) ;
513
+ let mut removed = Vec :: new ( ) ;
514
+ for ( key, value) in orig {
515
+ match after. get ( key) {
516
+ Some ( after_value) => {
517
+ if value != after_value {
518
+ changed. push ( key. to_string_lossy ( ) ) ;
519
+ }
520
+ }
521
+ None => removed. push ( key. to_string_lossy ( ) ) ,
522
+ }
523
+ }
524
+ let mut added: Vec < _ > = after
525
+ . keys ( )
526
+ . filter ( |key| !orig. contains_key ( * key) )
527
+ . map ( |key| key. to_string_lossy ( ) )
528
+ . collect ( ) ;
529
+ let mut result = Vec :: new ( ) ;
530
+ if !changed. is_empty ( ) {
531
+ changed. sort_unstable ( ) ;
532
+ result. push ( format ! ( "Changed: {}" , changed. join( "\n \t " ) ) ) ;
533
+ }
534
+ if !added. is_empty ( ) {
535
+ added. sort_unstable ( ) ;
536
+ result. push ( format ! ( "Added: {}" , added. join( "\n \t " ) ) ) ;
537
+ }
538
+ if !removed. is_empty ( ) {
539
+ removed. sort_unstable ( ) ;
540
+ result. push ( format ! ( "Removed: {}" , removed. join( "\n \t " ) ) ) ;
541
+ }
542
+ assert ! ( !result. is_empty( ) , "unexpected empty change detection" ) ;
543
+ result. join ( "\n " )
544
+ }
545
+
483
546
// It can often be the case that files of a particular name on one platform
484
547
// can't actually be created on another platform. For example files with colons
485
548
// in the name are allowed on Unix but not on Windows.
0 commit comments