golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gorebuild/build.go (about)

     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  package main
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  )
    15  
    16  // BootstrapVersion returns the Go bootstrap version required
    17  // for the named version of Go. If the version needs no bootstrap
    18  // (that is, if it's before Go 1.5), BootstrapVersion returns an empty version.
    19  func BootstrapVersion(version string) (string, error) {
    20  	if Compare(version, Go(1, 5)) < 0 {
    21  		return "", nil
    22  	}
    23  	if Compare(version, Go(1, 20)) < 0 {
    24  		return Go(1, 4), nil
    25  	}
    26  	if Compare(version, Go(1, 22)) < 0 {
    27  		return Go(1, 17), nil
    28  	}
    29  	if Compare(version, Go(1, 1000)) > 0 {
    30  		return "", fmt.Errorf("invalid version %q", version)
    31  	}
    32  	for i := 24; ; i += 2 {
    33  		if Compare(version, Go(1, i)) < 0 {
    34  			// 1.24 will switch to 1.22; before that we used 1.20
    35  			// 1.26 will switch to 1.24; before that we used 1.22
    36  			// ...
    37  			return Go(1, i-4), nil
    38  		}
    39  	}
    40  }
    41  
    42  // BootstrapDir returns the name of a directory containing the GOROOT
    43  // for a fully built bootstrap toolchain with the given version.
    44  func (r *Report) BootstrapDir(version string) (dir string, err error) {
    45  	for _, b := range r.Bootstraps {
    46  		if b.Version == version {
    47  			return b.Dir, b.Err
    48  		}
    49  	}
    50  
    51  	dir = filepath.Join(r.Work, "bootstrap-"+version)
    52  	b := &Bootstrap{
    53  		Version: version,
    54  		Dir:     dir,
    55  		Err:     fmt.Errorf("bootstrap %s cycle", version),
    56  	}
    57  	b.Log.Name = "bootstrap " + version
    58  	r.Bootstraps = append(r.Bootstraps, b)
    59  
    60  	defer func() {
    61  		if err != nil {
    62  			b.Log.Printf("%v", err)
    63  			err = fmt.Errorf("bootstrap %s: %v", version, err)
    64  		}
    65  		b.Err = err
    66  	}()
    67  
    68  	if r.Full {
    69  		return b.Dir, r.BootstrapBuild(b, version)
    70  	}
    71  	return b.Dir, r.BootstrapPrebuilt(b, version)
    72  }
    73  
    74  // BootstrapPrebuilt downloads a prebuilt toolchain.
    75  func (r *Report) BootstrapPrebuilt(b *Bootstrap, version string) error {
    76  	for _, dl := range r.dl {
    77  		if strings.HasPrefix(dl.Version, version+".") {
    78  			b.Log.Printf("using %s binary distribution for %s", dl.Version, version)
    79  			version = dl.Version
    80  			break
    81  		}
    82  	}
    83  
    84  	url := "https://go.dev/dl/" + version + "." + runtime.GOOS + "-" + runtime.GOARCH + ".tar.gz"
    85  	unpack := UnpackTarGz
    86  	if runtime.GOOS == "windows" {
    87  		url = strings.TrimSuffix(url, ".tar.gz") + ".zip"
    88  		unpack = UnpackZip
    89  	}
    90  
    91  	arch, err := Get(&b.Log, url)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if err := unpack(b.Dir, arch); err != nil {
    96  		return err
    97  	}
    98  	b.Dir = filepath.Join(b.Dir, "go")
    99  	return nil
   100  }
   101  
   102  // BootstrapBuild builds the named bootstrap toolchain and returns
   103  // the directory containing the GOROOT for the build.
   104  func (r *Report) BootstrapBuild(b *Bootstrap, version string) error {
   105  	tgz, err := GerritTarGz(&b.Log, "go", "refs/heads/release-branch."+version)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	if err := UnpackTarGz(b.Dir, tgz); err != nil {
   110  		return err
   111  	}
   112  	return r.Build(&b.Log, b.Dir, version, nil, nil)
   113  }
   114  
   115  // Build runs a Go make.bash/make.bat/make.rc in the named goroot
   116  // which contains the named version of Go,
   117  // with the additional environment and command-line arguments.
   118  // The returned error is not logged.
   119  // If an error happens during the build, the full output is logged to log,
   120  // but the returned error simply says "make.bash in <goroot> failed".
   121  func (r *Report) Build(log *Log, goroot, version string, env, args []string) error {
   122  	bver, err := BootstrapVersion(version)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	var bdir string
   127  	if bver != "" {
   128  		bdir, err = r.BootstrapDir(bver)
   129  		if err != nil {
   130  			return err
   131  		}
   132  	}
   133  
   134  	make := "./make.bash"
   135  	switch runtime.GOOS {
   136  	case "windows":
   137  		make = `.\make.bat`
   138  	case "plan9":
   139  		make = "./make.rc"
   140  	}
   141  	cmd := exec.Command(make, args...)
   142  	cmd.Dir = filepath.Join(goroot, "src")
   143  	cmd.Env = append(os.Environ(),
   144  		"GOROOT="+goroot,
   145  		"GOROOT_BOOTSTRAP="+bdir,
   146  		"GOTOOLCHAIN=local", // keep bootstraps honest
   147  
   148  		// Clear various settings that would leak into defaults
   149  		// in the toolchain and change the generated binaries.
   150  		// These are unlikely to be set to begin with, except
   151  		// maybe $CC and $CXX, but if they are, the failures would
   152  		// be mysterious.
   153  		"CC=",
   154  		"CC_FOR_TARGET=",
   155  		"CGO_ENABLED=",
   156  		"CXX=",
   157  		"CXX_FOR_TARGET=",
   158  		"GO386=",
   159  		"GOAMD64=",
   160  		"GOARM=",
   161  		"GOBIN=",
   162  		"GOEXPERIMENT=",
   163  		"GOMIPS64=",
   164  		"GOMIPS=",
   165  		"GOPATH=",
   166  		"GOPPC64=",
   167  		"GOROOT_FINAL=",
   168  		"GO_EXTLINK_ENABLED=",
   169  		"GO_GCFLAGS=",
   170  		"GO_LDFLAGS=",
   171  		"GO_LDSO=",
   172  		"PKG_CONFIG=",
   173  	)
   174  	cmd.Env = append(cmd.Env, env...)
   175  	log.Printf("running %s env=%v args=%v\nGOROOT=%s\nGOROOT_BOOTSTRAP=%s\n",
   176  		make, env, args, goroot, bdir)
   177  	out, err := cmd.CombinedOutput()
   178  	if err != nil {
   179  		log.Printf("%s: %s\n%s", make, err, out)
   180  		return fmt.Errorf("%s in %s failed", make, goroot)
   181  	}
   182  	log.Printf("%s completed:\n%s", make, out)
   183  	return nil
   184  }