golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gorebuild/main.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Gorebuild rebuilds and verifies the distribution files posted at https://go.dev/dl/. 6 // 7 // Usage: 8 // 9 // gorebuild [-p N] [goos-goarch][@version]... 10 // 11 // With no arguments, gorebuild rebuilds and verifies the files for all systems 12 // (that is, all operating system-architecture pairs) for up to three versions of Go: 13 // 14 // - the most recent patch release of the latest Go major version, 15 // - the most recent patch release of the previous Go major version, and 16 // - the latest release candidate of an upcoming Go major version, if there is one. 17 // 18 // Only Go versions starting at Go 1.21 or later are considered for this default 19 // set of versions, because Go 1.20 and earlier did not ship reproducible toolchains. 20 // 21 // With arguments, gorebuild rebuilds the files only for the named toolchains: 22 // 23 // - The syntax goos-goarch (for example, "linux-amd64") denotes the files 24 // for that specific system's toolchains for the three default versions. 25 // - The syntax @version (for example, "@go1.21rc3") denotes the files 26 // for all systems, at a specific Go version. 27 // - The syntax goos-goarch@version (for example, "linux-amd64@go1.21rc3") 28 // denotes the files for a specific system at a specific Go version. 29 // 30 // The -p flag specifies how many toolchain rebuilds to run in parallel (default 2). 31 // 32 // When running on linux-amd64, gorebuild does a full bootstrap, building Go 1.4 33 // (written in C) with the host C compiler, then building Go 1.17 with Go 1.4, 34 // then building Go 1.20 using Go 1.17, and so on, up to the target toolchain. 35 // On other systems, gorebuild downloads a binary distribution 36 // of the bootstrap toolchain it needs. For example, Go 1.21 required Go 1.17, 37 // so to rebuild and verify Go 1.21, gorebuild downloads and uses the latest binary 38 // distribution of the Go 1.17 toolchain (specifically, Go 1.17.13) from https://go.dev/dl/. 39 // 40 // In general, gorebuild checks that the local rebuild produces a bit-for-bit 41 // identical copy of the file posted at https://go.dev/dl/. 42 // Similarly, gorebuild checks that the local rebuild produces a bit-for-bit 43 // identical copy of the module form of the toolchain used by Go 1.21's 44 // toolchain downloads (also served by https://go.dev/dl/). 45 // 46 // However, in a few cases gorebuild does not insist on a bit-for-bit comparison. 47 // These cases are: 48 // 49 // - For macOS, https://go.dev/dl/ posts .tar.gz files containing binaries 50 // signed by Google's code-signing key. 51 // Gorebuild has no way to sign the binaries it produces using that same key. 52 // Instead, gorebuild compares the content of the rebuilt archive with the 53 // content of the posted archive, checking that non-executables match exactly 54 // and that executables match exactly after stripping their code signatures. 55 // The same comparison is applied to the module form of the toolchain. 56 // 57 // - For macOS, https://go.dev/dl/ posts a .pkg installer file. 58 // Gorebuild does not run the macOS tools to rebuild that installer. 59 // Instead, it parses the .pkg file and checks that the contents match 60 // the rebuilt .tar.gz file exactly, again after stripping code signatures. 61 // The .pkg is permitted to have one extra file, /etc/paths.d/go, which 62 // is unique to the .pkg form. 63 // 64 // - For Windows, https://go.dev/dl/ posts a .msi installer file. 65 // Gorebuild does not run the Windows tools to rebuild that installer. 66 // Instead, it invokes the Unix program “msiextract” to unpack the file 67 // and then checks that the contents match the rebuilt .zip file exactly. 68 // If “msiextract” is not found in the PATH, the .msi file is skipped 69 // rather than considered a failure. 70 // 71 // Gorebuild prints log messages to standard error but also accumulates them 72 // in a structured report. Before exiting, it writes the report as JSON to gorebuild.json 73 // and as HTML to gorebuild.html. 74 // 75 // Gorebuild exits with status 0 when it succeeds in writing a report, 76 // whether or not the report verified all the posted files. 77 package main 78 79 import ( 80 "bytes" 81 _ "embed" 82 "encoding/json" 83 "flag" 84 "fmt" 85 "html/template" 86 "log" 87 "os" 88 "strings" 89 ) 90 91 var pFlag = flag.Int("p", 2, "run `n` builds in parallel") 92 93 func usage() { 94 fmt.Fprintf(os.Stderr, "usage: gorebuild [goos-goarch][@version]...\n") 95 flag.PrintDefaults() 96 os.Exit(2) 97 } 98 99 func main() { 100 log.SetFlags(0) 101 log.SetPrefix("gorebuild: ") 102 flag.Usage = usage 103 flag.Parse() 104 105 args := flag.Args() 106 107 // Undocumented feature for developers working on report template: 108 // pass in a gorebuild.json file and it reformats the gorebuild.html file. 109 if len(args) == 1 && strings.HasSuffix(args[0], ".json") { 110 reformat(args[0]) 111 return 112 } 113 114 r := Run(args) 115 writeJSON(r) 116 writeHTML(r) 117 } 118 119 func reformat(file string) { 120 data, err := os.ReadFile(file) 121 if err != nil { 122 log.Fatal(err) 123 } 124 var r Report 125 if err := json.Unmarshal(data, &r); err != nil { 126 log.Fatal(err) 127 } 128 writeHTML(&r) 129 } 130 131 func writeJSON(r *Report) { 132 js, err := json.MarshalIndent(r, "", "\t") 133 if err != nil { 134 log.Fatal(err) 135 } 136 js = append(js, '\n') 137 if err := os.WriteFile("gorebuild.json", js, 0666); err != nil { 138 log.Fatal(err) 139 } 140 } 141 142 //go:embed report.tmpl 143 var reportTmpl string 144 145 func writeHTML(r *Report) { 146 t, err := template.New("report.tmpl").Parse(reportTmpl) 147 if err != nil { 148 log.Fatal(err) 149 } 150 var buf bytes.Buffer 151 if err := t.Execute(&buf, &r); err != nil { 152 log.Fatal(err) 153 } 154 if err := os.WriteFile("gorebuild.html", buf.Bytes(), 0666); err != nil { 155 log.Fatal(err) 156 } 157 }