| // 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) | 
 | 	} | 
 | } |