blob: d828f19b47588255dcbcc1b8162e5824bd885deb [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Gorebuild rebuilds and verifies the distribution files posted at https://go.dev/dl/.
//
// Usage:
//
// gorebuild [-p N] [goos-goarch][@version]...
//
// With no arguments, gorebuild rebuilds and verifies the files for all systems
// (that is, all operating system-architecture pairs) for up to three versions of Go:
//
// - the most recent patch release of the latest Go major version,
// - the most recent patch release of the previous Go major version, and
// - the latest release candidate of an upcoming Go major version, if there is one.
//
// Only Go versions starting at Go 1.21 or later are considered for this default
// set of versions, because Go 1.20 and earlier did not ship reproducible toolchains.
//
// With arguments, gorebuild rebuilds the files only for the named toolchains:
//
// - The syntax goos-goarch (for example, "linux-amd64") denotes the files
// for that specific system's toolchains for the three default versions.
// - The syntax @version (for example, "@go1.21rc3") denotes the files
// for all systems, at a specific Go version.
// - The syntax goos-goarch@version (for example, "linux-amd64@go1.21rc3")
// denotes the files for a specific system at a specific Go version.
//
// The -p flag specifies how many toolchain rebuilds to run in parallel (default 2).
//
// When running on linux-amd64, gorebuild does a full bootstrap, building Go 1.4
// (written in C) with the host C compiler, then building Go 1.17 with Go 1.4,
// then building Go 1.20 using Go 1.17, and so on, up to the target toolchain.
// On other systems, gorebuild downloads a binary distribution
// of the bootstrap toolchain it needs. For example, Go 1.21 required Go 1.17,
// so to rebuild and verify Go 1.21, gorebuild downloads and uses the latest binary
// distribution of the Go 1.17 toolchain (specifically, Go 1.17.13) from https://go.dev/dl/.
//
// In general, gorebuild checks that the local rebuild produces a bit-for-bit
// identical copy of the file posted at https://go.dev/dl/.
// Similarly, gorebuild checks that the local rebuild produces a bit-for-bit
// identical copy of the module form of the toolchain used by Go 1.21's
// toolchain downloads (also served by https://go.dev/dl/).
//
// However, in a few cases gorebuild does not insist on a bit-for-bit comparison.
// These cases are:
//
// - For macOS, https://go.dev/dl/ posts .tar.gz files containing binaries
// signed by Google's code-signing key.
// Gorebuild has no way to sign the binaries it produces using that same key.
// Instead, gorebuild compares the content of the rebuilt archive with the
// content of the posted archive, checking that non-executables match exactly
// and that executables match exactly after stripping their code signatures.
// The same comparison is applied to the module form of the toolchain.
//
// - For macOS, https://go.dev/dl/ posts a .pkg installer file.
// Gorebuild does not run the macOS tools to rebuild that installer.
// Instead, it parses the .pkg file and checks that the contents match
// the rebuilt .tar.gz file exactly, again after stripping code signatures.
// The .pkg is permitted to have one extra file, /etc/paths.d/go, which
// is unique to the .pkg form.
//
// - For Windows, https://go.dev/dl/ posts a .msi installer file.
// Gorebuild does not run the Windows tools to rebuild that installer.
// Instead, it invokes the Unix program “msiextract” to unpack the file
// and then checks that the contents match the rebuilt .zip file exactly.
// If “msiextract” is not found in the PATH, the .msi file is skipped
// rather than considered a failure.
//
// Gorebuild prints log messages to standard error but also accumulates them
// in a structured report. Before exiting, it writes the report as JSON to gorebuild.json
// and as HTML to gorebuild.html.
//
// Gorebuild exits with status 0 when it succeeds in writing a report,
// whether or not the report verified all the posted files.
package main
import (
"bytes"
_ "embed"
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"os"
"strings"
)
var pFlag = flag.Int("p", 2, "run `n` builds in parallel")
func usage() {
fmt.Fprintf(os.Stderr, "usage: gorebuild [goos-goarch][@version]...\n")
flag.PrintDefaults()
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("gorebuild: ")
flag.Usage = usage
flag.Parse()
args := flag.Args()
// Undocumented feature for developers working on report template:
// pass in a gorebuild.json file and it reformats the gorebuild.html file.
if len(args) == 1 && strings.HasSuffix(args[0], ".json") {
reformat(args[0])
return
}
r := Run(args)
writeJSON(r)
writeHTML(r)
}
func reformat(file string) {
data, err := os.ReadFile(file)
if err != nil {
log.Fatal(err)
}
var r Report
if err := json.Unmarshal(data, &r); err != nil {
log.Fatal(err)
}
writeHTML(&r)
}
func writeJSON(r *Report) {
js, err := json.MarshalIndent(r, "", "\t")
if err != nil {
log.Fatal(err)
}
js = append(js, '\n')
if err := os.WriteFile("gorebuild.json", js, 0666); err != nil {
log.Fatal(err)
}
}
//go:embed report.tmpl
var reportTmpl string
func writeHTML(r *Report) {
t, err := template.New("report.tmpl").Parse(reportTmpl)
if err != nil {
log.Fatal(err)
}
var buf bytes.Buffer
if err := t.Execute(&buf, &r); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("gorebuild.html", buf.Bytes(), 0666); err != nil {
log.Fatal(err)
}
}