From 8c70aa7f802bd68103f8995dd2be0a016d28176b 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 | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index 37f799c8..d9287742 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,76 @@ func main() { ) } + c.NewCommand( + "validate", + "Repeatedly cure named artifact to validate determinism", + func(args []string) error { + if len(args) != 2 { + return errors.New("validate requires 2 arguments") + } + + n, err := strconv.Atoi(args[0]) + if err != nil { + return err + } + + if n < 2 { + return errors.New("validate must rebuild at least twice") + } + + if cm.jobs < 1 { + cm.jobs = runtime.NumCPU()/n + 1 + } + + _, a := rosa.Native().Std().Load(rosa.ArtifactH(unique.Make(args[1]))) + if a == nil { + return fmt.Errorf("unknown artifact %q", args[1]) + } + + type res struct { + pathname *check.Absolute + checksum unique.Handle[pkg.Checksum] + } + r := make([]res, 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() { + r[i].pathname, r[i].checksum, errs[i] = cache.CureNew(a) + }) + } + wg.Wait() + return errors.Join(errs...) + }); err != nil { + return err + } + + for i := range n - 1 { + if r[i].checksum == r[i+1].checksum { + continue + } + + var buf strings.Builder + buf.WriteString("nondeterminism:") + for _, o := range r { + buf.WriteString("\n\t") + buf.WriteString(pkg.Encode(o.checksum.Value())) + buf.WriteString(" (") + buf.WriteString(o.pathname.String()) + buf.WriteByte(')') + } + return errors.New(buf.String()) + } + + for _, o := range r { + msg.Verbosef("%s: %s", pkg.Encode(o.checksum.Value()), o.pathname) + } + return nil + }, + ) + cleanC := c.New("clean", "Remove unused entries from the cache") cleanC.NewCommand( "fault",