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

     1  // Copyright 2016 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  /*
     6  Genbootstrap prepares GOROOT_BOOTSTRAP tarballs suitable for
     7  use on builders. It's a wrapper around bootstrap.bash. After
     8  bootstrap.bash produces the full output, genbootstrap trims it up,
     9  removing unnecessary and unwanted files.
    10  
    11  Usage:
    12  
    13  	genbootstrap [-upload] [-rev=rev] [-v] GOOS-GOARCH[-suffix]...
    14  
    15  The argument list can be a single glob pattern (for example '*'),
    16  which expands to all known targets matching that pattern.
    17  
    18  Deprecated: As of Go 1.21.0, genbootstrap is superseded
    19  by make.bash -distpack and doesn't need to be used anymore.
    20  The one exception are GOOS=windows targets, since their
    21  go.dev/dl downloads are in .zip format but the builders
    22  in x/build support pushing .tar.gz format only.
    23  */
    24  package main
    25  
    26  import (
    27  	"bytes"
    28  	"context"
    29  	"flag"
    30  	"fmt"
    31  	"io"
    32  	"log"
    33  	"net/http"
    34  	"os"
    35  	"os/exec"
    36  	"path"
    37  	"path/filepath"
    38  	"sort"
    39  	"strings"
    40  
    41  	"cloud.google.com/go/storage"
    42  	"golang.org/x/build/dashboard"
    43  	"golang.org/x/build/internal/envutil"
    44  	"golang.org/x/build/maintner/maintnerd/maintapi/version"
    45  )
    46  
    47  var skipBuild = flag.String("skip_build", "", "skip bootstrap.bash and reuse output in `dir` instead")
    48  var upload = flag.Bool("upload", false, "upload outputs to gs://go-builder-data/")
    49  var verbose = flag.Bool("v", false, "show verbose output")
    50  var rev = flag.String("rev", "go1.17.13", "build Go at Git revision `rev`")
    51  
    52  func usage() {
    53  	fmt.Fprintln(os.Stderr, "Usage: genbootstrap GOOS-GOARCH[-GO$GOARCH]... (or a glob pattern like '*')")
    54  	flag.PrintDefaults()
    55  }
    56  
    57  func main() {
    58  	flag.Usage = usage
    59  	flag.Parse()
    60  	if flag.NArg() < 1 {
    61  		flag.Usage()
    62  		os.Exit(2)
    63  	}
    64  
    65  	list := flag.Args()
    66  	if len(list) == 1 && strings.ContainsAny(list[0], "*?[]") {
    67  		pattern := list[0]
    68  		list = nil
    69  		for _, name := range allPairs() {
    70  			if ok, err := path.Match(pattern, name); ok {
    71  				list = append(list, name)
    72  			} else if err != nil {
    73  				log.Fatalf("invalid match: %v", err)
    74  			}
    75  		}
    76  		if len(list) == 0 {
    77  			log.Fatalf("no matches for %q", pattern)
    78  		}
    79  		log.Printf("expanded %s: %v", pattern, list)
    80  	}
    81  
    82  	var nonWindowsTargets []string
    83  	for _, pair := range list {
    84  		f := strings.Split(pair, "-")
    85  		if len(f) != 2 && len(f) != 3 {
    86  			log.Fatalf("invalid target: %q", pair)
    87  		}
    88  		if goos := f[0]; goos != "windows" {
    89  			nonWindowsTargets = append(nonWindowsTargets, pair)
    90  		}
    91  	}
    92  
    93  	if x, _ := version.Go1PointX(*rev); x >= 21 && len(nonWindowsTargets) > 0 {
    94  		log.Fatalf("genbootstrap isn't needed to build Go 1.21.0 and newer bootstrap toolchains for %v, "+
    95  			"they're already built with make.bash -distpack and made available at go.dev/dl for all ports "+
    96  			"(GOOS=windows targets are permitted; builders need .tar.gz format so go.dev/dl can't be used as is)", nonWindowsTargets)
    97  	}
    98  
    99  	dir, err := os.MkdirTemp("", "genbootstrap-*")
   100  	if err != nil {
   101  		log.Fatal(err)
   102  	}
   103  	goroot := filepath.Join(dir, "goroot")
   104  	if err := os.MkdirAll(goroot, 0777); err != nil {
   105  		log.Fatal(err)
   106  	}
   107  
   108  	log.Printf("Bootstrapping in %s at revision %s\n", dir, *rev)
   109  
   110  	resp, err := http.Get("https://go.googlesource.com/go/+archive/" + *rev + ".tar.gz")
   111  	if err != nil {
   112  		log.Fatal(err)
   113  	}
   114  	if resp.StatusCode != 200 {
   115  		body, _ := io.ReadAll(io.LimitReader(resp.Body, 64<<10))
   116  		log.Fatalf("fetching %s: %v\n%s", *rev, resp.Status, body)
   117  	}
   118  
   119  	cmd := exec.Command("tar", "-C", goroot, "-xzf", "-")
   120  	cmd.Stdin = resp.Body
   121  	out, err := cmd.CombinedOutput()
   122  	if err != nil {
   123  		log.Fatalf("tar: %v\n%s", err, out)
   124  	}
   125  
   126  	// Work around GO_LDSO bug by removing implicit setting from make.bash.
   127  	// See go.dev/issue/54196 and go.dev/issue/54197.
   128  	// Even if those are fixed, the old toolchains we are using for bootstrap won't get the fix.
   129  	makebash := filepath.Join(goroot, "src/make.bash")
   130  	data, err := os.ReadFile(makebash)
   131  	if err != nil {
   132  		log.Fatal(err)
   133  	}
   134  	data = bytes.ReplaceAll(data, []byte("GO_LDSO"), []byte("GO_LDSO_BUG"))
   135  	if err := os.WriteFile(makebash, data, 0666); err != nil {
   136  		log.Fatal(err)
   137  	}
   138  
   139  	var storageClient *storage.Client
   140  	if *upload {
   141  		ctx := context.Background()
   142  		storageClient, err = storage.NewClient(ctx)
   143  		if err != nil {
   144  			log.Fatalf("storage.NewClient: %v", err)
   145  		}
   146  	}
   147  
   148  	gorootSrc := filepath.Join(goroot, "src")
   149  List:
   150  	for _, pair := range list {
   151  		f := strings.Split(pair, "-")
   152  		goos, goarch, gosuffix := f[0], f[1], ""
   153  		if len(f) == 3 {
   154  			gosuffix = "-" + f[2]
   155  		}
   156  
   157  		log.Printf("# %s-%s%s\n", goos, goarch, gosuffix)
   158  
   159  		tgz := filepath.Join(dir, "gobootstrap-"+goos+"-"+goarch+gosuffix+"-"+*rev+".tar.gz")
   160  		os.Remove(tgz)
   161  		outDir := filepath.Join(dir, "go-"+goos+"-"+goarch+"-bootstrap")
   162  		if *skipBuild != "" {
   163  			outDir = *skipBuild
   164  		} else {
   165  			os.RemoveAll(outDir)
   166  			cmd := exec.Command(filepath.Join(gorootSrc, "bootstrap.bash"))
   167  			envutil.SetDir(cmd, gorootSrc)
   168  			envutil.SetEnv(cmd,
   169  				"GOROOT="+goroot,
   170  				"CGO_ENABLED=0",
   171  				"GOOS="+goos,
   172  				"GOARCH="+goarch,
   173  				"GOROOT_BOOTSTRAP="+os.Getenv("GOROOT_BOOTSTRAP"),
   174  			)
   175  			if gosuffix != "" {
   176  				envutil.SetEnv(cmd, "GO"+strings.ToUpper(goarch)+"="+gosuffix[len("-"):])
   177  			}
   178  			if *verbose {
   179  				cmd.Stdout = os.Stdout
   180  				cmd.Stderr = os.Stderr
   181  				if err := cmd.Run(); err != nil {
   182  					log.Print(err)
   183  					continue List
   184  				}
   185  			} else {
   186  				if out, err := cmd.CombinedOutput(); err != nil {
   187  					os.Stdout.Write(out)
   188  					log.Print(err)
   189  					continue List
   190  				}
   191  			}
   192  
   193  			// bootstrap.bash makes a bzipped tar file too,
   194  			// but it's fat and full of stuff we don't need. Delete it.
   195  			os.Remove(outDir + ".tbz")
   196  		}
   197  
   198  		if err := filepath.Walk(outDir, func(path string, fi os.FileInfo, err error) error {
   199  			if err != nil {
   200  				return err
   201  			}
   202  			rel := strings.TrimPrefix(strings.TrimPrefix(path, outDir), "/")
   203  			base := filepath.Base(path)
   204  			var pkgrel string // relative to pkg/<goos>_<goarch>/, or empty
   205  			if strings.HasPrefix(rel, "pkg/") && strings.Count(rel, "/") >= 2 {
   206  				pkgrel = strings.TrimPrefix(rel, "pkg/")
   207  				pkgrel = pkgrel[strings.Index(pkgrel, "/")+1:]
   208  				if *verbose {
   209  					log.Printf("rel %q => %q", rel, pkgrel)
   210  				}
   211  			}
   212  			remove := func() error {
   213  				if err := os.RemoveAll(path); err != nil {
   214  					return err
   215  				}
   216  				if fi.IsDir() {
   217  					return filepath.SkipDir
   218  				}
   219  				return nil
   220  			}
   221  			switch pkgrel {
   222  			case "cmd":
   223  				return remove()
   224  			}
   225  			switch rel {
   226  			case "api",
   227  				"bin/gofmt",
   228  				"doc",
   229  				"misc/android",
   230  				"misc/cgo",
   231  				"misc/chrome",
   232  				"misc/swig",
   233  				"test":
   234  				return remove()
   235  			}
   236  			if base == "testdata" {
   237  				return remove()
   238  			}
   239  			if strings.HasPrefix(rel, "pkg/tool/") {
   240  				switch base {
   241  				case "addr2line", "api", "cgo", "cover",
   242  					"dist", "doc", "fix", "nm",
   243  					"objdump", "pack", "pprof",
   244  					"trace", "vet", "yacc":
   245  					return remove()
   246  				}
   247  			}
   248  			if fi.IsDir() {
   249  				return nil
   250  			}
   251  			if isEditorJunkFile(path) {
   252  				return remove()
   253  			}
   254  			if !fi.Mode().IsRegular() {
   255  				return remove()
   256  			}
   257  			if strings.HasSuffix(path, "_test.go") {
   258  				return remove()
   259  			}
   260  			if *verbose {
   261  				log.Printf("keeping: %s\n", rel)
   262  			}
   263  			return nil
   264  		}); err != nil {
   265  			log.Print(err)
   266  			continue List
   267  		}
   268  
   269  		cmd := exec.Command("tar", "zcf", tgz, ".")
   270  		envutil.SetDir(cmd, outDir)
   271  		if *verbose {
   272  			cmd.Stdout = os.Stdout
   273  			cmd.Stderr = os.Stderr
   274  			if err := cmd.Run(); err != nil {
   275  				log.Print(err)
   276  				continue List
   277  			}
   278  		} else {
   279  			if out, err := cmd.CombinedOutput(); err != nil {
   280  				os.Stdout.Write(out)
   281  				log.Print(err)
   282  				continue List
   283  			}
   284  		}
   285  
   286  		log.Printf("Built %s", tgz)
   287  		if *upload {
   288  			project := "symbolic-datum-552"
   289  			bucket := "go-builder-data"
   290  			object := filepath.Base(tgz)
   291  			w := storageClient.Bucket(bucket).Object(object).NewWriter(context.Background())
   292  			// If you don't give the owners access, the web UI seems to
   293  			// have a bug and doesn't have access to see that it's public, so
   294  			// won't render the "Shared Publicly" link. So we do that, even
   295  			// though it's dumb and unnecessary otherwise:
   296  			w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.ACLEntity("project-owners-" + project), Role: storage.RoleOwner})
   297  			w.ACL = append(w.ACL, storage.ACLRule{Entity: storage.AllUsers, Role: storage.RoleReader})
   298  			f, err := os.Open(tgz)
   299  			if err != nil {
   300  				log.Print(err)
   301  				continue List
   302  			}
   303  			w.ContentType = "application/octet-stream"
   304  			_, err1 := io.Copy(w, f)
   305  			f.Close()
   306  			err = w.Close()
   307  			if err == nil {
   308  				err = err1
   309  			}
   310  			if err != nil {
   311  				log.Printf("Failed to upload %s: %v", tgz, err)
   312  				continue List
   313  			}
   314  			log.Printf("Uploaded gs://%s/%s", bucket, object)
   315  		}
   316  	}
   317  }
   318  
   319  func isEditorJunkFile(path string) bool {
   320  	path = filepath.Base(path)
   321  	if strings.HasPrefix(path, "#") && strings.HasSuffix(path, "#") {
   322  		return true
   323  	}
   324  	if strings.HasSuffix(path, "~") {
   325  		return true
   326  	}
   327  	return false
   328  }
   329  
   330  // allPairs returns a list of all known builder GOOS/GOARCH pairs.
   331  func allPairs() []string {
   332  	have := make(map[string]bool)
   333  	var list []string
   334  	add := func(name string) {
   335  		if !have[name] {
   336  			have[name] = true
   337  			list = append(list, name)
   338  		}
   339  	}
   340  
   341  	for _, b := range dashboard.Builders {
   342  		f := strings.Split(b.Name, "-")
   343  		switch f[0] {
   344  		case "android", "ios", "js", "misc":
   345  			// skip
   346  			continue
   347  		}
   348  		name := f[0] + "-" + f[1]
   349  		if f[1] == "arm" {
   350  			add(name + "-5")
   351  			add(name + "-7")
   352  			continue
   353  		}
   354  		add(name)
   355  	}
   356  	sort.Strings(list)
   357  	return list
   358  }