summaryrefslogtreecommitdiffstats
path: root/src/cmd/dist/build.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/dist/build.go')
-rw-r--r--src/cmd/dist/build.go1952
1 files changed, 1952 insertions, 0 deletions
diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go
new file mode 100644
index 0000000..32e59b4
--- /dev/null
+++ b/src/cmd/dist/build.go
@@ -0,0 +1,1952 @@
+// Copyright 2012 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 (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+)
+
+// Initialization for any invocation.
+
+// The usual variables.
+var (
+ goarch string
+ gorootBin string
+ gorootBinGo string
+ gohostarch string
+ gohostos string
+ goos string
+ goarm string
+ go386 string
+ goamd64 string
+ gomips string
+ gomips64 string
+ goppc64 string
+ goroot string
+ goroot_final string
+ goextlinkenabled string
+ gogcflags string // For running built compiler
+ goldflags string
+ goexperiment string
+ workdir string
+ tooldir string
+ oldgoos string
+ oldgoarch string
+ oldgocache string
+ exe string
+ defaultcc map[string]string
+ defaultcxx map[string]string
+ defaultpkgconfig string
+ defaultldso string
+
+ rebuildall bool
+ noOpt bool
+ isRelease bool
+
+ vflag int // verbosity
+)
+
+// The known architectures.
+var okgoarch = []string{
+ "386",
+ "amd64",
+ "arm",
+ "arm64",
+ "loong64",
+ "mips",
+ "mipsle",
+ "mips64",
+ "mips64le",
+ "ppc64",
+ "ppc64le",
+ "riscv64",
+ "s390x",
+ "sparc64",
+ "wasm",
+}
+
+// The known operating systems.
+var okgoos = []string{
+ "darwin",
+ "dragonfly",
+ "illumos",
+ "ios",
+ "js",
+ "wasip1",
+ "linux",
+ "android",
+ "solaris",
+ "freebsd",
+ "nacl", // keep;
+ "netbsd",
+ "openbsd",
+ "plan9",
+ "windows",
+ "aix",
+}
+
+// find reports the first index of p in l[0:n], or else -1.
+func find(p string, l []string) int {
+ for i, s := range l {
+ if p == s {
+ return i
+ }
+ }
+ return -1
+}
+
+// xinit handles initialization of the various global state, like goroot and goarch.
+func xinit() {
+ b := os.Getenv("GOROOT")
+ if b == "" {
+ fatalf("$GOROOT must be set")
+ }
+ goroot = filepath.Clean(b)
+ gorootBin = pathf("%s/bin", goroot)
+
+ // Don't run just 'go' because the build infrastructure
+ // runs cmd/dist inside go/bin often, and on Windows
+ // it will be found in the current directory and refuse to exec.
+ // All exec calls rewrite "go" into gorootBinGo.
+ gorootBinGo = pathf("%s/bin/go", goroot)
+
+ b = os.Getenv("GOROOT_FINAL")
+ if b == "" {
+ b = goroot
+ }
+ goroot_final = b
+
+ b = os.Getenv("GOOS")
+ if b == "" {
+ b = gohostos
+ }
+ goos = b
+ if find(goos, okgoos) < 0 {
+ fatalf("unknown $GOOS %s", goos)
+ }
+
+ b = os.Getenv("GOARM")
+ if b == "" {
+ b = xgetgoarm()
+ }
+ goarm = b
+
+ b = os.Getenv("GO386")
+ if b == "" {
+ b = "sse2"
+ }
+ go386 = b
+
+ b = os.Getenv("GOAMD64")
+ if b == "" {
+ b = "v1"
+ }
+ goamd64 = b
+
+ b = os.Getenv("GOMIPS")
+ if b == "" {
+ b = "hardfloat"
+ }
+ gomips = b
+
+ b = os.Getenv("GOMIPS64")
+ if b == "" {
+ b = "hardfloat"
+ }
+ gomips64 = b
+
+ b = os.Getenv("GOPPC64")
+ if b == "" {
+ b = "power8"
+ }
+ goppc64 = b
+
+ if p := pathf("%s/src/all.bash", goroot); !isfile(p) {
+ fatalf("$GOROOT is not set correctly or not exported\n"+
+ "\tGOROOT=%s\n"+
+ "\t%s does not exist", goroot, p)
+ }
+
+ b = os.Getenv("GOHOSTARCH")
+ if b != "" {
+ gohostarch = b
+ }
+ if find(gohostarch, okgoarch) < 0 {
+ fatalf("unknown $GOHOSTARCH %s", gohostarch)
+ }
+
+ b = os.Getenv("GOARCH")
+ if b == "" {
+ b = gohostarch
+ }
+ goarch = b
+ if find(goarch, okgoarch) < 0 {
+ fatalf("unknown $GOARCH %s", goarch)
+ }
+
+ b = os.Getenv("GO_EXTLINK_ENABLED")
+ if b != "" {
+ if b != "0" && b != "1" {
+ fatalf("unknown $GO_EXTLINK_ENABLED %s", b)
+ }
+ goextlinkenabled = b
+ }
+
+ goexperiment = os.Getenv("GOEXPERIMENT")
+ // TODO(mdempsky): Validate known experiments?
+
+ gogcflags = os.Getenv("BOOT_GO_GCFLAGS")
+ goldflags = os.Getenv("BOOT_GO_LDFLAGS")
+
+ defaultcc = compilerEnv("CC", "")
+ defaultcxx = compilerEnv("CXX", "")
+
+ b = os.Getenv("PKG_CONFIG")
+ if b == "" {
+ b = "pkg-config"
+ }
+ defaultpkgconfig = b
+
+ defaultldso = os.Getenv("GO_LDSO")
+
+ // For tools being invoked but also for os.ExpandEnv.
+ os.Setenv("GO386", go386)
+ os.Setenv("GOAMD64", goamd64)
+ os.Setenv("GOARCH", goarch)
+ os.Setenv("GOARM", goarm)
+ os.Setenv("GOHOSTARCH", gohostarch)
+ os.Setenv("GOHOSTOS", gohostos)
+ os.Setenv("GOOS", goos)
+ os.Setenv("GOMIPS", gomips)
+ os.Setenv("GOMIPS64", gomips64)
+ os.Setenv("GOPPC64", goppc64)
+ os.Setenv("GOROOT", goroot)
+ os.Setenv("GOROOT_FINAL", goroot_final)
+
+ // Set GOBIN to GOROOT/bin. The meaning of GOBIN has drifted over time
+ // (see https://go.dev/issue/3269, https://go.dev/cl/183058,
+ // https://go.dev/issue/31576). Since we want binaries installed by 'dist' to
+ // always go to GOROOT/bin anyway.
+ os.Setenv("GOBIN", gorootBin)
+
+ // Make the environment more predictable.
+ os.Setenv("LANG", "C")
+ os.Setenv("LANGUAGE", "en_US.UTF8")
+ os.Unsetenv("GO111MODULE")
+ os.Setenv("GOENV", "off")
+ os.Unsetenv("GOFLAGS")
+ os.Setenv("GOWORK", "off")
+
+ workdir = xworkdir()
+ if err := os.WriteFile(pathf("%s/go.mod", workdir), []byte("module bootstrap"), 0666); err != nil {
+ fatalf("cannot write stub go.mod: %s", err)
+ }
+ xatexit(rmworkdir)
+
+ tooldir = pathf("%s/pkg/tool/%s_%s", goroot, gohostos, gohostarch)
+
+ goversion := findgoversion()
+ isRelease = strings.HasPrefix(goversion, "release.") || strings.HasPrefix(goversion, "go")
+}
+
+// compilerEnv returns a map from "goos/goarch" to the
+// compiler setting to use for that platform.
+// The entry for key "" covers any goos/goarch not explicitly set in the map.
+// For example, compilerEnv("CC", "gcc") returns the C compiler settings
+// read from $CC, defaulting to gcc.
+//
+// The result is a map because additional environment variables
+// can be set to change the compiler based on goos/goarch settings.
+// The following applies to all envNames but CC is assumed to simplify
+// the presentation.
+//
+// If no environment variables are set, we use def for all goos/goarch.
+// $CC, if set, applies to all goos/goarch but is overridden by the following.
+// $CC_FOR_TARGET, if set, applies to all goos/goarch except gohostos/gohostarch,
+// but is overridden by the following.
+// If gohostos=goos and gohostarch=goarch, then $CC_FOR_TARGET applies even for gohostos/gohostarch.
+// $CC_FOR_goos_goarch, if set, applies only to goos/goarch.
+func compilerEnv(envName, def string) map[string]string {
+ m := map[string]string{"": def}
+
+ if env := os.Getenv(envName); env != "" {
+ m[""] = env
+ }
+ if env := os.Getenv(envName + "_FOR_TARGET"); env != "" {
+ if gohostos != goos || gohostarch != goarch {
+ m[gohostos+"/"+gohostarch] = m[""]
+ }
+ m[""] = env
+ }
+
+ for _, goos := range okgoos {
+ for _, goarch := range okgoarch {
+ if env := os.Getenv(envName + "_FOR_" + goos + "_" + goarch); env != "" {
+ m[goos+"/"+goarch] = env
+ }
+ }
+ }
+
+ return m
+}
+
+// clangos lists the operating systems where we prefer clang to gcc.
+var clangos = []string{
+ "darwin", "ios", // macOS 10.9 and later require clang
+ "freebsd", // FreeBSD 10 and later do not ship gcc
+ "openbsd", // OpenBSD ships with GCC 4.2, which is now quite old.
+}
+
+// compilerEnvLookup returns the compiler settings for goos/goarch in map m.
+// kind is "CC" or "CXX".
+func compilerEnvLookup(kind string, m map[string]string, goos, goarch string) string {
+ if !needCC() {
+ return ""
+ }
+ if cc := m[goos+"/"+goarch]; cc != "" {
+ return cc
+ }
+ if cc := m[""]; cc != "" {
+ return cc
+ }
+ for _, os := range clangos {
+ if goos == os {
+ if kind == "CXX" {
+ return "clang++"
+ }
+ return "clang"
+ }
+ }
+ if kind == "CXX" {
+ return "g++"
+ }
+ return "gcc"
+}
+
+// rmworkdir deletes the work directory.
+func rmworkdir() {
+ if vflag > 1 {
+ errprintf("rm -rf %s\n", workdir)
+ }
+ xremoveall(workdir)
+}
+
+// Remove trailing spaces.
+func chomp(s string) string {
+ return strings.TrimRight(s, " \t\r\n")
+}
+
+// findgoversion determines the Go version to use in the version string.
+// It also parses any other metadata found in the version file.
+func findgoversion() string {
+ // The $GOROOT/VERSION file takes priority, for distributions
+ // without the source repo.
+ path := pathf("%s/VERSION", goroot)
+ if isfile(path) {
+ b := chomp(readfile(path))
+
+ // Starting in Go 1.21 the VERSION file starts with the
+ // version on a line by itself but then can contain other
+ // metadata about the release, one item per line.
+ if i := strings.Index(b, "\n"); i >= 0 {
+ rest := b[i+1:]
+ b = chomp(b[:i])
+ for _, line := range strings.Split(rest, "\n") {
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ switch f[0] {
+ default:
+ fatalf("VERSION: unexpected line: %s", line)
+ case "time":
+ if len(f) != 2 {
+ fatalf("VERSION: unexpected time line: %s", line)
+ }
+ _, err := time.Parse(time.RFC3339, f[1])
+ if err != nil {
+ fatalf("VERSION: bad time: %s", err)
+ }
+ }
+ }
+ }
+
+ // Commands such as "dist version > VERSION" will cause
+ // the shell to create an empty VERSION file and set dist's
+ // stdout to its fd. dist in turn looks at VERSION and uses
+ // its content if available, which is empty at this point.
+ // Only use the VERSION file if it is non-empty.
+ if b != "" {
+ return b
+ }
+ }
+
+ // The $GOROOT/VERSION.cache file is a cache to avoid invoking
+ // git every time we run this command. Unlike VERSION, it gets
+ // deleted by the clean command.
+ path = pathf("%s/VERSION.cache", goroot)
+ if isfile(path) {
+ return chomp(readfile(path))
+ }
+
+ // Show a nicer error message if this isn't a Git repo.
+ if !isGitRepo() {
+ fatalf("FAILED: not a Git repo; must put a VERSION file in $GOROOT")
+ }
+
+ // Otherwise, use Git.
+ //
+ // Include 1.x base version, hash, and date in the version.
+ //
+ // Note that we lightly parse internal/goversion/goversion.go to
+ // obtain the base version. We can't just import the package,
+ // because cmd/dist is built with a bootstrap GOROOT which could
+ // be an entirely different version of Go. We assume
+ // that the file contains "const Version = <Integer>".
+ goversionSource := readfile(pathf("%s/src/internal/goversion/goversion.go", goroot))
+ m := regexp.MustCompile(`(?m)^const Version = (\d+)`).FindStringSubmatch(goversionSource)
+ if m == nil {
+ fatalf("internal/goversion/goversion.go does not contain 'const Version = ...'")
+ }
+ version := fmt.Sprintf("devel go1.%s-", m[1])
+ version += chomp(run(goroot, CheckExit, "git", "log", "-n", "1", "--format=format:%h %cd", "HEAD"))
+
+ // Cache version.
+ writefile(version, path, 0)
+
+ return version
+}
+
+// isGitRepo reports whether the working directory is inside a Git repository.
+func isGitRepo() bool {
+ // NB: simply checking the exit code of `git rev-parse --git-dir` would
+ // suffice here, but that requires deviating from the infrastructure
+ // provided by `run`.
+ gitDir := chomp(run(goroot, 0, "git", "rev-parse", "--git-dir"))
+ if !filepath.IsAbs(gitDir) {
+ gitDir = filepath.Join(goroot, gitDir)
+ }
+ return isdir(gitDir)
+}
+
+/*
+ * Initial tree setup.
+ */
+
+// The old tools that no longer live in $GOBIN or $GOROOT/bin.
+var oldtool = []string{
+ "5a", "5c", "5g", "5l",
+ "6a", "6c", "6g", "6l",
+ "8a", "8c", "8g", "8l",
+ "9a", "9c", "9g", "9l",
+ "6cov",
+ "6nm",
+ "6prof",
+ "cgo",
+ "ebnflint",
+ "goapi",
+ "gofix",
+ "goinstall",
+ "gomake",
+ "gopack",
+ "gopprof",
+ "gotest",
+ "gotype",
+ "govet",
+ "goyacc",
+ "quietgcc",
+}
+
+// Unreleased directories (relative to $GOROOT) that should
+// not be in release branches.
+var unreleased = []string{
+ "src/cmd/newlink",
+ "src/cmd/objwriter",
+ "src/debug/goobj",
+ "src/old",
+}
+
+// setup sets up the tree for the initial build.
+func setup() {
+ // Create bin directory.
+ if p := pathf("%s/bin", goroot); !isdir(p) {
+ xmkdir(p)
+ }
+
+ // Create package directory.
+ if p := pathf("%s/pkg", goroot); !isdir(p) {
+ xmkdir(p)
+ }
+
+ goosGoarch := pathf("%s/pkg/%s_%s", goroot, gohostos, gohostarch)
+ if rebuildall {
+ xremoveall(goosGoarch)
+ }
+ xmkdirall(goosGoarch)
+ xatexit(func() {
+ if files := xreaddir(goosGoarch); len(files) == 0 {
+ xremove(goosGoarch)
+ }
+ })
+
+ if goos != gohostos || goarch != gohostarch {
+ p := pathf("%s/pkg/%s_%s", goroot, goos, goarch)
+ if rebuildall {
+ xremoveall(p)
+ }
+ xmkdirall(p)
+ }
+
+ // Create object directory.
+ // We used to use it for C objects.
+ // Now we use it for the build cache, to separate dist's cache
+ // from any other cache the user might have, and for the location
+ // to build the bootstrap versions of the standard library.
+ obj := pathf("%s/pkg/obj", goroot)
+ if !isdir(obj) {
+ xmkdir(obj)
+ }
+ xatexit(func() { xremove(obj) })
+
+ // Create build cache directory.
+ objGobuild := pathf("%s/pkg/obj/go-build", goroot)
+ if rebuildall {
+ xremoveall(objGobuild)
+ }
+ xmkdirall(objGobuild)
+ xatexit(func() { xremoveall(objGobuild) })
+
+ // Create directory for bootstrap versions of standard library .a files.
+ objGoBootstrap := pathf("%s/pkg/obj/go-bootstrap", goroot)
+ if rebuildall {
+ xremoveall(objGoBootstrap)
+ }
+ xmkdirall(objGoBootstrap)
+ xatexit(func() { xremoveall(objGoBootstrap) })
+
+ // Create tool directory.
+ // We keep it in pkg/, just like the object directory above.
+ if rebuildall {
+ xremoveall(tooldir)
+ }
+ xmkdirall(tooldir)
+
+ // Remove tool binaries from before the tool/gohostos_gohostarch
+ xremoveall(pathf("%s/bin/tool", goroot))
+
+ // Remove old pre-tool binaries.
+ for _, old := range oldtool {
+ xremove(pathf("%s/bin/%s", goroot, old))
+ }
+
+ // Special release-specific setup.
+ if isRelease {
+ // Make sure release-excluded things are excluded.
+ for _, dir := range unreleased {
+ if p := pathf("%s/%s", goroot, dir); isdir(p) {
+ fatalf("%s should not exist in release build", p)
+ }
+ }
+ }
+}
+
+/*
+ * Tool building
+ */
+
+// mustLinkExternal is a copy of internal/platform.MustLinkExternal,
+// duplicated here to avoid version skew in the MustLinkExternal function
+// during bootstrapping.
+func mustLinkExternal(goos, goarch string, cgoEnabled bool) bool {
+ if cgoEnabled {
+ switch goarch {
+ case "loong64", "mips", "mipsle", "mips64", "mips64le":
+ // Internally linking cgo is incomplete on some architectures.
+ // https://golang.org/issue/14449
+ return true
+ case "arm64":
+ if goos == "windows" {
+ // windows/arm64 internal linking is not implemented.
+ return true
+ }
+ case "ppc64":
+ // Big Endian PPC64 cgo internal linking is not implemented for aix or linux.
+ if goos == "aix" || goos == "linux" {
+ return true
+ }
+ }
+
+ switch goos {
+ case "android":
+ return true
+ case "dragonfly":
+ // It seems that on Dragonfly thread local storage is
+ // set up by the dynamic linker, so internal cgo linking
+ // doesn't work. Test case is "go test runtime/cgo".
+ return true
+ }
+ }
+
+ switch goos {
+ case "android":
+ if goarch != "arm64" {
+ return true
+ }
+ case "ios":
+ if goarch == "arm64" {
+ return true
+ }
+ }
+ return false
+}
+
+// depsuffix records the allowed suffixes for source files.
+var depsuffix = []string{
+ ".s",
+ ".go",
+}
+
+// gentab records how to generate some trivial files.
+// Files listed here should also be listed in ../distpack/pack.go's srcArch.Remove list.
+var gentab = []struct {
+ pkg string // Relative to $GOROOT/src
+ file string
+ gen func(dir, file string)
+}{
+ {"go/build", "zcgo.go", mkzcgo},
+ {"cmd/go/internal/cfg", "zdefaultcc.go", mkzdefaultcc},
+ {"runtime/internal/sys", "zversion.go", mkzversion},
+ {"time/tzdata", "zzipdata.go", mktzdata},
+}
+
+// installed maps from a dir name (as given to install) to a chan
+// closed when the dir's package is installed.
+var installed = make(map[string]chan struct{})
+var installedMu sync.Mutex
+
+func install(dir string) {
+ <-startInstall(dir)
+}
+
+func startInstall(dir string) chan struct{} {
+ installedMu.Lock()
+ ch := installed[dir]
+ if ch == nil {
+ ch = make(chan struct{})
+ installed[dir] = ch
+ go runInstall(dir, ch)
+ }
+ installedMu.Unlock()
+ return ch
+}
+
+// runInstall installs the library, package, or binary associated with pkg,
+// which is relative to $GOROOT/src.
+func runInstall(pkg string, ch chan struct{}) {
+ if pkg == "net" || pkg == "os/user" || pkg == "crypto/x509" {
+ fatalf("go_bootstrap cannot depend on cgo package %s", pkg)
+ }
+
+ defer close(ch)
+
+ if pkg == "unsafe" {
+ return
+ }
+
+ if vflag > 0 {
+ if goos != gohostos || goarch != gohostarch {
+ errprintf("%s (%s/%s)\n", pkg, goos, goarch)
+ } else {
+ errprintf("%s\n", pkg)
+ }
+ }
+
+ workdir := pathf("%s/%s", workdir, pkg)
+ xmkdirall(workdir)
+
+ var clean []string
+ defer func() {
+ for _, name := range clean {
+ xremove(name)
+ }
+ }()
+
+ // dir = full path to pkg.
+ dir := pathf("%s/src/%s", goroot, pkg)
+ name := filepath.Base(dir)
+
+ // ispkg predicts whether the package should be linked as a binary, based
+ // on the name. There should be no "main" packages in vendor, since
+ // 'go mod vendor' will only copy imported packages there.
+ ispkg := !strings.HasPrefix(pkg, "cmd/") || strings.Contains(pkg, "/internal/") || strings.Contains(pkg, "/vendor/")
+
+ // Start final link command line.
+ // Note: code below knows that link.p[targ] is the target.
+ var (
+ link []string
+ targ int
+ ispackcmd bool
+ )
+ if ispkg {
+ // Go library (package).
+ ispackcmd = true
+ link = []string{"pack", packagefile(pkg)}
+ targ = len(link) - 1
+ xmkdirall(filepath.Dir(link[targ]))
+ } else {
+ // Go command.
+ elem := name
+ if elem == "go" {
+ elem = "go_bootstrap"
+ }
+ link = []string{pathf("%s/link", tooldir)}
+ if goos == "android" {
+ link = append(link, "-buildmode=pie")
+ }
+ if goldflags != "" {
+ link = append(link, goldflags)
+ }
+ link = append(link, "-extld="+compilerEnvLookup("CC", defaultcc, goos, goarch))
+ link = append(link, "-L="+pathf("%s/pkg/obj/go-bootstrap/%s_%s", goroot, goos, goarch))
+ link = append(link, "-o", pathf("%s/%s%s", tooldir, elem, exe))
+ targ = len(link) - 1
+ }
+ ttarg := mtime(link[targ])
+
+ // Gather files that are sources for this target.
+ // Everything in that directory, and any target-specific
+ // additions.
+ files := xreaddir(dir)
+
+ // Remove files beginning with . or _,
+ // which are likely to be editor temporary files.
+ // This is the same heuristic build.ScanDir uses.
+ // There do exist real C files beginning with _,
+ // so limit that check to just Go files.
+ files = filter(files, func(p string) bool {
+ return !strings.HasPrefix(p, ".") && (!strings.HasPrefix(p, "_") || !strings.HasSuffix(p, ".go"))
+ })
+
+ // Add generated files for this package.
+ for _, gt := range gentab {
+ if gt.pkg == pkg {
+ files = append(files, gt.file)
+ }
+ }
+ files = uniq(files)
+
+ // Convert to absolute paths.
+ for i, p := range files {
+ if !filepath.IsAbs(p) {
+ files[i] = pathf("%s/%s", dir, p)
+ }
+ }
+
+ // Is the target up-to-date?
+ var gofiles, sfiles []string
+ stale := rebuildall
+ files = filter(files, func(p string) bool {
+ for _, suf := range depsuffix {
+ if strings.HasSuffix(p, suf) {
+ goto ok
+ }
+ }
+ return false
+ ok:
+ t := mtime(p)
+ if !t.IsZero() && !strings.HasSuffix(p, ".a") && !shouldbuild(p, pkg) {
+ return false
+ }
+ if strings.HasSuffix(p, ".go") {
+ gofiles = append(gofiles, p)
+ } else if strings.HasSuffix(p, ".s") {
+ sfiles = append(sfiles, p)
+ }
+ if t.After(ttarg) {
+ stale = true
+ }
+ return true
+ })
+
+ // If there are no files to compile, we're done.
+ if len(files) == 0 {
+ return
+ }
+
+ if !stale {
+ return
+ }
+
+ // For package runtime, copy some files into the work space.
+ if pkg == "runtime" {
+ xmkdirall(pathf("%s/pkg/include", goroot))
+ // For use by assembly and C files.
+ copyfile(pathf("%s/pkg/include/textflag.h", goroot),
+ pathf("%s/src/runtime/textflag.h", goroot), 0)
+ copyfile(pathf("%s/pkg/include/funcdata.h", goroot),
+ pathf("%s/src/runtime/funcdata.h", goroot), 0)
+ copyfile(pathf("%s/pkg/include/asm_ppc64x.h", goroot),
+ pathf("%s/src/runtime/asm_ppc64x.h", goroot), 0)
+ copyfile(pathf("%s/pkg/include/asm_amd64.h", goroot),
+ pathf("%s/src/runtime/asm_amd64.h", goroot), 0)
+ }
+
+ // Generate any missing files; regenerate existing ones.
+ for _, gt := range gentab {
+ if gt.pkg != pkg {
+ continue
+ }
+ p := pathf("%s/%s", dir, gt.file)
+ if vflag > 1 {
+ errprintf("generate %s\n", p)
+ }
+ gt.gen(dir, p)
+ // Do not add generated file to clean list.
+ // In runtime, we want to be able to
+ // build the package with the go tool,
+ // and it assumes these generated files already
+ // exist (it does not know how to build them).
+ // The 'clean' command can remove
+ // the generated files.
+ }
+
+ // Resolve imported packages to actual package paths.
+ // Make sure they're installed.
+ importMap := make(map[string]string)
+ for _, p := range gofiles {
+ for _, imp := range readimports(p) {
+ if imp == "C" {
+ fatalf("%s imports C", p)
+ }
+ importMap[imp] = resolveVendor(imp, dir)
+ }
+ }
+ sortedImports := make([]string, 0, len(importMap))
+ for imp := range importMap {
+ sortedImports = append(sortedImports, imp)
+ }
+ sort.Strings(sortedImports)
+
+ for _, dep := range importMap {
+ if dep == "C" {
+ fatalf("%s imports C", pkg)
+ }
+ startInstall(dep)
+ }
+ for _, dep := range importMap {
+ install(dep)
+ }
+
+ if goos != gohostos || goarch != gohostarch {
+ // We've generated the right files; the go command can do the build.
+ if vflag > 1 {
+ errprintf("skip build for cross-compile %s\n", pkg)
+ }
+ return
+ }
+
+ asmArgs := []string{
+ pathf("%s/asm", tooldir),
+ "-I", workdir,
+ "-I", pathf("%s/pkg/include", goroot),
+ "-D", "GOOS_" + goos,
+ "-D", "GOARCH_" + goarch,
+ "-D", "GOOS_GOARCH_" + goos + "_" + goarch,
+ "-p", pkg,
+ }
+ if goarch == "mips" || goarch == "mipsle" {
+ // Define GOMIPS_value from gomips.
+ asmArgs = append(asmArgs, "-D", "GOMIPS_"+gomips)
+ }
+ if goarch == "mips64" || goarch == "mips64le" {
+ // Define GOMIPS64_value from gomips64.
+ asmArgs = append(asmArgs, "-D", "GOMIPS64_"+gomips64)
+ }
+ if goarch == "ppc64" || goarch == "ppc64le" {
+ // We treat each powerpc version as a superset of functionality.
+ switch goppc64 {
+ case "power10":
+ asmArgs = append(asmArgs, "-D", "GOPPC64_power10")
+ fallthrough
+ case "power9":
+ asmArgs = append(asmArgs, "-D", "GOPPC64_power9")
+ fallthrough
+ default: // This should always be power8.
+ asmArgs = append(asmArgs, "-D", "GOPPC64_power8")
+ }
+ }
+ goasmh := pathf("%s/go_asm.h", workdir)
+
+ // Collect symabis from assembly code.
+ var symabis string
+ if len(sfiles) > 0 {
+ symabis = pathf("%s/symabis", workdir)
+ var wg sync.WaitGroup
+ asmabis := append(asmArgs[:len(asmArgs):len(asmArgs)], "-gensymabis", "-o", symabis)
+ asmabis = append(asmabis, sfiles...)
+ if err := os.WriteFile(goasmh, nil, 0666); err != nil {
+ fatalf("cannot write empty go_asm.h: %s", err)
+ }
+ bgrun(&wg, dir, asmabis...)
+ bgwait(&wg)
+ }
+
+ // Build an importcfg file for the compiler.
+ buf := &bytes.Buffer{}
+ for _, imp := range sortedImports {
+ if imp == "unsafe" {
+ continue
+ }
+ dep := importMap[imp]
+ if imp != dep {
+ fmt.Fprintf(buf, "importmap %s=%s\n", imp, dep)
+ }
+ fmt.Fprintf(buf, "packagefile %s=%s\n", dep, packagefile(dep))
+ }
+ importcfg := pathf("%s/importcfg", workdir)
+ if err := os.WriteFile(importcfg, buf.Bytes(), 0666); err != nil {
+ fatalf("cannot write importcfg file: %v", err)
+ }
+
+ var archive string
+ // The next loop will compile individual non-Go files.
+ // Hand the Go files to the compiler en masse.
+ // For packages containing assembly, this writes go_asm.h, which
+ // the assembly files will need.
+ pkgName := pkg
+ if strings.HasPrefix(pkg, "cmd/") && strings.Count(pkg, "/") == 1 {
+ pkgName = "main"
+ }
+ b := pathf("%s/_go_.a", workdir)
+ clean = append(clean, b)
+ if !ispackcmd {
+ link = append(link, b)
+ } else {
+ archive = b
+ }
+
+ // Compile Go code.
+ compile := []string{pathf("%s/compile", tooldir), "-std", "-pack", "-o", b, "-p", pkgName, "-importcfg", importcfg}
+ if gogcflags != "" {
+ compile = append(compile, strings.Fields(gogcflags)...)
+ }
+ if len(sfiles) > 0 {
+ compile = append(compile, "-asmhdr", goasmh)
+ }
+ if symabis != "" {
+ compile = append(compile, "-symabis", symabis)
+ }
+ if goos == "android" {
+ compile = append(compile, "-shared")
+ }
+
+ compile = append(compile, gofiles...)
+ var wg sync.WaitGroup
+ // We use bgrun and immediately wait for it instead of calling run() synchronously.
+ // This executes all jobs through the bgwork channel and allows the process
+ // to exit cleanly in case an error occurs.
+ bgrun(&wg, dir, compile...)
+ bgwait(&wg)
+
+ // Compile the files.
+ for _, p := range sfiles {
+ // Assembly file for a Go package.
+ compile := asmArgs[:len(asmArgs):len(asmArgs)]
+
+ doclean := true
+ b := pathf("%s/%s", workdir, filepath.Base(p))
+
+ // Change the last character of the output file (which was c or s).
+ b = b[:len(b)-1] + "o"
+ compile = append(compile, "-o", b, p)
+ bgrun(&wg, dir, compile...)
+
+ link = append(link, b)
+ if doclean {
+ clean = append(clean, b)
+ }
+ }
+ bgwait(&wg)
+
+ if ispackcmd {
+ xremove(link[targ])
+ dopack(link[targ], archive, link[targ+1:])
+ return
+ }
+
+ // Remove target before writing it.
+ xremove(link[targ])
+ bgrun(&wg, "", link...)
+ bgwait(&wg)
+}
+
+// packagefile returns the path to a compiled .a file for the given package
+// path. Paths may need to be resolved with resolveVendor first.
+func packagefile(pkg string) string {
+ return pathf("%s/pkg/obj/go-bootstrap/%s_%s/%s.a", goroot, goos, goarch, pkg)
+}
+
+// unixOS is the set of GOOS values matched by the "unix" build tag.
+// This is the same list as in go/build/syslist.go and
+// cmd/go/internal/imports/build.go.
+var unixOS = map[string]bool{
+ "aix": true,
+ "android": true,
+ "darwin": true,
+ "dragonfly": true,
+ "freebsd": true,
+ "hurd": true,
+ "illumos": true,
+ "ios": true,
+ "linux": true,
+ "netbsd": true,
+ "openbsd": true,
+ "solaris": true,
+}
+
+// matchtag reports whether the tag matches this build.
+func matchtag(tag string) bool {
+ switch tag {
+ case "gc", "cmd_go_bootstrap", "go1.1":
+ return true
+ case "linux":
+ return goos == "linux" || goos == "android"
+ case "solaris":
+ return goos == "solaris" || goos == "illumos"
+ case "darwin":
+ return goos == "darwin" || goos == "ios"
+ case goos, goarch:
+ return true
+ case "unix":
+ return unixOS[goos]
+ default:
+ return false
+ }
+}
+
+// shouldbuild reports whether we should build this file.
+// It applies the same rules that are used with context tags
+// in package go/build, except it's less picky about the order
+// of GOOS and GOARCH.
+// We also allow the special tag cmd_go_bootstrap.
+// See ../go/bootstrap.go and package go/build.
+func shouldbuild(file, pkg string) bool {
+ // Check file name for GOOS or GOARCH.
+ name := filepath.Base(file)
+ excluded := func(list []string, ok string) bool {
+ for _, x := range list {
+ if x == ok || (ok == "android" && x == "linux") || (ok == "illumos" && x == "solaris") || (ok == "ios" && x == "darwin") {
+ continue
+ }
+ i := strings.Index(name, x)
+ if i <= 0 || name[i-1] != '_' {
+ continue
+ }
+ i += len(x)
+ if i == len(name) || name[i] == '.' || name[i] == '_' {
+ return true
+ }
+ }
+ return false
+ }
+ if excluded(okgoos, goos) || excluded(okgoarch, goarch) {
+ return false
+ }
+
+ // Omit test files.
+ if strings.Contains(name, "_test") {
+ return false
+ }
+
+ // Check file contents for //go:build lines.
+ for _, p := range strings.Split(readfile(file), "\n") {
+ p = strings.TrimSpace(p)
+ if p == "" {
+ continue
+ }
+ code := p
+ i := strings.Index(code, "//")
+ if i > 0 {
+ code = strings.TrimSpace(code[:i])
+ }
+ if code == "package documentation" {
+ return false
+ }
+ if code == "package main" && pkg != "cmd/go" && pkg != "cmd/cgo" {
+ return false
+ }
+ if !strings.HasPrefix(p, "//") {
+ break
+ }
+ if strings.HasPrefix(p, "//go:build ") {
+ matched, err := matchexpr(p[len("//go:build "):])
+ if err != nil {
+ errprintf("%s: %v", file, err)
+ }
+ return matched
+ }
+ }
+
+ return true
+}
+
+// copyfile copies the file src to dst, via memory (so only good for small files).
+func copyfile(dst, src string, flag int) {
+ if vflag > 1 {
+ errprintf("cp %s %s\n", src, dst)
+ }
+ writefile(readfile(src), dst, flag)
+}
+
+// dopack copies the package src to dst,
+// appending the files listed in extra.
+// The archive format is the traditional Unix ar format.
+func dopack(dst, src string, extra []string) {
+ bdst := bytes.NewBufferString(readfile(src))
+ for _, file := range extra {
+ b := readfile(file)
+ // find last path element for archive member name
+ i := strings.LastIndex(file, "/") + 1
+ j := strings.LastIndex(file, `\`) + 1
+ if i < j {
+ i = j
+ }
+ fmt.Fprintf(bdst, "%-16.16s%-12d%-6d%-6d%-8o%-10d`\n", file[i:], 0, 0, 0, 0644, len(b))
+ bdst.WriteString(b)
+ if len(b)&1 != 0 {
+ bdst.WriteByte(0)
+ }
+ }
+ writefile(bdst.String(), dst, 0)
+}
+
+func clean() {
+ generated := []byte(generatedHeader)
+
+ // Remove generated source files.
+ filepath.WalkDir(pathf("%s/src", goroot), func(path string, d fs.DirEntry, err error) error {
+ switch {
+ case err != nil:
+ // ignore
+ case d.IsDir() && (d.Name() == "vendor" || d.Name() == "testdata"):
+ return filepath.SkipDir
+ case d.IsDir() && d.Name() != "dist":
+ // Remove generated binary named for directory, but not dist out from under us.
+ exe := filepath.Join(path, d.Name())
+ if info, err := os.Stat(exe); err == nil && !info.IsDir() {
+ xremove(exe)
+ }
+ xremove(exe + ".exe")
+ case !d.IsDir() && strings.HasPrefix(d.Name(), "z"):
+ // Remove generated file, identified by marker string.
+ head := make([]byte, 512)
+ if f, err := os.Open(path); err == nil {
+ io.ReadFull(f, head)
+ f.Close()
+ }
+ if bytes.HasPrefix(head, generated) {
+ xremove(path)
+ }
+ }
+ return nil
+ })
+
+ if rebuildall {
+ // Remove object tree.
+ xremoveall(pathf("%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch))
+
+ // Remove installed packages and tools.
+ xremoveall(pathf("%s/pkg/%s_%s", goroot, gohostos, gohostarch))
+ xremoveall(pathf("%s/pkg/%s_%s", goroot, goos, goarch))
+ xremoveall(pathf("%s/pkg/%s_%s_race", goroot, gohostos, gohostarch))
+ xremoveall(pathf("%s/pkg/%s_%s_race", goroot, goos, goarch))
+ xremoveall(tooldir)
+
+ // Remove cached version info.
+ xremove(pathf("%s/VERSION.cache", goroot))
+
+ // Remove distribution packages.
+ xremoveall(pathf("%s/pkg/distpack", goroot))
+ }
+}
+
+/*
+ * command implementations
+ */
+
+// The env command prints the default environment.
+func cmdenv() {
+ path := flag.Bool("p", false, "emit updated PATH")
+ plan9 := flag.Bool("9", gohostos == "plan9", "emit plan 9 syntax")
+ windows := flag.Bool("w", gohostos == "windows", "emit windows syntax")
+ xflagparse(0)
+
+ format := "%s=\"%s\";\n" // Include ; to separate variables when 'dist env' output is used with eval.
+ switch {
+ case *plan9:
+ format = "%s='%s'\n"
+ case *windows:
+ format = "set %s=%s\r\n"
+ }
+
+ xprintf(format, "GO111MODULE", "")
+ xprintf(format, "GOARCH", goarch)
+ xprintf(format, "GOBIN", gorootBin)
+ xprintf(format, "GODEBUG", os.Getenv("GODEBUG"))
+ xprintf(format, "GOENV", "off")
+ xprintf(format, "GOFLAGS", "")
+ xprintf(format, "GOHOSTARCH", gohostarch)
+ xprintf(format, "GOHOSTOS", gohostos)
+ xprintf(format, "GOOS", goos)
+ xprintf(format, "GOPROXY", os.Getenv("GOPROXY"))
+ xprintf(format, "GOROOT", goroot)
+ xprintf(format, "GOTMPDIR", os.Getenv("GOTMPDIR"))
+ xprintf(format, "GOTOOLDIR", tooldir)
+ if goarch == "arm" {
+ xprintf(format, "GOARM", goarm)
+ }
+ if goarch == "386" {
+ xprintf(format, "GO386", go386)
+ }
+ if goarch == "amd64" {
+ xprintf(format, "GOAMD64", goamd64)
+ }
+ if goarch == "mips" || goarch == "mipsle" {
+ xprintf(format, "GOMIPS", gomips)
+ }
+ if goarch == "mips64" || goarch == "mips64le" {
+ xprintf(format, "GOMIPS64", gomips64)
+ }
+ if goarch == "ppc64" || goarch == "ppc64le" {
+ xprintf(format, "GOPPC64", goppc64)
+ }
+ xprintf(format, "GOWORK", "off")
+
+ if *path {
+ sep := ":"
+ if gohostos == "windows" {
+ sep = ";"
+ }
+ xprintf(format, "PATH", fmt.Sprintf("%s%s%s", gorootBin, sep, os.Getenv("PATH")))
+
+ // Also include $DIST_UNMODIFIED_PATH with the original $PATH
+ // for the internal needs of "dist banner", along with export
+ // so that it reaches the dist process. See its comment below.
+ var exportFormat string
+ if !*windows && !*plan9 {
+ exportFormat = "export " + format
+ } else {
+ exportFormat = format
+ }
+ xprintf(exportFormat, "DIST_UNMODIFIED_PATH", os.Getenv("PATH"))
+ }
+}
+
+var (
+ timeLogEnabled = os.Getenv("GOBUILDTIMELOGFILE") != ""
+ timeLogMu sync.Mutex
+ timeLogFile *os.File
+ timeLogStart time.Time
+)
+
+func timelog(op, name string) {
+ if !timeLogEnabled {
+ return
+ }
+ timeLogMu.Lock()
+ defer timeLogMu.Unlock()
+ if timeLogFile == nil {
+ f, err := os.OpenFile(os.Getenv("GOBUILDTIMELOGFILE"), os.O_RDWR|os.O_APPEND, 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+ buf := make([]byte, 100)
+ n, _ := f.Read(buf)
+ s := string(buf[:n])
+ if i := strings.Index(s, "\n"); i >= 0 {
+ s = s[:i]
+ }
+ i := strings.Index(s, " start")
+ if i < 0 {
+ log.Fatalf("time log %s does not begin with start line", os.Getenv("GOBUILDTIMELOGFILE"))
+ }
+ t, err := time.Parse(time.UnixDate, s[:i])
+ if err != nil {
+ log.Fatalf("cannot parse time log line %q: %v", s, err)
+ }
+ timeLogStart = t
+ timeLogFile = f
+ }
+ t := time.Now()
+ fmt.Fprintf(timeLogFile, "%s %+.1fs %s %s\n", t.Format(time.UnixDate), t.Sub(timeLogStart).Seconds(), op, name)
+}
+
+// toolenv returns the environment to use when building commands in cmd.
+//
+// This is a function instead of a variable because the exact toolenv depends
+// on the GOOS and GOARCH, and (at least for now) those are modified in place
+// to switch between the host and target configurations when cross-compiling.
+func toolenv() []string {
+ var env []string
+ if !mustLinkExternal(goos, goarch, false) {
+ // Unless the platform requires external linking,
+ // we disable cgo to get static binaries for cmd/go and cmd/pprof,
+ // so that they work on systems without the same dynamic libraries
+ // as the original build system.
+ env = append(env, "CGO_ENABLED=0")
+ }
+ if isRelease || os.Getenv("GO_BUILDER_NAME") != "" {
+ // Add -trimpath for reproducible builds of releases.
+ // Include builders so that -trimpath is well-tested ahead of releases.
+ // Do not include local development, so that people working in the
+ // main branch for day-to-day work on the Go toolchain itself can
+ // still have full paths for stack traces for compiler crashes and the like.
+ env = append(env, "GOFLAGS=-trimpath -ldflags=-w -gcflags=cmd/...=-dwarf=false")
+ }
+ return env
+}
+
+var toolchain = []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/link"}
+
+// The bootstrap command runs a build from scratch,
+// stopping at having installed the go_bootstrap command.
+//
+// WARNING: This command runs after cmd/dist is built with the Go bootstrap toolchain.
+// It rebuilds and installs cmd/dist with the new toolchain, so other
+// commands (like "go tool dist test" in run.bash) can rely on bug fixes
+// made since the Go bootstrap version, but this function cannot.
+func cmdbootstrap() {
+ timelog("start", "dist bootstrap")
+ defer timelog("end", "dist bootstrap")
+
+ var debug, distpack, force, noBanner, noClean bool
+ flag.BoolVar(&rebuildall, "a", rebuildall, "rebuild all")
+ flag.BoolVar(&debug, "d", debug, "enable debugging of bootstrap process")
+ flag.BoolVar(&distpack, "distpack", distpack, "write distribution files to pkg/distpack")
+ flag.BoolVar(&force, "force", force, "build even if the port is marked as broken")
+ flag.BoolVar(&noBanner, "no-banner", noBanner, "do not print banner")
+ flag.BoolVar(&noClean, "no-clean", noClean, "print deprecation warning")
+
+ xflagparse(0)
+
+ if noClean {
+ xprintf("warning: --no-clean is deprecated and has no effect; use 'go install std cmd' instead\n")
+ }
+
+ // Don't build broken ports by default.
+ if broken[goos+"/"+goarch] && !force {
+ fatalf("build stopped because the port %s/%s is marked as broken\n\n"+
+ "Use the -force flag to build anyway.\n", goos, goarch)
+ }
+
+ // Set GOPATH to an internal directory. We shouldn't actually
+ // need to store files here, since the toolchain won't
+ // depend on modules outside of vendor directories, but if
+ // GOPATH points somewhere else (e.g., to GOROOT), the
+ // go tool may complain.
+ os.Setenv("GOPATH", pathf("%s/pkg/obj/gopath", goroot))
+
+ // Use a build cache separate from the default user one.
+ // Also one that will be wiped out during startup, so that
+ // make.bash really does start from a clean slate.
+ oldgocache = os.Getenv("GOCACHE")
+ os.Setenv("GOCACHE", pathf("%s/pkg/obj/go-build", goroot))
+
+ // Disable GOEXPERIMENT when building toolchain1 and
+ // go_bootstrap. We don't need any experiments for the
+ // bootstrap toolchain, and this lets us avoid duplicating the
+ // GOEXPERIMENT-related build logic from cmd/go here. If the
+ // bootstrap toolchain is < Go 1.17, it will ignore this
+ // anyway since GOEXPERIMENT is baked in; otherwise it will
+ // pick it up from the environment we set here. Once we're
+ // using toolchain1 with dist as the build system, we need to
+ // override this to keep the experiments assumed by the
+ // toolchain and by dist consistent. Once go_bootstrap takes
+ // over the build process, we'll set this back to the original
+ // GOEXPERIMENT.
+ os.Setenv("GOEXPERIMENT", "none")
+
+ if debug {
+ // cmd/buildid is used in debug mode.
+ toolchain = append(toolchain, "cmd/buildid")
+ }
+
+ if isdir(pathf("%s/src/pkg", goroot)) {
+ fatalf("\n\n"+
+ "The Go package sources have moved to $GOROOT/src.\n"+
+ "*** %s still exists. ***\n"+
+ "It probably contains stale files that may confuse the build.\n"+
+ "Please (check what's there and) remove it and try again.\n"+
+ "See https://golang.org/s/go14nopkg\n",
+ pathf("%s/src/pkg", goroot))
+ }
+
+ if rebuildall {
+ clean()
+ }
+
+ setup()
+
+ timelog("build", "toolchain1")
+ checkCC()
+ bootstrapBuildTools()
+
+ // Remember old content of $GOROOT/bin for comparison below.
+ oldBinFiles, err := filepath.Glob(pathf("%s/bin/*", goroot))
+ if err != nil {
+ fatalf("glob: %v", err)
+ }
+
+ // For the main bootstrap, building for host os/arch.
+ oldgoos = goos
+ oldgoarch = goarch
+ goos = gohostos
+ goarch = gohostarch
+ os.Setenv("GOHOSTARCH", gohostarch)
+ os.Setenv("GOHOSTOS", gohostos)
+ os.Setenv("GOARCH", goarch)
+ os.Setenv("GOOS", goos)
+
+ timelog("build", "go_bootstrap")
+ xprintf("Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.\n")
+ install("runtime") // dependency not visible in sources; also sets up textflag.h
+ install("time/tzdata") // no dependency in sources; creates generated file
+ install("cmd/go")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+
+ gogcflags = os.Getenv("GO_GCFLAGS") // we were using $BOOT_GO_GCFLAGS until now
+ setNoOpt()
+ goldflags = os.Getenv("GO_LDFLAGS") // we were using $BOOT_GO_LDFLAGS until now
+ goBootstrap := pathf("%s/go_bootstrap", tooldir)
+ if debug {
+ run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
+ copyfile(pathf("%s/compile1", tooldir), pathf("%s/compile", tooldir), writeExec)
+ }
+
+ // To recap, so far we have built the new toolchain
+ // (cmd/asm, cmd/cgo, cmd/compile, cmd/link)
+ // using the Go bootstrap toolchain and go command.
+ // Then we built the new go command (as go_bootstrap)
+ // using the new toolchain and our own build logic (above).
+ //
+ // toolchain1 = mk(new toolchain, go1.17 toolchain, go1.17 cmd/go)
+ // go_bootstrap = mk(new cmd/go, toolchain1, cmd/dist)
+ //
+ // The toolchain1 we built earlier is built from the new sources,
+ // but because it was built using cmd/go it has no build IDs.
+ // The eventually installed toolchain needs build IDs, so we need
+ // to do another round:
+ //
+ // toolchain2 = mk(new toolchain, toolchain1, go_bootstrap)
+ //
+ timelog("build", "toolchain2")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ xprintf("Building Go toolchain2 using go_bootstrap and Go toolchain1.\n")
+ os.Setenv("CC", compilerEnvLookup("CC", defaultcc, goos, goarch))
+ // Now that cmd/go is in charge of the build process, enable GOEXPERIMENT.
+ os.Setenv("GOEXPERIMENT", goexperiment)
+ // No need to enable PGO for toolchain2.
+ goInstall(toolenv(), goBootstrap, append([]string{"-pgo=off"}, toolchain...)...)
+ if debug {
+ run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
+ copyfile(pathf("%s/compile2", tooldir), pathf("%s/compile", tooldir), writeExec)
+ }
+
+ // Toolchain2 should be semantically equivalent to toolchain1,
+ // but it was built using the newly built compiler instead of the Go bootstrap compiler,
+ // so it should at the least run faster. Also, toolchain1 had no build IDs
+ // in the binaries, while toolchain2 does. In non-release builds, the
+ // toolchain's build IDs feed into constructing the build IDs of built targets,
+ // so in non-release builds, everything now looks out-of-date due to
+ // toolchain2 having build IDs - that is, due to the go command seeing
+ // that there are new compilers. In release builds, the toolchain's reported
+ // version is used in place of the build ID, and the go command does not
+ // see that change from toolchain1 to toolchain2, so in release builds,
+ // nothing looks out of date.
+ // To keep the behavior the same in both non-release and release builds,
+ // we force-install everything here.
+ //
+ // toolchain3 = mk(new toolchain, toolchain2, go_bootstrap)
+ //
+ timelog("build", "toolchain3")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ xprintf("Building Go toolchain3 using go_bootstrap and Go toolchain2.\n")
+ goInstall(toolenv(), goBootstrap, append([]string{"-a"}, toolchain...)...)
+ if debug {
+ run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
+ copyfile(pathf("%s/compile3", tooldir), pathf("%s/compile", tooldir), writeExec)
+ }
+
+ // Now that toolchain3 has been built from scratch, its compiler and linker
+ // should have accurate build IDs suitable for caching.
+ // Now prime the build cache with the rest of the standard library for
+ // testing, and so that the user can run 'go install std cmd' to quickly
+ // iterate on local changes without waiting for a full rebuild.
+ if _, err := os.Stat(pathf("%s/VERSION", goroot)); err == nil {
+ // If we have a VERSION file, then we use the Go version
+ // instead of build IDs as a cache key, and there is no guarantee
+ // that code hasn't changed since the last time we ran a build
+ // with this exact VERSION file (especially if someone is working
+ // on a release branch). We must not fall back to the shared build cache
+ // in this case. Leave $GOCACHE alone.
+ } else {
+ os.Setenv("GOCACHE", oldgocache)
+ }
+
+ if goos == oldgoos && goarch == oldgoarch {
+ // Common case - not setting up for cross-compilation.
+ timelog("build", "toolchain")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ xprintf("Building packages and commands for %s/%s.\n", goos, goarch)
+ } else {
+ // GOOS/GOARCH does not match GOHOSTOS/GOHOSTARCH.
+ // Finish GOHOSTOS/GOHOSTARCH installation and then
+ // run GOOS/GOARCH installation.
+ timelog("build", "host toolchain")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ xprintf("Building commands for host, %s/%s.\n", goos, goarch)
+ goInstall(toolenv(), goBootstrap, "cmd")
+ checkNotStale(toolenv(), goBootstrap, "cmd")
+ checkNotStale(toolenv(), gorootBinGo, "cmd")
+
+ timelog("build", "target toolchain")
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ goos = oldgoos
+ goarch = oldgoarch
+ os.Setenv("GOOS", goos)
+ os.Setenv("GOARCH", goarch)
+ os.Setenv("CC", compilerEnvLookup("CC", defaultcc, goos, goarch))
+ xprintf("Building packages and commands for target, %s/%s.\n", goos, goarch)
+ }
+ goInstall(nil, goBootstrap, "std")
+ goInstall(toolenv(), goBootstrap, "cmd")
+ checkNotStale(toolenv(), goBootstrap, toolchain...)
+ checkNotStale(nil, goBootstrap, "std")
+ checkNotStale(toolenv(), goBootstrap, "cmd")
+ checkNotStale(nil, gorootBinGo, "std")
+ checkNotStale(toolenv(), gorootBinGo, "cmd")
+ if debug {
+ run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full")
+ checkNotStale(toolenv(), goBootstrap, toolchain...)
+ copyfile(pathf("%s/compile4", tooldir), pathf("%s/compile", tooldir), writeExec)
+ }
+
+ // Check that there are no new files in $GOROOT/bin other than
+ // go and gofmt and $GOOS_$GOARCH (target bin when cross-compiling).
+ binFiles, err := filepath.Glob(pathf("%s/bin/*", goroot))
+ if err != nil {
+ fatalf("glob: %v", err)
+ }
+
+ ok := map[string]bool{}
+ for _, f := range oldBinFiles {
+ ok[f] = true
+ }
+ for _, f := range binFiles {
+ if gohostos == "darwin" && filepath.Base(f) == ".DS_Store" {
+ continue // unfortunate but not unexpected
+ }
+ elem := strings.TrimSuffix(filepath.Base(f), ".exe")
+ if !ok[f] && elem != "go" && elem != "gofmt" && elem != goos+"_"+goarch {
+ fatalf("unexpected new file in $GOROOT/bin: %s", elem)
+ }
+ }
+
+ // Remove go_bootstrap now that we're done.
+ xremove(pathf("%s/go_bootstrap"+exe, tooldir))
+
+ if goos == "android" {
+ // Make sure the exec wrapper will sync a fresh $GOROOT to the device.
+ xremove(pathf("%s/go_android_exec-adb-sync-status", os.TempDir()))
+ }
+
+ if wrapperPath := wrapperPathFor(goos, goarch); wrapperPath != "" {
+ oldcc := os.Getenv("CC")
+ os.Setenv("GOOS", gohostos)
+ os.Setenv("GOARCH", gohostarch)
+ os.Setenv("CC", compilerEnvLookup("CC", defaultcc, gohostos, gohostarch))
+ goCmd(nil, gorootBinGo, "build", "-o", pathf("%s/go_%s_%s_exec%s", gorootBin, goos, goarch, exe), wrapperPath)
+ // Restore environment.
+ // TODO(elias.naur): support environment variables in goCmd?
+ os.Setenv("GOOS", goos)
+ os.Setenv("GOARCH", goarch)
+ os.Setenv("CC", oldcc)
+ }
+
+ if distpack {
+ xprintf("Packaging archives for %s/%s.\n", goos, goarch)
+ run("", ShowOutput|CheckExit, pathf("%s/distpack", tooldir))
+ }
+
+ // Print trailing banner unless instructed otherwise.
+ if !noBanner {
+ banner()
+ }
+}
+
+func wrapperPathFor(goos, goarch string) string {
+ switch {
+ case goos == "android":
+ if gohostos != "android" {
+ return pathf("%s/misc/go_android_exec/main.go", goroot)
+ }
+ case goos == "ios":
+ if gohostos != "ios" {
+ return pathf("%s/misc/ios/go_ios_exec.go", goroot)
+ }
+ }
+ return ""
+}
+
+func goInstall(env []string, goBinary string, args ...string) {
+ goCmd(env, goBinary, "install", args...)
+}
+
+func appendCompilerFlags(args []string) []string {
+ if gogcflags != "" {
+ args = append(args, "-gcflags=all="+gogcflags)
+ }
+ if goldflags != "" {
+ args = append(args, "-ldflags=all="+goldflags)
+ }
+ return args
+}
+
+func goCmd(env []string, goBinary string, cmd string, args ...string) {
+ goCmd := []string{goBinary, cmd}
+ if noOpt {
+ goCmd = append(goCmd, "-tags=noopt")
+ }
+ goCmd = appendCompilerFlags(goCmd)
+ if vflag > 0 {
+ goCmd = append(goCmd, "-v")
+ }
+
+ // Force only one process at a time on vx32 emulation.
+ if gohostos == "plan9" && os.Getenv("sysname") == "vx32" {
+ goCmd = append(goCmd, "-p=1")
+ }
+
+ runEnv(workdir, ShowOutput|CheckExit, env, append(goCmd, args...)...)
+}
+
+func checkNotStale(env []string, goBinary string, targets ...string) {
+ goCmd := []string{goBinary, "list"}
+ if noOpt {
+ goCmd = append(goCmd, "-tags=noopt")
+ }
+ goCmd = appendCompilerFlags(goCmd)
+ goCmd = append(goCmd, "-f={{if .Stale}}\tSTALE {{.ImportPath}}: {{.StaleReason}}{{end}}")
+
+ out := runEnv(workdir, CheckExit, env, append(goCmd, targets...)...)
+ if strings.Contains(out, "\tSTALE ") {
+ os.Setenv("GODEBUG", "gocachehash=1")
+ for _, target := range []string{"runtime/internal/sys", "cmd/dist", "cmd/link"} {
+ if strings.Contains(out, "STALE "+target) {
+ run(workdir, ShowOutput|CheckExit, goBinary, "list", "-f={{.ImportPath}} {{.Stale}}", target)
+ break
+ }
+ }
+ fatalf("unexpected stale targets reported by %s list -gcflags=\"%s\" -ldflags=\"%s\" for %v (consider rerunning with GOMAXPROCS=1 GODEBUG=gocachehash=1):\n%s", goBinary, gogcflags, goldflags, targets, out)
+ }
+}
+
+// Cannot use go/build directly because cmd/dist for a new release
+// builds against an old release's go/build, which may be out of sync.
+// To reduce duplication, we generate the list for go/build from this.
+//
+// We list all supported platforms in this list, so that this is the
+// single point of truth for supported platforms. This list is used
+// by 'go tool dist list'.
+var cgoEnabled = map[string]bool{
+ "aix/ppc64": true,
+ "darwin/amd64": true,
+ "darwin/arm64": true,
+ "dragonfly/amd64": true,
+ "freebsd/386": true,
+ "freebsd/amd64": true,
+ "freebsd/arm": true,
+ "freebsd/arm64": true,
+ "freebsd/riscv64": true,
+ "illumos/amd64": true,
+ "linux/386": true,
+ "linux/amd64": true,
+ "linux/arm": true,
+ "linux/arm64": true,
+ "linux/loong64": true,
+ "linux/ppc64": false,
+ "linux/ppc64le": true,
+ "linux/mips": true,
+ "linux/mipsle": true,
+ "linux/mips64": true,
+ "linux/mips64le": true,
+ "linux/riscv64": true,
+ "linux/s390x": true,
+ "linux/sparc64": true,
+ "android/386": true,
+ "android/amd64": true,
+ "android/arm": true,
+ "android/arm64": true,
+ "ios/arm64": true,
+ "ios/amd64": true,
+ "js/wasm": false,
+ "wasip1/wasm": false,
+ "netbsd/386": true,
+ "netbsd/amd64": true,
+ "netbsd/arm": true,
+ "netbsd/arm64": true,
+ "openbsd/386": true,
+ "openbsd/amd64": true,
+ "openbsd/arm": true,
+ "openbsd/arm64": true,
+ "openbsd/mips64": true,
+ "openbsd/ppc64": false,
+ "openbsd/riscv64": false,
+ "plan9/386": false,
+ "plan9/amd64": false,
+ "plan9/arm": false,
+ "solaris/amd64": true,
+ "windows/386": true,
+ "windows/amd64": true,
+ "windows/arm": false,
+ "windows/arm64": true,
+}
+
+// List of platforms that are marked as broken ports.
+// These require -force flag to build, and also
+// get filtered out of cgoEnabled for 'dist list'.
+// See go.dev/issue/56679.
+var broken = map[string]bool{
+ "linux/sparc64": true, // An incomplete port. See CL 132155.
+ "openbsd/mips64": true, // Broken: go.dev/issue/58110.
+ "openbsd/riscv64": true, // An incomplete port: go.dev/issue/55999.
+}
+
+// List of platforms which are first class ports. See go.dev/issue/38874.
+var firstClass = map[string]bool{
+ "darwin/amd64": true,
+ "darwin/arm64": true,
+ "linux/386": true,
+ "linux/amd64": true,
+ "linux/arm": true,
+ "linux/arm64": true,
+ "windows/386": true,
+ "windows/amd64": true,
+}
+
+// We only need CC if cgo is forced on, or if the platform requires external linking.
+// Otherwise the go command will automatically disable it.
+func needCC() bool {
+ return os.Getenv("CGO_ENABLED") == "1" || mustLinkExternal(gohostos, gohostarch, false)
+}
+
+func checkCC() {
+ if !needCC() {
+ return
+ }
+ cc1 := defaultcc[""]
+ if cc1 == "" {
+ cc1 = "gcc"
+ for _, os := range clangos {
+ if gohostos == os {
+ cc1 = "clang"
+ break
+ }
+ }
+ }
+ cc, err := quotedSplit(cc1)
+ if err != nil {
+ fatalf("split CC: %v", err)
+ }
+ var ccHelp = append(cc, "--help")
+
+ if output, err := exec.Command(ccHelp[0], ccHelp[1:]...).CombinedOutput(); err != nil {
+ outputHdr := ""
+ if len(output) > 0 {
+ outputHdr = "\nCommand output:\n\n"
+ }
+ fatalf("cannot invoke C compiler %q: %v\n\n"+
+ "Go needs a system C compiler for use with cgo.\n"+
+ "To set a C compiler, set CC=the-compiler.\n"+
+ "To disable cgo, set CGO_ENABLED=0.\n%s%s", cc, err, outputHdr, output)
+ }
+}
+
+func defaulttarg() string {
+ // xgetwd might return a path with symlinks fully resolved, and if
+ // there happens to be symlinks in goroot, then the hasprefix test
+ // will never succeed. Instead, we use xrealwd to get a canonical
+ // goroot/src before the comparison to avoid this problem.
+ pwd := xgetwd()
+ src := pathf("%s/src/", goroot)
+ real_src := xrealwd(src)
+ if !strings.HasPrefix(pwd, real_src) {
+ fatalf("current directory %s is not under %s", pwd, real_src)
+ }
+ pwd = pwd[len(real_src):]
+ // guard against xrealwd returning the directory without the trailing /
+ pwd = strings.TrimPrefix(pwd, "/")
+
+ return pwd
+}
+
+// Install installs the list of packages named on the command line.
+func cmdinstall() {
+ xflagparse(-1)
+
+ if flag.NArg() == 0 {
+ install(defaulttarg())
+ }
+
+ for _, arg := range flag.Args() {
+ install(arg)
+ }
+}
+
+// Clean deletes temporary objects.
+func cmdclean() {
+ xflagparse(0)
+ clean()
+}
+
+// Banner prints the 'now you've installed Go' banner.
+func cmdbanner() {
+ xflagparse(0)
+ banner()
+}
+
+func banner() {
+ if vflag > 0 {
+ xprintf("\n")
+ }
+ xprintf("---\n")
+ xprintf("Installed Go for %s/%s in %s\n", goos, goarch, goroot)
+ xprintf("Installed commands in %s\n", gorootBin)
+
+ if !xsamefile(goroot_final, goroot) {
+ // If the files are to be moved, don't check that gobin
+ // is on PATH; assume they know what they are doing.
+ } else if gohostos == "plan9" {
+ // Check that GOROOT/bin is bound before /bin.
+ pid := strings.Replace(readfile("#c/pid"), " ", "", -1)
+ ns := fmt.Sprintf("/proc/%s/ns", pid)
+ if !strings.Contains(readfile(ns), fmt.Sprintf("bind -b %s /bin", gorootBin)) {
+ xprintf("*** You need to bind %s before /bin.\n", gorootBin)
+ }
+ } else {
+ // Check that GOROOT/bin appears in $PATH.
+ pathsep := ":"
+ if gohostos == "windows" {
+ pathsep = ";"
+ }
+ path := os.Getenv("PATH")
+ if p, ok := os.LookupEnv("DIST_UNMODIFIED_PATH"); ok {
+ // Scripts that modify $PATH and then run dist should also provide
+ // dist with an unmodified copy of $PATH via $DIST_UNMODIFIED_PATH.
+ // Use it here when determining if the user still needs to update
+ // their $PATH. See go.dev/issue/42563.
+ path = p
+ }
+ if !strings.Contains(pathsep+path+pathsep, pathsep+gorootBin+pathsep) {
+ xprintf("*** You need to add %s to your PATH.\n", gorootBin)
+ }
+ }
+
+ if !xsamefile(goroot_final, goroot) {
+ xprintf("\n"+
+ "The binaries expect %s to be copied or moved to %s\n",
+ goroot, goroot_final)
+ }
+}
+
+// Version prints the Go version.
+func cmdversion() {
+ xflagparse(0)
+ xprintf("%s\n", findgoversion())
+}
+
+// cmdlist lists all supported platforms.
+func cmdlist() {
+ jsonFlag := flag.Bool("json", false, "produce JSON output")
+ brokenFlag := flag.Bool("broken", false, "include broken ports")
+ xflagparse(0)
+
+ var plats []string
+ for p := range cgoEnabled {
+ if broken[p] && !*brokenFlag {
+ continue
+ }
+ plats = append(plats, p)
+ }
+ sort.Strings(plats)
+
+ if !*jsonFlag {
+ for _, p := range plats {
+ xprintf("%s\n", p)
+ }
+ return
+ }
+
+ type jsonResult struct {
+ GOOS string
+ GOARCH string
+ CgoSupported bool
+ FirstClass bool
+ Broken bool `json:",omitempty"`
+ }
+ var results []jsonResult
+ for _, p := range plats {
+ fields := strings.Split(p, "/")
+ results = append(results, jsonResult{
+ GOOS: fields[0],
+ GOARCH: fields[1],
+ CgoSupported: cgoEnabled[p],
+ FirstClass: firstClass[p],
+ Broken: broken[p],
+ })
+ }
+ out, err := json.MarshalIndent(results, "", "\t")
+ if err != nil {
+ fatalf("json marshal error: %v", err)
+ }
+ if _, err := os.Stdout.Write(out); err != nil {
+ fatalf("write failed: %v", err)
+ }
+}
+
+func setNoOpt() {
+ for _, gcflag := range strings.Split(gogcflags, " ") {
+ if gcflag == "-N" || gcflag == "-l" {
+ noOpt = true
+ break
+ }
+ }
+}