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. 28package main 29 30import ( 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 "cmd/internal/telemetry/counter" 49) 50 51func usage() { 52 fmt.Fprintf(os.Stderr, "usage: distpack\n") 53 os.Exit(2) 54} 55 56const ( 57 modPath = "golang.org/toolchain" 58 modVersionPrefix = "v0.0.1" 59) 60 61var ( 62 goroot string 63 gohostos string 64 gohostarch string 65 goos string 66 goarch string 67) 68 69func main() { 70 log.SetPrefix("distpack: ") 71 log.SetFlags(0) 72 counter.Open() 73 flag.Usage = usage 74 flag.Parse() 75 counter.Inc("distpack/invocations") 76 counter.CountFlags("distpack/flag:", *flag.CommandLine) 77 if flag.NArg() != 0 { 78 usage() 79 } 80 81 // Load context. 82 goroot = runtime.GOROOT() 83 if goroot == "" { 84 log.Fatalf("missing $GOROOT") 85 } 86 gohostos = runtime.GOOS 87 gohostarch = runtime.GOARCH 88 goos = os.Getenv("GOOS") 89 if goos == "" { 90 goos = gohostos 91 } 92 goarch = os.Getenv("GOARCH") 93 if goarch == "" { 94 goarch = gohostarch 95 } 96 goosUnderGoarch := goos + "_" + goarch 97 goosDashGoarch := goos + "-" + goarch 98 exe := "" 99 if goos == "windows" { 100 exe = ".exe" 101 } 102 version, versionTime := readVERSION(goroot) 103 104 // Start with files from GOROOT, filtering out non-distribution files. 105 base, err := NewArchive(goroot) 106 if err != nil { 107 log.Fatal(err) 108 } 109 base.SetTime(versionTime) 110 base.SetMode(mode) 111 base.Remove( 112 ".git/**", 113 ".gitattributes", 114 ".github/**", 115 ".gitignore", 116 "VERSION.cache", 117 "misc/cgo/*/_obj/**", 118 "**/.DS_Store", 119 "**/*.exe~", // go.dev/issue/23894 120 // Generated during make.bat/make.bash. 121 "src/cmd/dist/dist", 122 "src/cmd/dist/dist.exe", 123 ) 124 125 // The source distribution removes files generated during the release build. 126 // See ../dist/build.go's deptab. 127 srcArch := base.Clone() 128 srcArch.Remove( 129 "bin/**", 130 "pkg/**", 131 132 // Generated during cmd/dist. See ../dist/build.go:/gentab. 133 "src/cmd/go/internal/cfg/zdefaultcc.go", 134 "src/go/build/zcgo.go", 135 "src/runtime/internal/sys/zversion.go", 136 "src/time/tzdata/zzipdata.go", 137 138 // Generated during cmd/dist by bootstrapBuildTools. 139 "src/cmd/cgo/zdefaultcc.go", 140 "src/cmd/internal/objabi/zbootstrap.go", 141 "src/internal/buildcfg/zbootstrap.go", 142 143 // Generated by earlier versions of cmd/dist . 144 "src/cmd/go/internal/cfg/zosarch.go", 145 ) 146 srcArch.AddPrefix("go") 147 testSrc(srcArch) 148 149 // The binary distribution includes only a subset of bin and pkg. 150 binArch := base.Clone() 151 binArch.Filter(func(name string) bool { 152 // Discard bin/ for now, will add back later. 153 if strings.HasPrefix(name, "bin/") { 154 return false 155 } 156 // Discard most of pkg. 157 if strings.HasPrefix(name, "pkg/") { 158 // Keep pkg/include. 159 if strings.HasPrefix(name, "pkg/include/") { 160 return true 161 } 162 // Discard other pkg except pkg/tool. 163 if !strings.HasPrefix(name, "pkg/tool/") { 164 return false 165 } 166 // Inside pkg/tool, keep only $GOOS_$GOARCH. 167 if !strings.HasPrefix(name, "pkg/tool/"+goosUnderGoarch+"/") { 168 return false 169 } 170 // Inside pkg/tool/$GOOS_$GOARCH, discard helper tools. 171 switch strings.TrimSuffix(path.Base(name), ".exe") { 172 case "api", "dist", "distpack", "metadata": 173 return false 174 } 175 } 176 return true 177 }) 178 179 // Add go and gofmt to bin, using cross-compiled binaries 180 // if this is a cross-compiled distribution. 181 binExes := []string{ 182 "go", 183 "gofmt", 184 } 185 crossBin := "bin" 186 if goos != gohostos || goarch != gohostarch { 187 crossBin = "bin/" + goosUnderGoarch 188 } 189 for _, b := range binExes { 190 name := "bin/" + b + exe 191 src := filepath.Join(goroot, crossBin, b+exe) 192 info, err := os.Stat(src) 193 if err != nil { 194 log.Fatal(err) 195 } 196 binArch.Add(name, src, info) 197 } 198 binArch.Sort() 199 binArch.SetTime(versionTime) // fix added files 200 binArch.SetMode(mode) // fix added files 201 202 zipArch := binArch.Clone() 203 zipArch.AddPrefix("go") 204 testZip(zipArch) 205 206 // The module distribution is the binary distribution with unnecessary files removed 207 // and file names using the necessary prefix for the module. 208 modArch := binArch.Clone() 209 modArch.Remove( 210 "api/**", 211 "doc/**", 212 "misc/**", 213 "test/**", 214 ) 215 modVers := modVersionPrefix + "-" + version + "." + goosDashGoarch 216 modArch.AddPrefix(modPath + "@" + modVers) 217 modArch.RenameGoMod() 218 modArch.Sort() 219 testMod(modArch) 220 221 // distpack returns the full path to name in the distpack directory. 222 distpack := func(name string) string { 223 return filepath.Join(goroot, "pkg/distpack", name) 224 } 225 if err := os.MkdirAll(filepath.Join(goroot, "pkg/distpack"), 0777); err != nil { 226 log.Fatal(err) 227 } 228 229 writeTgz(distpack(version+".src.tar.gz"), srcArch) 230 231 if goos == "windows" { 232 writeZip(distpack(version+"."+goos+"-"+goarch+".zip"), zipArch) 233 } else { 234 writeTgz(distpack(version+"."+goos+"-"+goarch+".tar.gz"), zipArch) 235 } 236 237 writeZip(distpack(modVers+".zip"), modArch) 238 writeFile(distpack(modVers+".mod"), 239 []byte(fmt.Sprintf("module %s\n", modPath))) 240 writeFile(distpack(modVers+".info"), 241 []byte(fmt.Sprintf("{%q:%q, %q:%q}\n", 242 "Version", modVers, 243 "Time", versionTime.Format(time.RFC3339)))) 244} 245 246// mode computes the mode for the given file name. 247func mode(name string, _ fs.FileMode) fs.FileMode { 248 if strings.HasPrefix(name, "bin/") || 249 strings.HasPrefix(name, "pkg/tool/") || 250 strings.HasSuffix(name, ".bash") || 251 strings.HasSuffix(name, ".sh") || 252 strings.HasSuffix(name, ".pl") || 253 strings.HasSuffix(name, ".rc") { 254 return 0o755 255 } else if ok, _ := amatch("**/go_?*_?*_exec", name); ok { 256 return 0o755 257 } 258 return 0o644 259} 260 261// readVERSION reads the VERSION file. 262// The first line of the file is the Go version. 263// Additional lines are 'key value' pairs setting other data. 264// The only valid key at the moment is 'time', which sets the modification time for file archives. 265func readVERSION(goroot string) (version string, t time.Time) { 266 data, err := os.ReadFile(filepath.Join(goroot, "VERSION")) 267 if err != nil { 268 log.Fatal(err) 269 } 270 version, rest, _ := strings.Cut(string(data), "\n") 271 for _, line := range strings.Split(rest, "\n") { 272 f := strings.Fields(line) 273 if len(f) == 0 { 274 continue 275 } 276 switch f[0] { 277 default: 278 log.Fatalf("VERSION: unexpected line: %s", line) 279 case "time": 280 if len(f) != 2 { 281 log.Fatalf("VERSION: unexpected time line: %s", line) 282 } 283 t, err = time.ParseInLocation(time.RFC3339, f[1], time.UTC) 284 if err != nil { 285 log.Fatalf("VERSION: bad time: %s", err) 286 } 287 } 288 } 289 return version, t 290} 291 292// writeFile writes a file with the given name and data or fatals. 293func writeFile(name string, data []byte) { 294 if err := os.WriteFile(name, data, 0666); err != nil { 295 log.Fatal(err) 296 } 297 reportHash(name) 298} 299 300// check panics if err is not nil. Otherwise it returns x. 301// It is only meant to be used in a function that has deferred 302// a function to recover appropriately from the panic. 303func check[T any](x T, err error) T { 304 check1(err) 305 return x 306} 307 308// check1 panics if err is not nil. 309// It is only meant to be used in a function that has deferred 310// a function to recover appropriately from the panic. 311func check1(err error) { 312 if err != nil { 313 panic(err) 314 } 315} 316 317// writeTgz writes the archive in tgz form to the file named name. 318func writeTgz(name string, a *Archive) { 319 out, err := os.Create(name) 320 if err != nil { 321 log.Fatal(err) 322 } 323 324 var f File 325 defer func() { 326 if err := recover(); err != nil { 327 extra := "" 328 if f.Name != "" { 329 extra = " " + f.Name 330 } 331 log.Fatalf("writing %s%s: %v", name, extra, err) 332 } 333 }() 334 335 zw := check(gzip.NewWriterLevel(out, gzip.BestCompression)) 336 tw := tar.NewWriter(zw) 337 338 // Find the mode and mtime to use for directory entries, 339 // based on the mode and mtime of the first file we see. 340 // We know that modes and mtimes are uniform across the archive. 341 var dirMode fs.FileMode 342 var mtime time.Time 343 for _, f := range a.Files { 344 dirMode = fs.ModeDir | f.Mode | (f.Mode&0444)>>2 // copy r bits down to x bits 345 mtime = f.Time 346 break 347 } 348 349 // mkdirAll ensures that the tar file contains directory 350 // entries for dir and all its parents. Some programs reading 351 // these tar files expect that. See go.dev/issue/61862. 352 haveDir := map[string]bool{".": true} 353 var mkdirAll func(string) 354 mkdirAll = func(dir string) { 355 if dir == "/" { 356 panic("mkdirAll /") 357 } 358 if haveDir[dir] { 359 return 360 } 361 haveDir[dir] = true 362 mkdirAll(path.Dir(dir)) 363 df := &File{ 364 Name: dir + "/", 365 Time: mtime, 366 Mode: dirMode, 367 } 368 h := check(tar.FileInfoHeader(df.Info(), "")) 369 h.Name = dir + "/" 370 if err := tw.WriteHeader(h); err != nil { 371 panic(err) 372 } 373 } 374 375 for _, f = range a.Files { 376 h := check(tar.FileInfoHeader(f.Info(), "")) 377 mkdirAll(path.Dir(f.Name)) 378 h.Name = f.Name 379 if err := tw.WriteHeader(h); err != nil { 380 panic(err) 381 } 382 r := check(os.Open(f.Src)) 383 check(io.Copy(tw, r)) 384 check1(r.Close()) 385 } 386 f.Name = "" 387 check1(tw.Close()) 388 check1(zw.Close()) 389 check1(out.Close()) 390 reportHash(name) 391} 392 393// writeZip writes the archive in zip form to the file named name. 394func writeZip(name string, a *Archive) { 395 out, err := os.Create(name) 396 if err != nil { 397 log.Fatal(err) 398 } 399 400 var f File 401 defer func() { 402 if err := recover(); err != nil { 403 extra := "" 404 if f.Name != "" { 405 extra = " " + f.Name 406 } 407 log.Fatalf("writing %s%s: %v", name, extra, err) 408 } 409 }() 410 411 zw := zip.NewWriter(out) 412 zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { 413 return flate.NewWriter(out, flate.BestCompression) 414 }) 415 for _, f = range a.Files { 416 h := check(zip.FileInfoHeader(f.Info())) 417 h.Name = f.Name 418 h.Method = zip.Deflate 419 w := check(zw.CreateHeader(h)) 420 r := check(os.Open(f.Src)) 421 check(io.Copy(w, r)) 422 check1(r.Close()) 423 } 424 f.Name = "" 425 check1(zw.Close()) 426 check1(out.Close()) 427 reportHash(name) 428} 429 430func reportHash(name string) { 431 f, err := os.Open(name) 432 if err != nil { 433 log.Fatal(err) 434 } 435 h := sha256.New() 436 io.Copy(h, f) 437 f.Close() 438 fmt.Printf("distpack: %x %s\n", h.Sum(nil)[:8], filepath.Base(name)) 439} 440