From 3dca68fc95ae453571ecda7a3046052a2fbb50fc Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 8 Jul 2026 16:17:11 +0900 Subject: [PATCH] cmd/mbf: validate build determinism This rebuilds a package repeatedly and compares outcomes. Signed-off-by: Ophestra --- cmd/mbf/main.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index 37f799c8..b0f5495a 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -27,6 +27,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "sync" "sync/atomic" "syscall" @@ -897,6 +898,82 @@ func main() { ) } + c.NewCommand( + "validate", + "Repeatedly cure named artifact to validate determinism", + func(args []string) error { + stage := rosa.Std + if len(args) == 3 { + switch args[0] { + case "boot": + stage -= 2 + case "std": + stage -= 1 + + default: + return fmt.Errorf("invalid stage %q", args[0]) + } + args = args[1:] + } + + if len(args) != 2 { + return errors.New("validate requires 2 arguments") + } + + n, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid passes %q", args[0]) + } + + if n < 2 { + return errors.New("validate must rebuild at least twice") + } + + _, a := rosa.Native().New(stage).Load(rosa.ArtifactH(unique.Make(args[1]))) + if a == nil { + return fmt.Errorf("unknown artifact %q", args[1]) + } + + if !a.IsExclusive() && cm.jobs < 1 { + cm.jobs = runtime.NumCPU()/n + 1 + } + + res := make([]unique.Handle[pkg.Checksum], n) + if err = cm.Do(func(cache *pkg.Cache) error { + errs := make([]error, n) + var wg sync.WaitGroup + for i := range n { + wg.Go(func() { + _, res[i], errs[i] = cache.CureNew(a) + }) + } + wg.Wait() + return errors.Join(errs...) + }); err != nil { + return err + } + + for i := range n - 1 { + if res[i] == res[i+1] { + continue + } + + var buf strings.Builder + buf.WriteString("nondeterminism:") + for _, o := range res { + buf.WriteString("\n\t") + buf.WriteString(filepath.Join( + cm.base, + "checksum", + pkg.Encode(o.Value()), + )) + } + return errors.New(buf.String()) + } + return nil + }, + ) + cleanC := c.New("clean", "Remove unused entries from the cache") cleanC.NewCommand( "fault",