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.
5 // Distpack creates the tgz and zip files for a Go distribution.
6 // It writes into GOROOT/pkg/distpack:
8 // - a binary distribution (tgz or zip) for the current GOOS and GOARCH
9 // - a source distribution that is independent of GOOS/GOARCH
10 // - the module mod, info, and zip files for a distribution in module form
11 // (as used by GOTOOLCHAIN support in the go command).
13 // Distpack is typically invoked by the -distpack flag to make.bash.
14 // A cross-compiled distribution for goos/goarch can be built using:
16 // GOOS=goos GOARCH=goarch ./make.bash -distpack
18 // To test that the module downloads are usable with the go command:
20 // ./make.bash -distpack
21 // mkdir -p /tmp/goproxy/golang.org/toolchain/
22 // ln -sf $(pwd)/../pkg/distpack /tmp/goproxy/golang.org/toolchain/@v
23 // GOPROXY=file:///tmp/goproxy GOTOOLCHAIN=$(sed 1q ../VERSION) gotip version
25 // gotip can be replaced with an older released Go version once there is one.
26 // It just can't be the one make.bash built, because it knows it is already that
27 // version and will skip the download.
50 fmt.Fprintf(os.Stderr, "usage: distpack\n")
55 modPath = "golang.org/toolchain"
56 modVersionPrefix = "v0.0.1"
68 log.SetPrefix("distpack: ")
77 goroot = runtime.GOROOT()
79 log.Fatalf("missing $GOROOT")
81 gohostos = runtime.GOOS
82 gohostarch = runtime.GOARCH
83 goos = os.Getenv("GOOS")
87 goarch = os.Getenv("GOARCH")
91 goosUnderGoarch := goos + "_" + goarch
92 goosDashGoarch := goos + "-" + goarch
94 if goos == "windows" {
97 version, versionTime := readVERSION(goroot)
99 // Start with files from GOROOT, filtering out non-distribution files.
100 base, err := NewArchive(goroot)
104 base.SetTime(versionTime)
112 "misc/cgo/*/_obj/**",
114 "**/*.exe~", // go.dev/issue/23894
115 // Generated during make.bat/make.bash.
117 "src/cmd/dist/dist.exe",
120 // The source distribution removes files generated during the release build.
121 // See ../dist/build.go's deptab.
122 srcArch := base.Clone()
127 // Generated during cmd/dist. See ../dist/build.go:/gentab.
128 "src/cmd/go/internal/cfg/zdefaultcc.go",
129 "src/go/build/zcgo.go",
130 "src/internal/platform/zosarch.go",
131 "src/runtime/internal/sys/zversion.go",
132 "src/time/tzdata/zzipdata.go",
134 // Generated during cmd/dist by bootstrapBuildTools.
135 "src/cmd/cgo/zdefaultcc.go",
136 "src/cmd/internal/objabi/zbootstrap.go",
137 "src/internal/buildcfg/zbootstrap.go",
139 // Generated by earlier versions of cmd/dist .
140 "src/cmd/go/internal/cfg/zosarch.go",
142 srcArch.AddPrefix("go")
145 // The binary distribution includes only a subset of bin and pkg.
146 binArch := base.Clone()
147 binArch.Filter(func(name string) bool {
148 // Discard bin/ for now, will add back later.
149 if strings.HasPrefix(name, "bin/") {
152 // Discard most of pkg.
153 if strings.HasPrefix(name, "pkg/") {
155 if strings.HasPrefix(name, "pkg/include/") {
158 // Discard other pkg except pkg/tool.
159 if !strings.HasPrefix(name, "pkg/tool/") {
162 // Inside pkg/tool, keep only $GOOS_$GOARCH.
163 if !strings.HasPrefix(name, "pkg/tool/"+goosUnderGoarch+"/") {
166 // Inside pkg/tool/$GOOS_$GOARCH, discard helper tools.
167 switch strings.TrimSuffix(path.Base(name), ".exe") {
168 case "api", "dist", "distpack", "metadata":
175 // Add go and gofmt to bin, using cross-compiled binaries
176 // if this is a cross-compiled distribution.
182 if goos != gohostos || goarch != gohostarch {
183 crossBin = "bin/" + goosUnderGoarch
185 for _, b := range binExes {
186 name := "bin/" + b + exe
187 src := filepath.Join(goroot, crossBin, b+exe)
188 info, err := os.Stat(src)
192 binArch.Add(name, src, info)
195 binArch.SetTime(versionTime) // fix added files
196 binArch.SetMode(mode) // fix added files
198 zipArch := binArch.Clone()
199 zipArch.AddPrefix("go")
202 // The module distribution is the binary distribution with unnecessary files removed
203 // and file names using the necessary prefix for the module.
204 modArch := binArch.Clone()
211 modVers := modVersionPrefix + "-" + version + "." + goosDashGoarch
212 modArch.AddPrefix(modPath + "@" + modVers)
213 modArch.RenameGoMod()
217 // distpack returns the full path to name in the distpack directory.
218 distpack := func(name string) string {
219 return filepath.Join(goroot, "pkg/distpack", name)
221 if err := os.MkdirAll(filepath.Join(goroot, "pkg/distpack"), 0777); err != nil {
225 writeTgz(distpack(version+".src.tar.gz"), srcArch)
227 if goos == "windows" {
228 writeZip(distpack(version+"."+goos+"-"+goarch+".zip"), zipArch)
230 writeTgz(distpack(version+"."+goos+"-"+goarch+".tar.gz"), zipArch)
233 writeZip(distpack(modVers+".zip"), modArch)
234 writeFile(distpack(modVers+".mod"),
235 []byte(fmt.Sprintf("module %s\n", modPath)))
236 writeFile(distpack(modVers+".info"),
237 []byte(fmt.Sprintf("{%q:%q, %q:%q}\n",
239 "Time", versionTime.Format(time.RFC3339))))
242 // mode computes the mode for the given file name.
243 func mode(name string, _ fs.FileMode) fs.FileMode {
244 if strings.HasPrefix(name, "bin/") ||
245 strings.HasPrefix(name, "pkg/tool/") ||
246 strings.HasSuffix(name, ".bash") ||
247 strings.HasSuffix(name, ".sh") ||
248 strings.HasSuffix(name, ".pl") ||
249 strings.HasSuffix(name, ".rc") {
251 } else if ok, _ := amatch("**/go_?*_?*_exec", name); ok {
257 // readVERSION reads the VERSION file.
258 // The first line of the file is the Go version.
259 // Additional lines are 'key value' pairs setting other data.
260 // The only valid key at the moment is 'time', which sets the modification time for file archives.
261 func readVERSION(goroot string) (version string, t time.Time) {
262 data, err := os.ReadFile(filepath.Join(goroot, "VERSION"))
266 version, rest, _ := strings.Cut(string(data), "\n")
267 for _, line := range strings.Split(rest, "\n") {
268 f := strings.Fields(line)
274 log.Fatalf("VERSION: unexpected line: %s", line)
277 log.Fatalf("VERSION: unexpected time line: %s", line)
279 t, err = time.ParseInLocation(time.RFC3339, f[1], time.UTC)
281 log.Fatalf("VERSION: bad time: %s", err)
288 // writeFile writes a file with the given name and data or fatals.
289 func writeFile(name string, data []byte) {
290 if err := os.WriteFile(name, data, 0666); err != nil {
296 // check panics if err is not nil. Otherwise it returns x.
297 // It is only meant to be used in a function that has deferred
298 // a function to recover appropriately from the panic.
299 func check[T any](x T, err error) T {
304 // check1 panics if err is not nil.
305 // It is only meant to be used in a function that has deferred
306 // a function to recover appropriately from the panic.
307 func check1(err error) {
313 // writeTgz writes the archive in tgz form to the file named name.
314 func writeTgz(name string, a *Archive) {
315 out, err := os.Create(name)
322 if err := recover(); err != nil {
327 log.Fatalf("writing %s%s: %v", name, extra, err)
331 zw := check(gzip.NewWriterLevel(out, gzip.BestCompression))
332 tw := tar.NewWriter(zw)
333 for _, f = range a.Files {
334 h := check(tar.FileInfoHeader(f.Info(), ""))
336 if err := tw.WriteHeader(h); err != nil {
339 r := check(os.Open(f.Src))
340 check(io.Copy(tw, r))
350 // writeZip writes the archive in zip form to the file named name.
351 func writeZip(name string, a *Archive) {
352 out, err := os.Create(name)
359 if err := recover(); err != nil {
364 log.Fatalf("writing %s%s: %v", name, extra, err)
368 zw := zip.NewWriter(out)
369 zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
370 return flate.NewWriter(out, flate.BestCompression)
372 for _, f = range a.Files {
373 h := check(zip.FileInfoHeader(f.Info()))
375 h.Method = zip.Deflate
376 w := check(zw.CreateHeader(h))
377 r := check(os.Open(f.Src))
387 func reportHash(name string) {
388 f, err := os.Open(name)
395 fmt.Printf("distpack: %x %s\n", h.Sum(nil)[:8], filepath.Base(name))