| // 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 ( |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "runtime" |
| "strings" |
| ) |
| |
| // BootstrapVersion returns the Go bootstrap version required |
| // for the named version of Go. If the version needs no bootstrap |
| // (that is, if it's before Go 1.5), BootstrapVersion returns an empty version. |
| func BootstrapVersion(version string) (string, error) { |
| if Compare(version, Go(1, 5)) < 0 { |
| return "", nil |
| } |
| if Compare(version, Go(1, 20)) < 0 { |
| return Go(1, 4), nil |
| } |
| if Compare(version, Go(1, 22)) < 0 { |
| return Go(1, 17), nil |
| } |
| if Compare(version, Go(1, 1000)) > 0 { |
| return "", fmt.Errorf("invalid version %q", version) |
| } |
| for i := 24; ; i += 2 { |
| if Compare(version, Go(1, i)) < 0 { |
| // 1.24 will switch to 1.22; before that we used 1.20 |
| // 1.26 will switch to 1.24; before that we used 1.22 |
| // ... |
| return Go(1, i-4), nil |
| } |
| } |
| } |
| |
| // BootstrapDir returns the name of a directory containing the GOROOT |
| // for a fully built bootstrap toolchain with the given version. |
| func (r *Report) BootstrapDir(version string) (dir string, err error) { |
| for _, b := range r.Bootstraps { |
| if b.Version == version { |
| return b.Dir, b.Err |
| } |
| } |
| |
| dir = filepath.Join(r.Work, "bootstrap-"+version) |
| b := &Bootstrap{ |
| Version: version, |
| Dir: dir, |
| Err: fmt.Errorf("bootstrap %s cycle", version), |
| } |
| b.Log.Name = "bootstrap " + version |
| r.Bootstraps = append(r.Bootstraps, b) |
| |
| defer func() { |
| if err != nil { |
| b.Log.Printf("%v", err) |
| err = fmt.Errorf("bootstrap %s: %v", version, err) |
| } |
| b.Err = err |
| }() |
| |
| if r.Full { |
| return b.Dir, r.BootstrapBuild(b, version) |
| } |
| return b.Dir, r.BootstrapPrebuilt(b, version) |
| } |
| |
| // BootstrapPrebuilt downloads a prebuilt toolchain. |
| func (r *Report) BootstrapPrebuilt(b *Bootstrap, version string) error { |
| for _, dl := range r.dl { |
| if strings.HasPrefix(dl.Version, version+".") { |
| b.Log.Printf("using %s binary distribution for %s", dl.Version, version) |
| version = dl.Version |
| break |
| } |
| } |
| |
| url := "https://go.dev/dl/" + version + "." + runtime.GOOS + "-" + runtime.GOARCH + ".tar.gz" |
| unpack := UnpackTarGz |
| if runtime.GOOS == "windows" { |
| url = strings.TrimSuffix(url, ".tar.gz") + ".zip" |
| unpack = UnpackZip |
| } |
| |
| arch, err := Get(&b.Log, url) |
| if err != nil { |
| return err |
| } |
| if err := unpack(b.Dir, arch); err != nil { |
| return err |
| } |
| b.Dir = filepath.Join(b.Dir, "go") |
| return nil |
| } |
| |
| // BootstrapBuild builds the named bootstrap toolchain and returns |
| // the directory containing the GOROOT for the build. |
| func (r *Report) BootstrapBuild(b *Bootstrap, version string) error { |
| tgz, err := GerritTarGz(&b.Log, "go", "refs/heads/release-branch."+version) |
| if err != nil { |
| return err |
| } |
| if err := UnpackTarGz(b.Dir, tgz); err != nil { |
| return err |
| } |
| return r.Build(&b.Log, b.Dir, version, nil, nil) |
| } |
| |
| // Build runs a Go make.bash/make.bat/make.rc in the named goroot |
| // which contains the named version of Go, |
| // with the additional environment and command-line arguments. |
| // The returned error is not logged. |
| // If an error happens during the build, the full output is logged to log, |
| // but the returned error simply says "make.bash in <goroot> failed". |
| func (r *Report) Build(log *Log, goroot, version string, env, args []string) error { |
| bver, err := BootstrapVersion(version) |
| if err != nil { |
| return err |
| } |
| var bdir string |
| if bver != "" { |
| bdir, err = r.BootstrapDir(bver) |
| if err != nil { |
| return err |
| } |
| } |
| |
| make := "./make.bash" |
| switch runtime.GOOS { |
| case "windows": |
| make = `.\make.bat` |
| case "plan9": |
| make = "./make.rc" |
| } |
| cmd := exec.Command(make, args...) |
| cmd.Dir = filepath.Join(goroot, "src") |
| cmd.Env = append(os.Environ(), |
| "GOROOT="+goroot, |
| "GOROOT_BOOTSTRAP="+bdir, |
| "GOTOOLCHAIN=local", // keep bootstraps honest |
| |
| // Clear various settings that would leak into defaults |
| // in the toolchain and change the generated binaries. |
| // These are unlikely to be set to begin with, except |
| // maybe $CC and $CXX, but if they are, the failures would |
| // be mysterious. |
| "CC=", |
| "CC_FOR_TARGET=", |
| "CGO_ENABLED=", |
| "CXX=", |
| "CXX_FOR_TARGET=", |
| "GO386=", |
| "GOAMD64=", |
| "GOARM=", |
| "GOBIN=", |
| "GOEXPERIMENT=", |
| "GOMIPS64=", |
| "GOMIPS=", |
| "GOPATH=", |
| "GOPPC64=", |
| "GOROOT_FINAL=", |
| "GO_EXTLINK_ENABLED=", |
| "GO_GCFLAGS=", |
| "GO_LDFLAGS=", |
| "GO_LDSO=", |
| "PKG_CONFIG=", |
| ) |
| cmd.Env = append(cmd.Env, env...) |
| log.Printf("running %s env=%v args=%v\nGOROOT=%s\nGOROOT_BOOTSTRAP=%s\n", |
| make, env, args, goroot, bdir) |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| log.Printf("%s: %s\n%s", make, err, out) |
| return fmt.Errorf("%s in %s failed", make, goroot) |
| } |
| log.Printf("%s completed:\n%s", make, out) |
| return nil |
| } |