]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/distpack/pack.go
6867ac17c2abe998391fcb391ca6fdb47ca6475c
[gostls13.git] / src / cmd / distpack / pack.go
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 // Distpack creates the tgz and zip files for a Go distribution.
6 // It writes into GOROOT/pkg/distpack:
7 //
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).
12 //
13 // Distpack is typically invoked by the -distpack flag to make.bash.
14 // A cross-compiled distribution for goos/goarch can be built using:
15 //
16 //      GOOS=goos GOARCH=goarch ./make.bash -distpack
17 //
18 // To test that the module downloads are usable with the go command:
19 //
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
24 //
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.
28 package main
29
30 import (
31         "archive/tar"
32         "archive/zip"
33         "compress/flate"
34         "compress/gzip"
35         "crypto/sha256"
36         "flag"
37         "fmt"
38         "io"
39         "io/fs"
40         "log"
41         "os"
42         "path"
43         "path/filepath"
44         "runtime"
45         "strings"
46         "time"
47 )
48
49 func usage() {
50         fmt.Fprintf(os.Stderr, "usage: distpack\n")
51         os.Exit(2)
52 }
53
54 const (
55         modPath          = "golang.org/toolchain"
56         modVersionPrefix = "v0.0.1"
57 )
58
59 var (
60         goroot     string
61         gohostos   string
62         gohostarch string
63         goos       string
64         goarch     string
65 )
66
67 func main() {
68         log.SetPrefix("distpack: ")
69         log.SetFlags(0)
70         flag.Usage = usage
71         flag.Parse()
72         if flag.NArg() != 0 {
73                 usage()
74         }
75
76         // Load context.
77         goroot = runtime.GOROOT()
78         if goroot == "" {
79                 log.Fatalf("missing $GOROOT")
80         }
81         gohostos = runtime.GOOS
82         gohostarch = runtime.GOARCH
83         goos = os.Getenv("GOOS")
84         if goos == "" {
85                 goos = gohostos
86         }
87         goarch = os.Getenv("GOARCH")
88         if goarch == "" {
89                 goarch = gohostarch
90         }
91         goosUnderGoarch := goos + "_" + goarch
92         goosDashGoarch := goos + "-" + goarch
93         exe := ""
94         if goos == "windows" {
95                 exe = ".exe"
96         }
97         version, versionTime := readVERSION(goroot)
98
99         // Start with files from GOROOT, filtering out non-distribution files.
100         base, err := NewArchive(goroot)
101         if err != nil {
102                 log.Fatal(err)
103         }
104         base.SetTime(versionTime)
105         base.SetMode(mode)
106         base.Remove(
107                 ".git/**",
108                 ".gitattributes",
109                 ".github/**",
110                 ".gitignore",
111                 "VERSION.cache",
112                 "misc/cgo/*/_obj/**",
113                 "**/.DS_Store",
114                 "**/*.exe~", // go.dev/issue/23894
115                 // Generated during make.bat/make.bash.
116                 "src/cmd/dist/dist",
117                 "src/cmd/dist/dist.exe",
118         )
119
120         // The source distribution removes files generated during the release build.
121         // See ../dist/build.go's deptab.
122         srcArch := base.Clone()
123         srcArch.Remove(
124                 "bin/**",
125                 "pkg/**",
126
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",
133
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",
138
139                 // Generated by earlier versions of cmd/dist .
140                 "src/cmd/go/internal/cfg/zosarch.go",
141         )
142         srcArch.AddPrefix("go")
143         testSrc(srcArch)
144
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/") {
150                         return false
151                 }
152                 // Discard most of pkg.
153                 if strings.HasPrefix(name, "pkg/") {
154                         // Keep pkg/include.
155                         if strings.HasPrefix(name, "pkg/include/") {
156                                 return true
157                         }
158                         // Discard other pkg except pkg/tool.
159                         if !strings.HasPrefix(name, "pkg/tool/") {
160                                 return false
161                         }
162                         // Inside pkg/tool, keep only $GOOS_$GOARCH.
163                         if !strings.HasPrefix(name, "pkg/tool/"+goosUnderGoarch+"/") {
164                                 return false
165                         }
166                         // Inside pkg/tool/$GOOS_$GOARCH, discard helper tools.
167                         switch strings.TrimSuffix(path.Base(name), ".exe") {
168                         case "api", "dist", "distpack", "metadata":
169                                 return false
170                         }
171                 }
172                 return true
173         })
174
175         // Add go and gofmt to bin, using cross-compiled binaries
176         // if this is a cross-compiled distribution.
177         binExes := []string{
178                 "go",
179                 "gofmt",
180         }
181         crossBin := "bin"
182         if goos != gohostos || goarch != gohostarch {
183                 crossBin = "bin/" + goosUnderGoarch
184         }
185         for _, b := range binExes {
186                 name := "bin/" + b + exe
187                 src := filepath.Join(goroot, crossBin, b+exe)
188                 info, err := os.Stat(src)
189                 if err != nil {
190                         log.Fatal(err)
191                 }
192                 binArch.Add(name, src, info)
193         }
194         binArch.Sort()
195         binArch.SetTime(versionTime) // fix added files
196         binArch.SetMode(mode)        // fix added files
197
198         zipArch := binArch.Clone()
199         zipArch.AddPrefix("go")
200         testZip(zipArch)
201
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()
205         modArch.Remove(
206                 "api/**",
207                 "doc/**",
208                 "misc/**",
209                 "test/**",
210         )
211         modVers := modVersionPrefix + "-" + version + "." + goosDashGoarch
212         modArch.AddPrefix(modPath + "@" + modVers)
213         modArch.RenameGoMod()
214         modArch.Sort()
215         testMod(modArch)
216
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)
220         }
221         if err := os.MkdirAll(filepath.Join(goroot, "pkg/distpack"), 0777); err != nil {
222                 log.Fatal(err)
223         }
224
225         writeTgz(distpack(version+".src.tar.gz"), srcArch)
226
227         if goos == "windows" {
228                 writeZip(distpack(version+"."+goos+"-"+goarch+".zip"), zipArch)
229         } else {
230                 writeTgz(distpack(version+"."+goos+"-"+goarch+".tar.gz"), zipArch)
231         }
232
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",
238                         "Version", modVers,
239                         "Time", versionTime.Format(time.RFC3339))))
240 }
241
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") {
250                 return 0o755
251         } else if ok, _ := amatch("**/go_?*_?*_exec", name); ok {
252                 return 0o755
253         }
254         return 0o644
255 }
256
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"))
263         if err != nil {
264                 log.Fatal(err)
265         }
266         version, rest, _ := strings.Cut(string(data), "\n")
267         for _, line := range strings.Split(rest, "\n") {
268                 f := strings.Fields(line)
269                 if len(f) == 0 {
270                         continue
271                 }
272                 switch f[0] {
273                 default:
274                         log.Fatalf("VERSION: unexpected line: %s", line)
275                 case "time":
276                         if len(f) != 2 {
277                                 log.Fatalf("VERSION: unexpected time line: %s", line)
278                         }
279                         t, err = time.ParseInLocation(time.RFC3339, f[1], time.UTC)
280                         if err != nil {
281                                 log.Fatalf("VERSION: bad time: %s", err)
282                         }
283                 }
284         }
285         return version, t
286 }
287
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 {
291                 log.Fatal(err)
292         }
293         reportHash(name)
294 }
295
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 {
300         check1(err)
301         return x
302 }
303
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) {
308         if err != nil {
309                 panic(err)
310         }
311 }
312
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)
316         if err != nil {
317                 log.Fatal(err)
318         }
319
320         var f File
321         defer func() {
322                 if err := recover(); err != nil {
323                         extra := ""
324                         if f.Name != "" {
325                                 extra = " " + f.Name
326                         }
327                         log.Fatalf("writing %s%s: %v", name, extra, err)
328                 }
329         }()
330
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(), ""))
335                 h.Name = f.Name
336                 if err := tw.WriteHeader(h); err != nil {
337                         panic(err)
338                 }
339                 r := check(os.Open(f.Src))
340                 check(io.Copy(tw, r))
341                 check1(r.Close())
342         }
343         f.Name = ""
344         check1(tw.Close())
345         check1(zw.Close())
346         check1(out.Close())
347         reportHash(name)
348 }
349
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)
353         if err != nil {
354                 log.Fatal(err)
355         }
356
357         var f File
358         defer func() {
359                 if err := recover(); err != nil {
360                         extra := ""
361                         if f.Name != "" {
362                                 extra = " " + f.Name
363                         }
364                         log.Fatalf("writing %s%s: %v", name, extra, err)
365                 }
366         }()
367
368         zw := zip.NewWriter(out)
369         zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
370                 return flate.NewWriter(out, flate.BestCompression)
371         })
372         for _, f = range a.Files {
373                 h := check(zip.FileInfoHeader(f.Info()))
374                 h.Name = f.Name
375                 h.Method = zip.Deflate
376                 w := check(zw.CreateHeader(h))
377                 r := check(os.Open(f.Src))
378                 check(io.Copy(w, r))
379                 check1(r.Close())
380         }
381         f.Name = ""
382         check1(zw.Close())
383         check1(out.Close())
384         reportHash(name)
385 }
386
387 func reportHash(name string) {
388         f, err := os.Open(name)
389         if err != nil {
390                 log.Fatal(err)
391         }
392         h := sha256.New()
393         io.Copy(h, f)
394         f.Close()
395         fmt.Printf("distpack: %x %s\n", h.Sum(nil)[:8], filepath.Base(name))
396 }