1+ use std:: collections:: HashMap ;
12use std:: fs:: { self , File } ;
23use std:: io:: prelude:: * ;
34use std:: io:: SeekFrom ;
@@ -439,7 +440,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
439440 let id = SourceId :: for_path ( & dst) ?;
440441 let mut src = PathSource :: new ( & dst, id, ws. config ( ) ) ;
441442 let new_pkg = src. root_package ( ) ?;
442- let pkg_fingerprint = src . last_modified_file ( & new_pkg ) ?;
443+ let pkg_fingerprint = hash_all ( & dst ) ?;
443444 let ws = Workspace :: ephemeral ( new_pkg, config, None , true ) ?;
444445
445446 let exec: Arc < dyn Executor > = Arc :: new ( DefaultExecutor ) ;
@@ -465,21 +466,83 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
465466 ) ?;
466467
467468 // 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 ) ?;
469470 if pkg_fingerprint != ws_fingerprint {
470- let ( _ , path ) = ws_fingerprint;
471+ let changes = report_hash_difference ( & pkg_fingerprint , & ws_fingerprint) ;
471472 failure:: bail!(
472473 "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 \
475476 To proceed despite this, pass the `--no-verify` flag.",
476- path . display ( )
477+ changes
477478 )
478479 }
479480
480481 Ok ( ( ) )
481482}
482483
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+
483546// It can often be the case that files of a particular name on one platform
484547// can't actually be created on another platform. For example files with colons
485548// in the name are allowed on Unix but not on Windows.
0 commit comments