summaryrefslogtreecommitdiffstats
path: root/src/cmd/distpack
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/cmd/distpack
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/distpack')
-rw-r--r--src/cmd/distpack/archive.go211
-rw-r--r--src/cmd/distpack/archive_test.go39
-rw-r--r--src/cmd/distpack/pack.go434
-rw-r--r--src/cmd/distpack/test.go170
4 files changed, 854 insertions, 0 deletions
diff --git a/src/cmd/distpack/archive.go b/src/cmd/distpack/archive.go
new file mode 100644
index 0000000..24ed077
--- /dev/null
+++ b/src/cmd/distpack/archive.go
@@ -0,0 +1,211 @@
+// 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.
+
+package main
+
+import (
+ "io/fs"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+)
+
+// An Archive describes an archive to write: a collection of files.
+// Directories are implied by the files and not explicitly listed.
+type Archive struct {
+ Files []File
+}
+
+// A File describes a single file to write to an archive.
+type File struct {
+ Name string // name in archive
+ Time time.Time // modification time
+ Mode fs.FileMode
+ Size int64
+ Src string // source file in OS file system
+}
+
+// Info returns a FileInfo about the file, for use with tar.FileInfoHeader
+// and zip.FileInfoHeader.
+func (f *File) Info() fs.FileInfo {
+ return fileInfo{f}
+}
+
+// A fileInfo is an implementation of fs.FileInfo describing a File.
+type fileInfo struct {
+ f *File
+}
+
+func (i fileInfo) Name() string { return path.Base(i.f.Name) }
+func (i fileInfo) ModTime() time.Time { return i.f.Time }
+func (i fileInfo) Mode() fs.FileMode { return i.f.Mode }
+func (i fileInfo) IsDir() bool { return i.f.Mode&fs.ModeDir != 0 }
+func (i fileInfo) Size() int64 { return i.f.Size }
+func (i fileInfo) Sys() any { return nil }
+
+func (i fileInfo) String() string {
+ return fs.FormatFileInfo(i)
+}
+
+// NewArchive returns a new Archive containing all the files in the directory dir.
+// The archive can be amended afterward using methods like Add and Filter.
+func NewArchive(dir string) (*Archive, error) {
+ a := new(Archive)
+ err := fs.WalkDir(os.DirFS(dir), ".", func(name string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ info, err := d.Info()
+ if err != nil {
+ return err
+ }
+ a.Add(name, filepath.Join(dir, name), info)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ a.Sort()
+ return a, nil
+}
+
+// Add adds a file with the given name and info to the archive.
+// The content of the file comes from the operating system file src.
+// After a sequence of one or more calls to Add,
+// the caller should invoke Sort to re-sort the archive's files.
+func (a *Archive) Add(name, src string, info fs.FileInfo) {
+ a.Files = append(a.Files, File{
+ Name: name,
+ Time: info.ModTime(),
+ Mode: info.Mode(),
+ Size: info.Size(),
+ Src: src,
+ })
+}
+
+// Sort sorts the files in the archive.
+// It is only necessary to call Sort after calling Add or RenameGoMod.
+// ArchiveDir returns a sorted archive, and the other methods
+// preserve the sorting of the archive.
+func (a *Archive) Sort() {
+ sort.Slice(a.Files, func(i, j int) bool {
+ return a.Files[i].Name < a.Files[j].Name
+ })
+}
+
+// Clone returns a copy of the Archive.
+// Method calls like Add and Filter invoked on the copy do not affect the original,
+// nor do calls on the original affect the copy.
+func (a *Archive) Clone() *Archive {
+ b := &Archive{
+ Files: make([]File, len(a.Files)),
+ }
+ copy(b.Files, a.Files)
+ return b
+}
+
+// AddPrefix adds a prefix to all file names in the archive.
+func (a *Archive) AddPrefix(prefix string) {
+ for i := range a.Files {
+ a.Files[i].Name = path.Join(prefix, a.Files[i].Name)
+ }
+}
+
+// Filter removes files from the archive for which keep(name) returns false.
+func (a *Archive) Filter(keep func(name string) bool) {
+ files := a.Files[:0]
+ for _, f := range a.Files {
+ if keep(f.Name) {
+ files = append(files, f)
+ }
+ }
+ a.Files = files
+}
+
+// SetMode changes the mode of every file in the archive
+// to be mode(name, m), where m is the file's current mode.
+func (a *Archive) SetMode(mode func(name string, m fs.FileMode) fs.FileMode) {
+ for i := range a.Files {
+ a.Files[i].Mode = mode(a.Files[i].Name, a.Files[i].Mode)
+ }
+}
+
+// Remove removes files matching any of the patterns from the archive.
+// The patterns use the syntax of path.Match, with an extension of allowing
+// a leading **/ or trailing /**, which match any number of path elements
+// (including no path elements) before or after the main match.
+func (a *Archive) Remove(patterns ...string) {
+ a.Filter(func(name string) bool {
+ for _, pattern := range patterns {
+ match, err := amatch(pattern, name)
+ if err != nil {
+ log.Fatalf("archive remove: %v", err)
+ }
+ if match {
+ return false
+ }
+ }
+ return true
+ })
+}
+
+// SetTime sets the modification time of all files in the archive to t.
+func (a *Archive) SetTime(t time.Time) {
+ for i := range a.Files {
+ a.Files[i].Time = t
+ }
+}
+
+// RenameGoMod renames the go.mod files in the archive to _go.mod,
+// for use with the module form, which cannot contain other go.mod files.
+func (a *Archive) RenameGoMod() {
+ for i, f := range a.Files {
+ if strings.HasSuffix(f.Name, "/go.mod") {
+ a.Files[i].Name = strings.TrimSuffix(f.Name, "go.mod") + "_go.mod"
+ }
+ }
+}
+
+func amatch(pattern, name string) (bool, error) {
+ // firstN returns the prefix of name corresponding to the first n path elements.
+ // If n <= 0, firstN returns the entire name.
+ firstN := func(name string, n int) string {
+ for i := 0; i < len(name); i++ {
+ if name[i] == '/' {
+ if n--; n == 0 {
+ return name[:i]
+ }
+ }
+ }
+ return name
+ }
+
+ // lastN returns the suffix of name corresponding to the last n path elements.
+ // If n <= 0, lastN returns the entire name.
+ lastN := func(name string, n int) string {
+ for i := len(name) - 1; i >= 0; i-- {
+ if name[i] == '/' {
+ if n--; n == 0 {
+ return name[i+1:]
+ }
+ }
+ }
+ return name
+ }
+
+ if p, ok := strings.CutPrefix(pattern, "**/"); ok {
+ return path.Match(p, lastN(name, 1+strings.Count(p, "/")))
+ }
+ if p, ok := strings.CutSuffix(pattern, "/**"); ok {
+ return path.Match(p, firstN(name, 1+strings.Count(p, "/")))
+ }
+ return path.Match(pattern, name)
+}
diff --git a/src/cmd/distpack/archive_test.go b/src/cmd/distpack/archive_test.go
new file mode 100644
index 0000000..620b970
--- /dev/null
+++ b/src/cmd/distpack/archive_test.go
@@ -0,0 +1,39 @@
+// 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.
+
+package main
+
+import "testing"
+
+var amatchTests = []struct {
+ pattern string
+ name string
+ ok bool
+}{
+ {"a", "a", true},
+ {"a", "b", false},
+ {"a/**", "a", true},
+ {"a/**", "b", false},
+ {"a/**", "a/b", true},
+ {"a/**", "b/b", false},
+ {"a/**", "a/b/c/d/e/f", true},
+ {"a/**", "z/a/b/c/d/e/f", false},
+ {"**/a", "a", true},
+ {"**/a", "b", false},
+ {"**/a", "x/a", true},
+ {"**/a", "x/a/b", false},
+ {"**/a", "x/y/z/a", true},
+ {"**/a", "x/y/z/a/b", false},
+
+ {"go/pkg/tool/*/compile", "go/pkg/tool/darwin_amd64/compile", true},
+}
+
+func TestAmatch(t *testing.T) {
+ for _, tt := range amatchTests {
+ ok, err := amatch(tt.pattern, tt.name)
+ if ok != tt.ok || err != nil {
+ t.Errorf("amatch(%q, %q) = %v, %v, want %v, nil", tt.pattern, tt.name, ok, err, tt.ok)
+ }
+ }
+}
diff --git a/src/cmd/distpack/pack.go b/src/cmd/distpack/pack.go
new file mode 100644
index 0000000..cf507ed
--- /dev/null
+++ b/src/cmd/distpack/pack.go
@@ -0,0 +1,434 @@
+// 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.
+
+// Distpack creates the tgz and zip files for a Go distribution.
+// It writes into GOROOT/pkg/distpack:
+//
+// - a binary distribution (tgz or zip) for the current GOOS and GOARCH
+// - a source distribution that is independent of GOOS/GOARCH
+// - the module mod, info, and zip files for a distribution in module form
+// (as used by GOTOOLCHAIN support in the go command).
+//
+// Distpack is typically invoked by the -distpack flag to make.bash.
+// A cross-compiled distribution for goos/goarch can be built using:
+//
+// GOOS=goos GOARCH=goarch ./make.bash -distpack
+//
+// To test that the module downloads are usable with the go command:
+//
+// ./make.bash -distpack
+// mkdir -p /tmp/goproxy/golang.org/toolchain/
+// ln -sf $(pwd)/../pkg/distpack /tmp/goproxy/golang.org/toolchain/@v
+// GOPROXY=file:///tmp/goproxy GOTOOLCHAIN=$(sed 1q ../VERSION) gotip version
+//
+// gotip can be replaced with an older released Go version once there is one.
+// It just can't be the one make.bash built, because it knows it is already that
+// version and will skip the download.
+package main
+
+import (
+ "archive/tar"
+ "archive/zip"
+ "compress/flate"
+ "compress/gzip"
+ "crypto/sha256"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: distpack\n")
+ os.Exit(2)
+}
+
+const (
+ modPath = "golang.org/toolchain"
+ modVersionPrefix = "v0.0.1"
+)
+
+var (
+ goroot string
+ gohostos string
+ gohostarch string
+ goos string
+ goarch string
+)
+
+func main() {
+ log.SetPrefix("distpack: ")
+ log.SetFlags(0)
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 0 {
+ usage()
+ }
+
+ // Load context.
+ goroot = runtime.GOROOT()
+ if goroot == "" {
+ log.Fatalf("missing $GOROOT")
+ }
+ gohostos = runtime.GOOS
+ gohostarch = runtime.GOARCH
+ goos = os.Getenv("GOOS")
+ if goos == "" {
+ goos = gohostos
+ }
+ goarch = os.Getenv("GOARCH")
+ if goarch == "" {
+ goarch = gohostarch
+ }
+ goosUnderGoarch := goos + "_" + goarch
+ goosDashGoarch := goos + "-" + goarch
+ exe := ""
+ if goos == "windows" {
+ exe = ".exe"
+ }
+ version, versionTime := readVERSION(goroot)
+
+ // Start with files from GOROOT, filtering out non-distribution files.
+ base, err := NewArchive(goroot)
+ if err != nil {
+ log.Fatal(err)
+ }
+ base.SetTime(versionTime)
+ base.SetMode(mode)
+ base.Remove(
+ ".git/**",
+ ".gitattributes",
+ ".github/**",
+ ".gitignore",
+ "VERSION.cache",
+ "misc/cgo/*/_obj/**",
+ "**/.DS_Store",
+ "**/*.exe~", // go.dev/issue/23894
+ // Generated during make.bat/make.bash.
+ "src/cmd/dist/dist",
+ "src/cmd/dist/dist.exe",
+ )
+
+ // The source distribution removes files generated during the release build.
+ // See ../dist/build.go's deptab.
+ srcArch := base.Clone()
+ srcArch.Remove(
+ "bin/**",
+ "pkg/**",
+
+ // Generated during cmd/dist. See ../dist/build.go:/gentab.
+ "src/cmd/go/internal/cfg/zdefaultcc.go",
+ "src/go/build/zcgo.go",
+ "src/runtime/internal/sys/zversion.go",
+ "src/time/tzdata/zzipdata.go",
+
+ // Generated during cmd/dist by bootstrapBuildTools.
+ "src/cmd/cgo/zdefaultcc.go",
+ "src/cmd/internal/objabi/zbootstrap.go",
+ "src/internal/buildcfg/zbootstrap.go",
+
+ // Generated by earlier versions of cmd/dist .
+ "src/cmd/go/internal/cfg/zosarch.go",
+ )
+ srcArch.AddPrefix("go")
+ testSrc(srcArch)
+
+ // The binary distribution includes only a subset of bin and pkg.
+ binArch := base.Clone()
+ binArch.Filter(func(name string) bool {
+ // Discard bin/ for now, will add back later.
+ if strings.HasPrefix(name, "bin/") {
+ return false
+ }
+ // Discard most of pkg.
+ if strings.HasPrefix(name, "pkg/") {
+ // Keep pkg/include.
+ if strings.HasPrefix(name, "pkg/include/") {
+ return true
+ }
+ // Discard other pkg except pkg/tool.
+ if !strings.HasPrefix(name, "pkg/tool/") {
+ return false
+ }
+ // Inside pkg/tool, keep only $GOOS_$GOARCH.
+ if !strings.HasPrefix(name, "pkg/tool/"+goosUnderGoarch+"/") {
+ return false
+ }
+ // Inside pkg/tool/$GOOS_$GOARCH, discard helper tools.
+ switch strings.TrimSuffix(path.Base(name), ".exe") {
+ case "api", "dist", "distpack", "metadata":
+ return false
+ }
+ }
+ return true
+ })
+
+ // Add go and gofmt to bin, using cross-compiled binaries
+ // if this is a cross-compiled distribution.
+ binExes := []string{
+ "go",
+ "gofmt",
+ }
+ crossBin := "bin"
+ if goos != gohostos || goarch != gohostarch {
+ crossBin = "bin/" + goosUnderGoarch
+ }
+ for _, b := range binExes {
+ name := "bin/" + b + exe
+ src := filepath.Join(goroot, crossBin, b+exe)
+ info, err := os.Stat(src)
+ if err != nil {
+ log.Fatal(err)
+ }
+ binArch.Add(name, src, info)
+ }
+ binArch.Sort()
+ binArch.SetTime(versionTime) // fix added files
+ binArch.SetMode(mode) // fix added files
+
+ zipArch := binArch.Clone()
+ zipArch.AddPrefix("go")
+ testZip(zipArch)
+
+ // The module distribution is the binary distribution with unnecessary files removed
+ // and file names using the necessary prefix for the module.
+ modArch := binArch.Clone()
+ modArch.Remove(
+ "api/**",
+ "doc/**",
+ "misc/**",
+ "test/**",
+ )
+ modVers := modVersionPrefix + "-" + version + "." + goosDashGoarch
+ modArch.AddPrefix(modPath + "@" + modVers)
+ modArch.RenameGoMod()
+ modArch.Sort()
+ testMod(modArch)
+
+ // distpack returns the full path to name in the distpack directory.
+ distpack := func(name string) string {
+ return filepath.Join(goroot, "pkg/distpack", name)
+ }
+ if err := os.MkdirAll(filepath.Join(goroot, "pkg/distpack"), 0777); err != nil {
+ log.Fatal(err)
+ }
+
+ writeTgz(distpack(version+".src.tar.gz"), srcArch)
+
+ if goos == "windows" {
+ writeZip(distpack(version+"."+goos+"-"+goarch+".zip"), zipArch)
+ } else {
+ writeTgz(distpack(version+"."+goos+"-"+goarch+".tar.gz"), zipArch)
+ }
+
+ writeZip(distpack(modVers+".zip"), modArch)
+ writeFile(distpack(modVers+".mod"),
+ []byte(fmt.Sprintf("module %s\n", modPath)))
+ writeFile(distpack(modVers+".info"),
+ []byte(fmt.Sprintf("{%q:%q, %q:%q}\n",
+ "Version", modVers,
+ "Time", versionTime.Format(time.RFC3339))))
+}
+
+// mode computes the mode for the given file name.
+func mode(name string, _ fs.FileMode) fs.FileMode {
+ if strings.HasPrefix(name, "bin/") ||
+ strings.HasPrefix(name, "pkg/tool/") ||
+ strings.HasSuffix(name, ".bash") ||
+ strings.HasSuffix(name, ".sh") ||
+ strings.HasSuffix(name, ".pl") ||
+ strings.HasSuffix(name, ".rc") {
+ return 0o755
+ } else if ok, _ := amatch("**/go_?*_?*_exec", name); ok {
+ return 0o755
+ }
+ return 0o644
+}
+
+// readVERSION reads the VERSION file.
+// The first line of the file is the Go version.
+// Additional lines are 'key value' pairs setting other data.
+// The only valid key at the moment is 'time', which sets the modification time for file archives.
+func readVERSION(goroot string) (version string, t time.Time) {
+ data, err := os.ReadFile(filepath.Join(goroot, "VERSION"))
+ if err != nil {
+ log.Fatal(err)
+ }
+ version, rest, _ := strings.Cut(string(data), "\n")
+ for _, line := range strings.Split(rest, "\n") {
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ switch f[0] {
+ default:
+ log.Fatalf("VERSION: unexpected line: %s", line)
+ case "time":
+ if len(f) != 2 {
+ log.Fatalf("VERSION: unexpected time line: %s", line)
+ }
+ t, err = time.ParseInLocation(time.RFC3339, f[1], time.UTC)
+ if err != nil {
+ log.Fatalf("VERSION: bad time: %s", err)
+ }
+ }
+ }
+ return version, t
+}
+
+// writeFile writes a file with the given name and data or fatals.
+func writeFile(name string, data []byte) {
+ if err := os.WriteFile(name, data, 0666); err != nil {
+ log.Fatal(err)
+ }
+ reportHash(name)
+}
+
+// check panics if err is not nil. Otherwise it returns x.
+// It is only meant to be used in a function that has deferred
+// a function to recover appropriately from the panic.
+func check[T any](x T, err error) T {
+ check1(err)
+ return x
+}
+
+// check1 panics if err is not nil.
+// It is only meant to be used in a function that has deferred
+// a function to recover appropriately from the panic.
+func check1(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+// writeTgz writes the archive in tgz form to the file named name.
+func writeTgz(name string, a *Archive) {
+ out, err := os.Create(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var f File
+ defer func() {
+ if err := recover(); err != nil {
+ extra := ""
+ if f.Name != "" {
+ extra = " " + f.Name
+ }
+ log.Fatalf("writing %s%s: %v", name, extra, err)
+ }
+ }()
+
+ zw := check(gzip.NewWriterLevel(out, gzip.BestCompression))
+ tw := tar.NewWriter(zw)
+
+ // Find the mode and mtime to use for directory entries,
+ // based on the mode and mtime of the first file we see.
+ // We know that modes and mtimes are uniform across the archive.
+ var dirMode fs.FileMode
+ var mtime time.Time
+ for _, f := range a.Files {
+ dirMode = fs.ModeDir | f.Mode | (f.Mode&0444)>>2 // copy r bits down to x bits
+ mtime = f.Time
+ break
+ }
+
+ // mkdirAll ensures that the tar file contains directory
+ // entries for dir and all its parents. Some programs reading
+ // these tar files expect that. See go.dev/issue/61862.
+ haveDir := map[string]bool{".": true}
+ var mkdirAll func(string)
+ mkdirAll = func(dir string) {
+ if dir == "/" {
+ panic("mkdirAll /")
+ }
+ if haveDir[dir] {
+ return
+ }
+ haveDir[dir] = true
+ mkdirAll(path.Dir(dir))
+ df := &File{
+ Name: dir + "/",
+ Time: mtime,
+ Mode: dirMode,
+ }
+ h := check(tar.FileInfoHeader(df.Info(), ""))
+ h.Name = dir + "/"
+ if err := tw.WriteHeader(h); err != nil {
+ panic(err)
+ }
+ }
+
+ for _, f = range a.Files {
+ h := check(tar.FileInfoHeader(f.Info(), ""))
+ mkdirAll(path.Dir(f.Name))
+ h.Name = f.Name
+ if err := tw.WriteHeader(h); err != nil {
+ panic(err)
+ }
+ r := check(os.Open(f.Src))
+ check(io.Copy(tw, r))
+ check1(r.Close())
+ }
+ f.Name = ""
+ check1(tw.Close())
+ check1(zw.Close())
+ check1(out.Close())
+ reportHash(name)
+}
+
+// writeZip writes the archive in zip form to the file named name.
+func writeZip(name string, a *Archive) {
+ out, err := os.Create(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var f File
+ defer func() {
+ if err := recover(); err != nil {
+ extra := ""
+ if f.Name != "" {
+ extra = " " + f.Name
+ }
+ log.Fatalf("writing %s%s: %v", name, extra, err)
+ }
+ }()
+
+ zw := zip.NewWriter(out)
+ zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
+ return flate.NewWriter(out, flate.BestCompression)
+ })
+ for _, f = range a.Files {
+ h := check(zip.FileInfoHeader(f.Info()))
+ h.Name = f.Name
+ h.Method = zip.Deflate
+ w := check(zw.CreateHeader(h))
+ r := check(os.Open(f.Src))
+ check(io.Copy(w, r))
+ check1(r.Close())
+ }
+ f.Name = ""
+ check1(zw.Close())
+ check1(out.Close())
+ reportHash(name)
+}
+
+func reportHash(name string) {
+ f, err := os.Open(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ h := sha256.New()
+ io.Copy(h, f)
+ f.Close()
+ fmt.Printf("distpack: %x %s\n", h.Sum(nil)[:8], filepath.Base(name))
+}
diff --git a/src/cmd/distpack/test.go b/src/cmd/distpack/test.go
new file mode 100644
index 0000000..4544d72
--- /dev/null
+++ b/src/cmd/distpack/test.go
@@ -0,0 +1,170 @@
+// 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.
+
+// This file contains tests applied to the archives before they are written.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+)
+
+type testRule struct {
+ name string
+ goos string
+ exclude bool
+}
+
+var srcRules = []testRule{
+ {name: "go/VERSION"},
+ {name: "go/src/cmd/go/main.go"},
+ {name: "go/src/bytes/bytes.go"},
+ {name: "go/.DS_Store", exclude: true},
+ {name: "go/.git", exclude: true},
+ {name: "go/.gitattributes", exclude: true},
+ {name: "go/.github", exclude: true},
+ {name: "go/VERSION.cache", exclude: true},
+ {name: "go/bin/**", exclude: true},
+ {name: "go/pkg/**", exclude: true},
+ {name: "go/src/cmd/dist/dist", exclude: true},
+ {name: "go/src/cmd/dist/dist.exe", exclude: true},
+ {name: "go/src/runtime/internal/sys/zversion.go", exclude: true},
+ {name: "go/src/time/tzdata/zzipdata.go", exclude: true},
+}
+
+var zipRules = []testRule{
+ {name: "go/VERSION"},
+ {name: "go/src/cmd/go/main.go"},
+ {name: "go/src/bytes/bytes.go"},
+
+ {name: "go/.DS_Store", exclude: true},
+ {name: "go/.git", exclude: true},
+ {name: "go/.gitattributes", exclude: true},
+ {name: "go/.github", exclude: true},
+ {name: "go/VERSION.cache", exclude: true},
+ {name: "go/bin", exclude: true},
+ {name: "go/pkg", exclude: true},
+ {name: "go/src/cmd/dist/dist", exclude: true},
+ {name: "go/src/cmd/dist/dist.exe", exclude: true},
+
+ {name: "go/bin/go", goos: "linux"},
+ {name: "go/bin/go", goos: "darwin"},
+ {name: "go/bin/go", goos: "windows", exclude: true},
+ {name: "go/bin/go.exe", goos: "windows"},
+ {name: "go/bin/gofmt", goos: "linux"},
+ {name: "go/bin/gofmt", goos: "darwin"},
+ {name: "go/bin/gofmt", goos: "windows", exclude: true},
+ {name: "go/bin/gofmt.exe", goos: "windows"},
+ {name: "go/pkg/tool/*/compile", goos: "linux"},
+ {name: "go/pkg/tool/*/compile", goos: "darwin"},
+ {name: "go/pkg/tool/*/compile", goos: "windows", exclude: true},
+ {name: "go/pkg/tool/*/compile.exe", goos: "windows"},
+}
+
+var modRules = []testRule{
+ {name: "golang.org/toolchain@*/VERSION"},
+ {name: "golang.org/toolchain@*/src/cmd/go/main.go"},
+ {name: "golang.org/toolchain@*/src/bytes/bytes.go"},
+
+ {name: "golang.org/toolchain@*/.DS_Store", exclude: true},
+ {name: "golang.org/toolchain@*/.git", exclude: true},
+ {name: "golang.org/toolchain@*/.gitattributes", exclude: true},
+ {name: "golang.org/toolchain@*/.github", exclude: true},
+ {name: "golang.org/toolchain@*/VERSION.cache", exclude: true},
+ {name: "golang.org/toolchain@*/bin", exclude: true},
+ {name: "golang.org/toolchain@*/pkg", exclude: true},
+ {name: "golang.org/toolchain@*/src/cmd/dist/dist", exclude: true},
+ {name: "golang.org/toolchain@*/src/cmd/dist/dist.exe", exclude: true},
+
+ {name: "golang.org/toolchain@*/bin/go", goos: "linux"},
+ {name: "golang.org/toolchain@*/bin/go", goos: "darwin"},
+ {name: "golang.org/toolchain@*/bin/go", goos: "windows", exclude: true},
+ {name: "golang.org/toolchain@*/bin/go.exe", goos: "windows"},
+ {name: "golang.org/toolchain@*/bin/gofmt", goos: "linux"},
+ {name: "golang.org/toolchain@*/bin/gofmt", goos: "darwin"},
+ {name: "golang.org/toolchain@*/bin/gofmt", goos: "windows", exclude: true},
+ {name: "golang.org/toolchain@*/bin/gofmt.exe", goos: "windows"},
+ {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "linux"},
+ {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "darwin"},
+ {name: "golang.org/toolchain@*/pkg/tool/*/compile", goos: "windows", exclude: true},
+ {name: "golang.org/toolchain@*/pkg/tool/*/compile.exe", goos: "windows"},
+
+ // go.mod are renamed to _go.mod.
+ {name: "**/go.mod", exclude: true},
+ {name: "**/_go.mod"},
+}
+
+func testSrc(a *Archive) {
+ test("source", a, srcRules)
+
+ // Check that no generated files slip in, even if new ones are added.
+ for _, f := range a.Files {
+ if strings.HasPrefix(path.Base(f.Name), "z") {
+ data, err := os.ReadFile(filepath.Join(goroot, strings.TrimPrefix(f.Name, "go/")))
+ if err != nil {
+ log.Fatalf("checking source archive: %v", err)
+ }
+ if strings.Contains(string(data), "generated by go tool dist; DO NOT EDIT") {
+ log.Fatalf("unexpected source archive file: %s (generated by dist)", f.Name)
+ }
+ }
+ }
+}
+
+func testZip(a *Archive) { test("binary", a, zipRules) }
+func testMod(a *Archive) { test("module", a, modRules) }
+
+func test(kind string, a *Archive, rules []testRule) {
+ ok := true
+ have := make([]bool, len(rules))
+ for _, f := range a.Files {
+ for i, r := range rules {
+ if r.goos != "" && r.goos != goos {
+ continue
+ }
+ match, err := amatch(r.name, f.Name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if match {
+ if r.exclude {
+ ok = false
+ if !have[i] {
+ log.Printf("unexpected %s archive file: %s", kind, f.Name)
+ have[i] = true // silence future prints for excluded directory
+ }
+ } else {
+ have[i] = true
+ }
+ }
+ }
+ }
+ missing := false
+ for i, r := range rules {
+ if r.goos != "" && r.goos != goos {
+ continue
+ }
+ if !r.exclude && !have[i] {
+ missing = true
+ log.Printf("missing %s archive file: %s", kind, r.name)
+ }
+ }
+ if missing {
+ ok = false
+ var buf bytes.Buffer
+ for _, f := range a.Files {
+ fmt.Fprintf(&buf, "\n\t%s", f.Name)
+ }
+ log.Printf("archive contents: %d files%s", len(a.Files), buf.Bytes())
+ }
+ if !ok {
+ log.Fatalf("bad archive file")
+ }
+}