aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-07-08 16:17:11 +0900
committerOphestra <cat@gensokyo.uk>2026-07-08 21:28:21 +0900
commit3dca68fc95ae453571ecda7a3046052a2fbb50fc (patch)
treef6bc64f3556d4b20b4e12a0f913db08ec3f3f8c5 /cmd
parent6495118165f7b30dda6857531af02a142c5aaa89 (diff)
cmd/mbf: validate build determinism
This rebuilds a package repeatedly and compares outcomes. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/mbf/main.go77
1 files changed, 77 insertions, 0 deletions
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",