@@ -14,6 +14,7 @@ import (
1414 "os"
1515 "os/exec"
1616 "path/filepath"
17+ "sort"
1718 "strings"
1819 "time"
1920
@@ -316,6 +317,12 @@ func createSquashFs(buildEnv env.ExecEnv, fn, dir string) (int64, error) {
316317 }
317318
318319 // Create reproducible tar archive.
320+ if os .Getenv ("ROFL_DEBUG_ROOTFS_HASH" ) != "" {
321+ if err := debugRootfsHashes (dir ); err != nil {
322+ return 0 , fmt .Errorf ("failed to compute rootfs debug hashes: %w" , err )
323+ }
324+ }
325+
319326 tarPath := fn + ".tar"
320327 tarPathEnv , err := buildEnv .PathToEnv (tarPath )
321328 if err != nil {
@@ -417,6 +424,69 @@ func createSquashFs(buildEnv env.ExecEnv, fn, dir string) (int64, error) {
417424 return fi .Size (), nil
418425}
419426
427+ // debugRootfsHashes prints deterministic SHA256 hashes for all files in dir to help diagnose
428+ // cross-host differences. Controlled via ROFL_DEBUG_ROOTFS_HASH env var.
429+ func debugRootfsHashes (dir string ) error {
430+ type entry struct {
431+ path string
432+ hash string
433+ }
434+ var entries []entry
435+
436+ err := filepath .WalkDir (dir , func (path string , d fs.DirEntry , err error ) error {
437+ if err != nil {
438+ return err
439+ }
440+ if d .IsDir () {
441+ return nil
442+ }
443+ rel , err := filepath .Rel (dir , path )
444+ if err != nil {
445+ return err
446+ }
447+
448+ fi , err := os .Lstat (path )
449+ if err != nil {
450+ return err
451+ }
452+
453+ switch {
454+ case fi .Mode ()& os .ModeSymlink != 0 :
455+ target , err := os .Readlink (path )
456+ if err != nil {
457+ return err
458+ }
459+ entries = append (entries , entry {path : rel , hash : "symlink->" + target })
460+ default :
461+ f , err := os .Open (path )
462+ if err != nil {
463+ return err
464+ }
465+ h := sha256 .New ()
466+ if _ , err = io .Copy (h , f ); err != nil {
467+ f .Close ()
468+ return err
469+ }
470+ f .Close ()
471+ entries = append (entries , entry {path : rel , hash : fmt .Sprintf ("%x" , h .Sum (nil ))})
472+ }
473+ return nil
474+ })
475+ if err != nil {
476+ return err
477+ }
478+
479+ sort .Slice (entries , func (i , j int ) bool {
480+ return entries [i ].path < entries [j ].path
481+ })
482+
483+ fmt .Println ("Rootfs file hashes (debug):" )
484+ for _ , e := range entries {
485+ fmt .Printf (" %s %s\n " , e .hash , e .path )
486+ }
487+ return nil
488+ }
489+
420490// sha256File computes a SHA-256 digest of the file with the given filename and returns a
421491// hex-encoded hash.
422492func sha256File (fn string ) (string , error ) {
0 commit comments