package main import ( "context" "errors" "fmt" "log" "os" "os/signal" "path/filepath" "runtime" "syscall" "unique" "hakurei.app/command" "hakurei.app/container" "hakurei.app/container/check" "hakurei.app/internal/pkg" "hakurei.app/internal/rosa" "hakurei.app/message" ) func main() { container.TryArgv0(nil) log.SetFlags(0) log.SetPrefix("mbf: ") msg := message.New(log.Default()) if os.Geteuid() == 0 { log.Fatal("this program must not run as root") } var cache *pkg.Cache ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) defer stop() defer func() { if cache != nil { cache.Close() } if r := recover(); r != nil { fmt.Println(r) log.Fatal("consider scrubbing the on-disk cache") } }() var ( flagQuiet bool flagCures int flagBase string flagTShift int ) c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) { msg.SwapVerbose(!flagQuiet) var base *check.Absolute if flagBase, err = filepath.Abs(flagBase); err != nil { return } else if base, err = check.NewAbs(flagBase); err != nil { return } if cache, err = pkg.Open(ctx, msg, flagCures, base); err == nil { if flagTShift < 0 { cache.SetThreshold(0) } else if flagTShift > 31 { cache.SetThreshold(1 << 31) } else { cache.SetThreshold(1 << flagTShift) } } return }).Flag( &flagQuiet, "q", command.BoolFlag(false), "Do not print cure messages", ).Flag( &flagCures, "cures", command.IntFlag(0), "Maximum number of dependencies to cure at any given time", ).Flag( &flagBase, "d", command.StringFlag("cache"), "Directory to store cured artifacts", ).Flag( &flagTShift, "tshift", command.IntFlag(-1), "Dependency graph size exponent, to the power of 2", ) { var flagShifts int c.NewCommand( "scrub", "Examine the on-disk cache for errors", func(args []string) error { if len(args) > 0 { return errors.New("scrub expects no arguments") } if flagShifts < 0 || flagShifts > 31 { flagShifts = 12 } return cache.Scrub(runtime.NumCPU() << flagShifts) }, ).Flag( &flagShifts, "shift", command.IntFlag(12), "Scrub parallelism size exponent, to the power of 2", ) } c.NewCommand( "stage3", "Check for toolchain 3-stage non-determinism", func(args []string) (err error) { _, _, _, stage2 := (rosa.Std - 1).NewLLVM() _, _, _, stage3 := rosa.Std.NewLLVM() var ( pathname *check.Absolute checksum [2]unique.Handle[pkg.Checksum] ) if pathname, checksum[0], err = cache.Cure(stage2); err != nil { return err } log.Println("stage2:", pathname) if pathname, checksum[1], err = cache.Cure(stage3); err != nil { return err } log.Println("stage3:", pathname) if checksum[0] != checksum[1] { err = &pkg.ChecksumMismatchError{ Got: checksum[0].Value(), Want: checksum[1].Value(), } } return }, ) c.NewCommand( "cure", "Cure the named artifact and show its path", func(args []string) error { if len(args) != 1 { return errors.New("cure requires 1 argument") } var p rosa.PArtifact switch args[0] { case "acl": p = rosa.ACL case "attr": p = rosa.Attr case "autoconf": p = rosa.Autoconf case "bash": p = rosa.Bash case "busybox": p = rosa.Busybox case "cmake": p = rosa.CMake case "coreutils": p = rosa.Coreutils case "diffutils": p = rosa.Diffutils case "gettext": p = rosa.Gettext case "git": p = rosa.Git case "go": p = rosa.Go case "gperf": p = rosa.Gperf case "kernel-headers": p = rosa.KernelHeaders case "libXau": p = rosa.LibXau case "libexpat": p = rosa.Libexpat case "libseccomp": p = rosa.Libseccomp case "libxml2": p = rosa.Libxml2 case "libffi": p = rosa.Libffi case "libgd": p = rosa.Libgd case "m4": p = rosa.M4 case "make": p = rosa.Make case "meson": p = rosa.Meson case "ninja": p = rosa.Ninja case "patch": p = rosa.Patch case "perl": p = rosa.Perl case "pkg-config": p = rosa.PkgConfig case "python": p = rosa.Python case "rsync": p = rosa.Rsync case "setuptools": p = rosa.Setuptools case "wayland": p = rosa.Wayland case "wayland-protocols": p = rosa.WaylandProtocols case "xcb": p = rosa.XCB case "xcb-proto": p = rosa.XCBProto case "xproto": p = rosa.Xproto case "zlib": p = rosa.Zlib default: return fmt.Errorf("unsupported artifact %q", args[0]) } pathname, _, err := cache.Cure(rosa.Std.Load(p)) if err == nil { log.Println(pathname) } return err }, ) c.MustParse(os.Args[1:], func(err error) { if cache != nil { cache.Close() } log.Fatal(err) }) }