blob: a3fa773ee34ec233f37cc7a756dea7a0f33ed359 [file] [log] [blame]
Andrew Gerrandf83f3e42015-02-02 12:05:01 +00001// Copyright 2015 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// Command release builds a Go release.
6package main
7
8import (
9 "bytes"
10 "crypto/rand"
11 "flag"
12 "fmt"
13 "io"
14 "log"
15 "os"
Andrew Gerrand319667f2015-02-04 16:04:07 +000016 "path"
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000017 "path/filepath"
Andrew Gerrand319667f2015-02-04 16:04:07 +000018 "strings"
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000019 "sync"
Andrew Gerrand54064352015-02-19 11:53:44 +110020 "time"
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000021
22 "golang.org/x/build/auth"
23 "golang.org/x/build/buildlet"
24 "golang.org/x/build/dashboard"
25 "golang.org/x/oauth2"
26 "google.golang.org/api/compute/v1"
27)
28
29var (
Andrew Gerrand15af43e2015-02-11 11:47:03 +110030 target = flag.String("target", "", "If specified, build specific target platform ('linux-amd64')")
31
Andrew Gerrand319667f2015-02-04 16:04:07 +000032 rev = flag.String("rev", "", "Go revision to build")
33 toolsRev = flag.String("tools", "", "Tools revision to build")
Andrew Gerrand15af43e2015-02-11 11:47:03 +110034 tourRev = flag.String("tour", "master", "Tour revision to include")
35 blogRev = flag.String("blog", "master", "Blog revision to include")
Andrew Gerrande88955c2015-02-19 10:36:32 +110036 netRev = flag.String("net", "master", "Net revision to include")
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000037
Andrew Gerrand15af43e2015-02-11 11:47:03 +110038 project = flag.String("project", "symbolic-datum-552", "Google Cloud Project")
39 zone = flag.String("zone", "us-central1-a", "Compute Engine zone")
40)
Andrew Gerrand319667f2015-02-04 16:04:07 +000041
42type Build struct {
43 OS, Arch string
44
45 Race bool // Build race detector.
46 Static bool // Statically-link binaries.
47
48 Builder string // Key for dashboard.Builders.
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000049}
50
Andrew Gerrand319667f2015-02-04 16:04:07 +000051func (b *Build) String() string {
52 return fmt.Sprintf("%v-%v", b.OS, b.Arch)
53}
54
55var builds = []*Build{
56 {
57 OS: "linux",
58 Arch: "386",
59 Builder: "linux-amd64",
60 },
61 {
62 OS: "linux",
63 Arch: "amd64",
64 Race: true,
65 Static: true,
66 Builder: "linux-amd64",
67 },
68 {
69 OS: "freebsd",
70 Arch: "386",
71 Builder: "freebsd-386-gce101",
72 },
73 {
74 OS: "freebsd",
75 Arch: "amd64",
76 Race: true,
77 Builder: "freebsd-amd64-gce101",
78 },
79}
80
Andrew Gerrand15af43e2015-02-11 11:47:03 +110081const (
82 toolsRepo = "golang.org/x/tools"
83 blogRepo = "golang.org/x/blog"
84 tourRepo = "golang.org/x/tour"
85)
Andrew Gerrand319667f2015-02-04 16:04:07 +000086
87var toolPaths = []string{
88 "golang.org/x/tools/cmd/cover",
89 "golang.org/x/tools/cmd/godoc",
90 "golang.org/x/tools/cmd/vet",
Andrew Gerrand15af43e2015-02-11 11:47:03 +110091 "golang.org/x/tour/gotour",
Andrew Gerrand319667f2015-02-04 16:04:07 +000092}
Andrew Gerrandf83f3e42015-02-02 12:05:01 +000093
94var preBuildCleanFiles = []string{
95 ".gitattributes",
96 ".gitignore",
97 ".hgignore",
98 ".hgtags",
99 "misc/dashboard",
100}
101
102var postBuildCleanFiles = []string{
103 "VERSION.cache",
104}
105
106func main() {
107 flag.Parse()
108
Andrew Gerrand319667f2015-02-04 16:04:07 +0000109 if *rev == "" {
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000110 log.Fatal("must specify -rev flag")
111 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000112 if *toolsRev == "" {
113 log.Fatal("must specify -tools flag")
114 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000115
116 var wg sync.WaitGroup
Andrew Gerrand319667f2015-02-04 16:04:07 +0000117 for _, b := range builds {
118 b := b
119 if *target != "" && b.String() != *target {
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000120 continue
121 }
122 wg.Add(1)
123 go func() {
124 defer wg.Done()
Andrew Gerrand319667f2015-02-04 16:04:07 +0000125 b.make() // error logged by make function
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000126 }()
127 }
128 // TODO(adg): show progress of running builders
129 wg.Wait()
130}
131
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700132func (b *Build) shouldStartNewVM() bool {
133 if strings.HasPrefix(b.Builder, "darwin-") {
134 return false
135 }
136 return true
137}
138
139func (b *Build) buildlet() (*buildlet.Client, error) {
140 if b.shouldStartNewVM() {
141 return b.buildletFromVM()
142 }
143 panic("TODO: obtain a buildlet via the coordiantor, where the OS X machines are dialed into via the reverse pool")
144}
145
146func (b *Build) buildletFromVM() (*buildlet.Client, error) {
Andrew Gerrand319667f2015-02-04 16:04:07 +0000147 bc, ok := dashboard.Builders[b.Builder]
148 if !ok {
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700149 return nil, fmt.Errorf("unknown builder: %v", bc)
Andrew Gerrand319667f2015-02-04 16:04:07 +0000150 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000151 // Start VM
Andrew Gerrand319667f2015-02-04 16:04:07 +0000152 log.Printf("%v: Starting VM.", b)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000153 keypair, err := buildlet.NewKeyPair()
154 if err != nil {
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700155 return nil, err
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000156 }
157 instance := fmt.Sprintf("release-%v-%v-rn%v", os.Getenv("USER"), bc.Name, randHex(6))
158 client, err := buildlet.StartNewVM(projTokenSource(), instance, bc.Name, buildlet.VMOpts{
159 Zone: *zone,
160 ProjectID: *project,
161 TLS: keypair,
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000162 Description: fmt.Sprintf("release buildlet for %s", os.Getenv("USER")),
Andrew Gerrand54064352015-02-19 11:53:44 +1100163 DeleteIn: 1 * time.Hour, // If we don't shut it down, it should kill itself.
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000164 OnInstanceRequested: func() {
Andrew Gerrand319667f2015-02-04 16:04:07 +0000165 log.Printf("%v: Sent create request. Waiting for operation.", b)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000166 },
167 OnInstanceCreated: func() {
Andrew Gerrand319667f2015-02-04 16:04:07 +0000168 log.Printf("%v: Instance created.", b)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000169 },
170 })
171 if err != nil {
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700172 return nil, err
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000173 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000174 log.Printf("%v: Instance %v up.", b, instance)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000175
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700176 client.SetCloseFunc(func() error {
Andrew Gerrand319667f2015-02-04 16:04:07 +0000177 log.Printf("%v: Destroying VM.", b)
Andrew Gerrand376b01d2015-02-03 12:39:25 +0000178 err := client.DestroyVM(projTokenSource(), *project, *zone, instance)
179 if err != nil {
Andrew Gerrand319667f2015-02-04 16:04:07 +0000180 log.Printf("%v: Destroying VM: %v", b, err)
Andrew Gerrand376b01d2015-02-03 12:39:25 +0000181 }
Brad Fitzpatrick2742bc32015-07-03 16:20:37 -0700182 return nil
183 })
184 return client, nil
185}
186
187func (b *Build) make() (err error) {
188 bc, ok := dashboard.Builders[b.Builder]
189 if !ok {
190 return fmt.Errorf("unknown builder: %v", bc)
191 }
192
193 client, err := b.buildlet()
194 if err != nil {
195 return err
196 }
197 defer client.Close()
198 defer func() {
199 if err != nil {
200 log.Printf("%v: %v", b, err)
201 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000202 }()
203
Andrew Gerrand319667f2015-02-04 16:04:07 +0000204 work, err := client.WorkDir()
205 if err != nil {
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000206 return err
207 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000208
Andrew Gerrand319667f2015-02-04 16:04:07 +0000209 // Push source to VM
210 log.Printf("%v: Pushing source to VM.", b)
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100211 const (
212 goDir = "go"
213 goPath = "gopath"
214 )
215 for _, r := range []struct {
216 repo, rev string
217 }{
218 {"go", *rev},
219 {"tools", *toolsRev},
220 {"blog", *blogRev},
221 {"tour", *tourRev},
222 {"net", *netRev},
223 } {
224 dir := goDir
225 if r.repo != "go" {
226 dir = goPath + "/src/golang.org/x/" + r.repo
227 }
228 tar := "https://go.googlesource.com/" + r.repo + "/+archive/" + r.rev + ".tar.gz"
229 if err := client.PutTarFromURL(tar, dir); err != nil {
230 return err
231 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000232 }
233
234 log.Printf("%v: Cleaning goroot (pre-build).", b)
235 if err := client.RemoveAll(addPrefix(goDir, preBuildCleanFiles)...); err != nil {
236 return err
237 }
238
239 // Set up build environment.
240 sep := "/"
241 if b.OS == "windows" {
242 sep = "\\"
243 }
244 env := []string{
245 "GOOS=" + b.OS,
246 "GOARCH=" + b.Arch,
247 "GOHOSTOS=" + b.OS,
248 "GOHOSTARCH=" + b.Arch,
249 "GOROOT_FINAL=" + bc.GorootFinal(),
250 "GOROOT=" + work + sep + goDir,
251 "GOPATH=" + work + sep + goPath,
252 }
253 if b.Static {
254 env = append(env, "GO_DISTFLAGS=-s")
255 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000256
257 // Execute build
Andrew Gerrand319667f2015-02-04 16:04:07 +0000258 log.Printf("%v: Building.", b)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000259 out := new(bytes.Buffer)
Andrew Gerrand319667f2015-02-04 16:04:07 +0000260 mk := filepath.Join(goDir, bc.MakeScript())
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000261 remoteErr, err := client.Exec(mk, buildlet.ExecOpts{
262 Output: out,
Andrew Gerrand319667f2015-02-04 16:04:07 +0000263 ExtraEnv: env,
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000264 })
265 if err != nil {
266 return err
267 }
268 if remoteErr != nil {
269 // TODO(adg): write log to file instead?
270 return fmt.Errorf("Build failed: %v\nOutput:\n%v", remoteErr, out)
271 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000272
Andrew Gerrand319667f2015-02-04 16:04:07 +0000273 goCmd := path.Join(goDir, "bin/go")
274 if b.OS == "windows" {
275 goCmd += ".exe"
276 }
277 runGo := func(args ...string) error {
278 out := new(bytes.Buffer)
279 remoteErr, err := client.Exec(goCmd, buildlet.ExecOpts{
280 Output: out,
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100281 Dir: ".", // root of buildlet work directory
Andrew Gerrand319667f2015-02-04 16:04:07 +0000282 Args: args,
283 ExtraEnv: env,
284 })
285 if err != nil {
286 return err
287 }
288 if remoteErr != nil {
289 return fmt.Errorf("go %v: %v\n%s", strings.Join(args, " "), remoteErr, out)
290 }
291 return nil
292 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000293
Andrew Gerrand319667f2015-02-04 16:04:07 +0000294 if b.Race {
295 log.Printf("%v: Building race detector.", b)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000296
Andrew Gerrand319667f2015-02-04 16:04:07 +0000297 // Because on release branches, go install -a std is a NOP,
298 // we have to resort to delete pkg/$GOOS_$GOARCH, install -race,
299 // and then reinstall std so that we're not left with a slower,
300 // race-enabled cmd/go, etc.
301 if err := client.RemoveAll(path.Join(goDir, "pkg", b.OS+"_"+b.Arch)); err != nil {
302 return err
303 }
304 if err := runGo("tool", "dist", "install", "runtime"); err != nil {
305 return err
306 }
307 if err := runGo("install", "-race", "std"); err != nil {
308 return err
309 }
310 if err := runGo("install", "std"); err != nil {
311 return err
312 }
313 // Re-building go command leaves old versions of go.exe as go.exe~ on windows.
314 // See (*builder).copyFile in $GOROOT/src/cmd/go/build.go for details.
315 // Remove it manually.
316 if b.OS == "windows" {
317 if err := client.RemoveAll(goCmd + "~"); err != nil {
318 return err
319 }
320 }
321 }
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000322
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100323 log.Printf("%v: Building %v.", b, strings.Join(toolPaths, ", "))
324 if err := runGo(append([]string{"install"}, toolPaths...)...); err != nil {
325 return err
Andrew Gerrand319667f2015-02-04 16:04:07 +0000326 }
327
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100328 log.Printf("%v: Pushing and running releaselet.", b)
329 // TODO(adg): locate releaselet.go in GOPATH
330 const releaselet = "releaselet.go"
331 f, err := os.Open(releaselet)
332 if err != nil {
333 return err
334 }
335 err = client.Put(f, releaselet, 0666)
336 f.Close()
337 if err != nil {
338 return err
339 }
340 if err := runGo("run", releaselet); err != nil {
341 return err
342 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000343
344 log.Printf("%v: Cleaning goroot (post-build).", b)
345 // Need to delete everything except the final "go" directory,
346 // as we make the tarball relative to workdir.
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100347 cleanFiles := append(addPrefix(goDir, postBuildCleanFiles), goPath, releaselet)
Andrew Gerrand319667f2015-02-04 16:04:07 +0000348 if err := client.RemoveAll(cleanFiles...); err != nil {
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000349 return err
350 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000351
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100352 // TODO(adg): fetch msi or pkg files
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000353
354 // Download tarball
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100355 log.Printf("%v: Downloading tarball.", b)
Andrew Gerrand319667f2015-02-04 16:04:07 +0000356 tgz, err := client.GetTar(".")
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000357 if err != nil {
358 return err
359 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000360 // TODO(adg): deduce actual version
361 version := "VERSION"
362 filename := "go." + version + "." + b.String() + ".tar.gz"
Andrew Gerrand15af43e2015-02-11 11:47:03 +1100363 f, err = os.Create(filename)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000364 if err != nil {
365 return err
366 }
367 if _, err := io.Copy(f, tgz); err != nil {
368 f.Close()
369 return err
370 }
371 if err := f.Close(); err != nil {
372 return err
373 }
Andrew Gerrand319667f2015-02-04 16:04:07 +0000374 log.Printf("%v: Wrote %q.", b, filename)
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000375
376 return nil
377}
378
Andrew Gerrandf83f3e42015-02-02 12:05:01 +0000379func projTokenSource() oauth2.TokenSource {
380 ts, err := auth.ProjectTokenSource(*project, compute.ComputeScope)
381 if err != nil {
382 log.Fatalf("Failed to get OAuth2 token source for project %s: %v", *project, err)
383 }
384 return ts
385}
386
387func randHex(n int) string {
388 buf := make([]byte, n/2)
389 _, err := rand.Read(buf)
390 if err != nil {
391 panic("Failed to get randomness: " + err.Error())
392 }
393 return fmt.Sprintf("%x", buf)
394}
Andrew Gerrand319667f2015-02-04 16:04:07 +0000395
396func addPrefix(prefix string, in []string) []string {
397 var out []string
398 for _, s := range in {
399 out = append(out, path.Join(prefix, s))
400 }
401 return out
402}