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  }