blob: 12a279ddcc83f76074e291c683772e9ead80f426 [file] [log] [blame]
// Copyright 2015 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.
// racebuild builds the race runtime (syso files) on all supported OSes using gomote.
// Usage:
// $ racebuild -rev <llvm_git_revision> -goroot <path_to_go_repo>
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"strings"
"sync"
"golang.org/x/sync/errgroup"
)
var (
flagGoroot = flag.String("goroot", "", "path to Go repository to update (required)")
flagRev = flag.String("rev", "", "llvm-project git revision from https://github.com/llvm/llvm-project (required)")
flagGoRev = flag.String("gorev", "HEAD", "Go repository revision to use; HEAD is relative to --goroot")
flagPlatforms = flag.String("platforms", "all", `comma-separated platforms (such as "linux/amd64") to rebuild, or "all"`)
)
// goRev is the resolved commit ID of flagGoRev.
var goRev string
// TODO: use buildlet package instead of calling out to gomote.
var platforms = []*Platform{
&Platform{
OS: "freebsd",
Arch: "amd64",
Type: "freebsd-amd64-race",
Script: `#!/usr/bin/env bash
set -e
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && CC=clang ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_freebsd_amd64.syso go/src/runtime/race
(cd go/src && ./race.bash)
`,
},
&Platform{
OS: "darwin",
Arch: "amd64",
Type: "darwin-amd64-10_12",
Script: `#!/usr/bin/env bash
set -e
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && CC=clang ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_darwin_amd64.syso go/src/runtime/race
(cd go/src && ./race.bash)
`,
},
&Platform{
OS: "linux",
Arch: "amd64",
Type: "linux-amd64-race",
Script: `#!/usr/bin/env bash
set -e
apt-get update
apt-get install -y git g++
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_linux_amd64.syso go/src/runtime/race
(cd go/src && ./race.bash)
`,
},
&Platform{
OS: "linux",
Arch: "ppc64le",
Type: "linux-ppc64le-buildlet",
Script: `#!/usr/bin/env bash
set -e
apt-get update
apt-get install -y git g++
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_linux_ppc64le.syso go/src/runtime/race
# TODO(#23731): Uncomment to test the syso file before accepting it.
# (cd go/src && ./race.bash)
`,
},
&Platform{
OS: "linux",
Arch: "arm64",
Type: "linux-arm64-packet",
Script: `#!/usr/bin/env bash
set -e
apt-get update
apt-get install -y git g++
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_linux_arm64.syso go/src/runtime/race
(cd go/src && ./race.bash)
`,
},
&Platform{
OS: "netbsd",
Arch: "amd64",
Type: "netbsd-amd64-8_0",
Script: `#!/usr/bin/env bash
set -e
git clone https://go.googlesource.com/go
pushd go
git checkout $GOREV
popd
git clone https://github.com/llvm/llvm-project
(cd llvm-project && git checkout $REV)
(cd llvm-project/compiler-rt/lib/tsan/go && CC=clang ./buildgo.sh)
cp llvm-project/compiler-rt/lib/tsan/go/race_netbsd_amd64.syso go/src/runtime/race
# TODO(#24322): Uncomment to test the syso file before accepting it.
# (cd go/src && ./race.bash)
`,
},
&Platform{
OS: "windows",
Arch: "amd64",
Type: "windows-amd64-race",
Script: `
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
choco install git -y
if %errorlevel% neq 0 exit /b %errorlevel%
choco install mingw --version 5.3.0 -y
if %errorlevel% neq 0 exit /b %errorlevel%
call refreshenv
git clone https://go.googlesource.com/go
if %errorlevel% neq 0 exit /b %errorlevel%
cd go
git checkout %GOREV%
if %errorlevel% neq 0 exit /b %errorlevel%
cd ..
git clone https://github.com/llvm/llvm-project
if %errorlevel% neq 0 exit /b %errorlevel%
cd llvm-project
git checkout %REV%
if %errorlevel% neq 0 exit /b %errorlevel%
cd ..
cd llvm-project/compiler-rt/lib/tsan/go
call build.bat
if %errorlevel% neq 0 exit /b %errorlevel%
cd ../../../../..
xcopy llvm-project\compiler-rt\lib\tsan\go\race_windows_amd64.syso go\src\runtime\race\race_windows_amd64.syso /Y
if %errorlevel% neq 0 exit /b %errorlevel%
cd go/src
call race.bat
if %errorlevel% neq 0 exit /b %errorlevel%
`,
},
}
func init() {
// Ensure that there are no duplicate platform entries.
seen := make(map[string]bool)
for _, p := range platforms {
if seen[p.Name()] {
log.Fatalf("Duplicate platforms entry for %s.", p.Name())
}
seen[p.Name()] = true
}
}
var platformEnabled = make(map[string]bool)
func parsePlatformsFlag() {
if *flagPlatforms == "all" {
for _, p := range platforms {
platformEnabled[p.Name()] = true
}
return
}
var invalid []string
for _, name := range strings.Split(*flagPlatforms, ",") {
for _, p := range platforms {
if name == p.Name() {
platformEnabled[name] = true
break
}
}
if !platformEnabled[name] {
invalid = append(invalid, name)
}
}
if len(invalid) > 0 {
var msg bytes.Buffer
fmt.Fprintf(&msg, "Unrecognized platforms: %q. Supported platforms are:\n", invalid)
for _, p := range platforms {
fmt.Fprintf(&msg, "\t%s/%s\n", p.OS, p.Arch)
}
log.Fatal(&msg)
}
}
func main() {
flag.Parse()
if *flagRev == "" || *flagGoroot == "" || *flagGoRev == "" {
flag.PrintDefaults()
os.Exit(1)
}
parsePlatformsFlag()
cmd := exec.Command("git", "rev-parse", *flagGoRev)
cmd.Dir = *flagGoroot
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
log.Fatalf("%s failed: %v", strings.Join(cmd.Args, " "), err)
}
goRev = string(bytes.TrimSpace(out))
log.Printf("using Go revision: %s", goRev)
// Start build on all platforms in parallel.
// On interrupt, destroy any in-flight builders before exiting.
ctx, cancel := context.WithCancel(context.Background())
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt)
go func() {
<-shutdown
cancel()
}()
g, ctx := errgroup.WithContext(ctx)
for _, p := range platforms {
if !platformEnabled[p.Name()] {
continue
}
p := p
g.Go(func() error {
if err := p.Build(ctx); err != nil {
return fmt.Errorf("%v failed: %v", p.Name(), err)
}
return p.UpdateReadme()
})
}
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
type Platform struct {
OS string
Arch string
Type string // gomote instance type
Inst string // actual gomote instance name
Script string
}
func (p *Platform) Name() string {
return fmt.Sprintf("%v/%v", p.OS, p.Arch)
}
// Basename returns the name of the output file relative to src/runtime/race.
func (p *Platform) Basename() string {
return fmt.Sprintf("race_%v_%s.syso", p.OS, p.Arch)
}
func (p *Platform) Build(ctx context.Context) error {
// Create gomote instance (or reuse an existing instance for debugging).
var lastErr error
for p.Inst == "" {
inst, err := p.Gomote(ctx, "create", "-status=false", p.Type)
if err != nil {
select {
case <-ctx.Done():
if lastErr != nil {
return lastErr
}
return err
default:
// Creation sometimes fails with transient errors like:
// "buildlet didn't come up at http://10.240.0.13 in 3m0s".
log.Printf("%v: instance creation failed, retrying", p.Name())
lastErr = err
continue
}
}
p.Inst = strings.Trim(string(inst), " \t\n")
defer p.Gomote(context.Background(), "destroy", p.Inst)
}
log.Printf("%s: using instance %v", p.Name(), p.Inst)
// put14
if _, err := p.Gomote(ctx, "put14", p.Inst); err != nil {
return err
}
// Execute the script.
script, err := ioutil.TempFile("", "racebuild")
if err != nil {
return fmt.Errorf("failed to create temp file: %v", err)
}
defer func() {
script.Close()
os.Remove(script.Name())
}()
if _, err := script.Write([]byte(p.Script)); err != nil {
return fmt.Errorf("failed to write temp file: %v", err)
}
script.Close()
targetName := "script.bash"
if p.OS == "windows" {
targetName = "script.bat"
}
if _, err := p.Gomote(ctx, "put", "-mode=0700", p.Inst, script.Name(), targetName); err != nil {
return err
}
if _, err := p.Gomote(ctx, "run", "-e=REV="+*flagRev, "-e=GOREV="+goRev, p.Inst, targetName); err != nil {
return err
}
// The script is supposed to leave updated runtime at that path. Copy it out.
syso := p.Basename()
targz, err := p.Gomote(ctx, "gettar", "-dir=go/src/runtime/race/"+syso, p.Inst)
if err != nil {
return err
}
// Untar the runtime and write it to goroot.
if err := p.WriteSyso(filepath.Join(*flagGoroot, "src", "runtime", "race", syso), targz); err != nil {
return fmt.Errorf("%v", err)
}
log.Printf("%v: build completed", p.Name())
return nil
}
func (p *Platform) WriteSyso(sysof string, targz []byte) error {
// Ungzip.
gzipr, err := gzip.NewReader(bytes.NewReader(targz))
if err != nil {
return fmt.Errorf("failed to read gzip archive: %v", err)
}
defer gzipr.Close()
tr := tar.NewReader(gzipr)
if _, err := tr.Next(); err != nil {
return fmt.Errorf("failed to read tar archive: %v", err)
}
// Copy the file.
syso, err := os.Create(sysof)
if err != nil {
return fmt.Errorf("failed to open race runtime: %v", err)
}
defer syso.Close()
if _, err := io.Copy(syso, tr); err != nil {
return fmt.Errorf("failed to write race runtime: %v", err)
}
return nil
}
var readmeMu sync.Mutex
func (p *Platform) UpdateReadme() error {
readmeMu.Lock()
defer readmeMu.Unlock()
readmeFile := filepath.Join(*flagGoroot, "src", "runtime", "race", "README")
readme, err := ioutil.ReadFile(readmeFile)
if err != nil {
log.Fatalf("bad -goroot? %v", err)
}
syso := p.Basename()
const (
readmeTmpl = "%s built with LLVM %s and Go %s."
commitRE = "[0-9a-f]+"
)
// TODO(bcmills): Extract the C++ toolchain version from the .syso file and
// record it in the README.
updatedLine := fmt.Sprintf(readmeTmpl, syso, *flagRev, goRev)
lineRE, err := regexp.Compile("(?m)^" + fmt.Sprintf(readmeTmpl, regexp.QuoteMeta(syso), commitRE, commitRE) + "$")
if err != nil {
return err
}
if lineRE.Match(readme) {
readme = lineRE.ReplaceAll(readme, []byte(updatedLine))
} else {
readme = append(append(readme, []byte(updatedLine)...), '\n')
}
return ioutil.WriteFile(readmeFile, readme, 0640)
}
func (p *Platform) Gomote(ctx context.Context, args ...string) ([]byte, error) {
log.Printf("%v: gomote %v", p.Name(), args)
cmd := exec.CommandContext(ctx, "gomote", args...)
outBuf := new(bytes.Buffer)
// Combine stderr and stdout for everything except gettar: gettar's output is
// huge, so we only want to log stderr for it.
errBuf := outBuf
if args[0] == "gettar" {
errBuf = new(bytes.Buffer)
}
cmd.Stdout = outBuf
cmd.Stderr = errBuf
run := cmd.Run
if len(platformEnabled) == 1 {
// If building only one platform, stream gomote output to os.Stderr.
r, w := io.Pipe()
errTee := io.TeeReader(r, cmd.Stderr)
if cmd.Stdout == cmd.Stderr {
cmd.Stdout = w
}
cmd.Stderr = w
run = func() (err error) {
go func() {
err = cmd.Run()
w.Close()
}()
io.Copy(os.Stderr, errTee)
return
}
}
if err := run(); err != nil {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
log.Printf("%v: gomote %v failed:\n%s", p.Name(), args, errBuf)
return nil, err
}
if errBuf.Len() == 0 {
log.Printf("%v: gomote %v succeeded: <no output>", p.Name(), args)
} else {
log.Printf("%v: gomote %v succeeded:\n%s", p.Name(), args, errBuf)
}
return outBuf.Bytes(), nil
}