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 }