all: add env support, freebsd-386, freebsd-amd64-race, re-enable plan9
Update golang/go#9491
Update golang/go#8639
Change-Id: I1f43c751777f10a6d5870ca9bbb8c2a3430189bf
Reviewed-on: https://go-review.googlesource.com/3170
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/buildlet/buildletclient.go b/buildlet/buildletclient.go
index 8098a73..0e4f98b 100644
--- a/buildlet/buildletclient.go
+++ b/buildlet/buildletclient.go
@@ -128,6 +128,10 @@
// Args are the arguments to pass to the cmd given to Client.Exec.
Args []string
+ // ExtraEnv are KEY=VALUE pairs to append to the buildlet
+ // process's environment.
+ ExtraEnv []string
+
// SystemLevel controls whether the command is run outside of
// the buildlet's environment.
SystemLevel bool
@@ -154,6 +158,7 @@
"cmd": {cmd},
"mode": {mode},
"cmdArg": opts.Args,
+ "env": opts.ExtraEnv,
}
req, err := http.NewRequest("POST", c.URL()+"/exec", strings.NewReader(form.Encode()))
if err != nil {
diff --git a/cmd/buildlet/Makefile b/cmd/buildlet/Makefile
index 93f57d9..46cb95b 100644
--- a/cmd/buildlet/Makefile
+++ b/cmd/buildlet/Makefile
@@ -7,7 +7,12 @@
buildlet.freebsd-amd64: buildlet.go
GOOS=freebsd GOARCH=amd64 go build -o $@
- cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
+
+# We use the same binary for freebsd-386 as freebsd-amd64:
+.PHONY: freebsd
+freebsd: buildlet.freebsd-amd64
+ cat buildlet.freebsd-amd64 | (cd ../upload && go run upload.go --public go-builder-data/buildlet.freebsd-amd64)
+ cat buildlet.freebsd-amd64 | (cd ../upload && go run upload.go --public go-builder-data/buildlet.freebsd-386)
buildlet.linux-amd64: buildlet.go
GOOS=linux GOARCH=amd64 go build -o $@
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 75ce410..92a1563 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -39,7 +39,7 @@
var (
haltEntireOS = flag.Bool("halt", true, "halt OS in /halt handler. If false, the buildlet process just ends.")
- scratchDir = flag.String("scratchdir", "", "Temporary directory to use. The contents of this directory may be deleted at any time. If empty, TempDir is used to create one.")
+ workDir = flag.String("workdir", "", "Temporary directory to use. The contents of this directory may be deleted at any time. If empty, TempDir is used to create one.")
listenAddr = flag.String("listen", defaultListenAddr(), "address to listen on. Warning: this service is inherently insecure and offers no protection of its own. Do not expose this port to the world.")
)
@@ -72,33 +72,21 @@
if onGCE {
fixMTU()
}
- if runtime.GOOS == "plan9" {
- // Plan 9 is too slow on GCE, so stop running run.rc after the basics.
- // See https://golang.org/cl/2522 and https://golang.org/issue/9491
- // TODO(bradfitz): once the buildlet has environment variable support,
- // the coordinator can send this in, and this variable can be part of
- // the build configuration struct instead of hard-coded here.
- // But no need for environment variables quite yet.
- os.Setenv("GOTESTONLY", "std")
- }
- if *scratchDir == "" {
+ if *workDir == "" {
dir, err := ioutil.TempDir("", "buildlet-scatch")
if err != nil {
- log.Fatalf("error creating scratchdir with ioutil.TempDir: %v", err)
+ log.Fatalf("error creating workdir with ioutil.TempDir: %v", err)
}
- *scratchDir = dir
+ *workDir = dir
}
- // TODO(bradfitz): if this becomes more of a general tool,
- // perhaps we want to remove this hard-coded here. Also,
- // if/once the exec handler ever gets generic environment
- // variable support, it would make sense to remove this too
- // and push it to the client. This hard-codes policy. But
- // that's okay for now.
- os.Setenv("GOROOT_BOOTSTRAP", filepath.Join(*scratchDir, "go1.4"))
- os.Setenv("WORKDIR", *scratchDir) // mostly for demos
+ // This is hard-coded because the client-supplied environment has
+ // no way to expand relative paths from the workDir.
+ // TODO(bradfitz): if we ever need more than this, make some mechanism.
+ os.Setenv("GOROOT_BOOTSTRAP", filepath.Join(*workDir, "go1.4"))
+ os.Setenv("WORKDIR", *workDir) // mostly for demos
- if _, err := os.Lstat(*scratchDir); err != nil {
- log.Fatalf("invalid --scratchdir %q: %v", *scratchDir, err)
+ if _, err := os.Lstat(*workDir); err != nil {
+ log.Fatalf("invalid --workdir %q: %v", *workDir, err)
}
http.HandleFunc("/", handleRoot)
http.HandleFunc("/debug/goroutines", handleGoroutines)
@@ -208,7 +196,7 @@
if err != nil {
return err
}
- if _, err := io.WriteString(f, "mtu 1400\n"); err != nil { // not 1460
+ if _, err := io.WriteString(f, "mtu 1460\n"); err != nil {
f.Close()
return err
}
@@ -230,46 +218,18 @@
}
}
-// mtuWriter is a hack for environments where we can't (or can't yet)
-// fix the machine's MTU.
-// Instead of telling the operating system the MTU, we just cut up our
-// writes into small pieces to make sure we don't get too near the
-// MTU, and we hope the kernel doesn't coalesce different flushed
-// writes back together into the same TCP IP packets.
-// TODO(bradfitz): this has never proven to work. See:
-// https://github.com/golang/go/issues/9491#issuecomment-70881865
-// But we do depend on its http.Flusher.Flush-per-Write behavior, so we
-// can't delete this entirely.
-type mtuWriter struct {
+// flushWriter is an io.Writer that Flushes after each Write if the
+// underlying Writer implements http.Flusher.
+type flushWriter struct {
rw http.ResponseWriter
}
-func (mw mtuWriter) Write(p []byte) (n int, err error) {
- const mtu = 1000 // way less than 1460; since HTTP response headers might be in there too
- for len(p) > 0 {
- chunk := p
- if len(chunk) > mtu {
- chunk = p[:mtu]
- }
- n0, err := mw.rw.Write(chunk)
- n += n0
- if n0 != len(chunk) && err == nil {
- err = io.ErrShortWrite
- }
- if err != nil {
- return n, err
- }
- p = p[n0:]
- mw.rw.(http.Flusher).Flush()
- if len(p) > 0 && runtime.GOOS == "plan9" {
- // Try to prevent the kernel from Nagel-ing the IP packets
- // together into one that's too large.
- // This didn't fix anything, though.
- // See https://github.com/golang/go/issues/9491#issuecomment-70881865
- //time.Sleep(250 * time.Millisecond)
- }
+func (fw flushWriter) Write(p []byte) (n int, err error) {
+ n, err = fw.rw.Write(p)
+ if f, ok := fw.rw.(http.Flusher); ok {
+ f.Flush()
}
- return n, nil
+ return
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
@@ -281,10 +241,9 @@
}
// unauthenticated /debug/goroutines handler
-func handleGoroutines(rw http.ResponseWriter, r *http.Request) {
- w := mtuWriter{rw}
+func handleGoroutines(w http.ResponseWriter, r *http.Request) {
log.Printf("Dumping goroutines.")
- rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
w.Write(buf)
@@ -319,19 +278,19 @@
return true
}
-func handleGetTGZ(rw http.ResponseWriter, r *http.Request) {
+func handleGetTGZ(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
- http.Error(rw, "requires GET method", http.StatusBadRequest)
+ http.Error(w, "requires GET method", http.StatusBadRequest)
return
}
dir := r.FormValue("dir")
if !validRelativeDir(dir) {
- http.Error(rw, "bogus dir", http.StatusBadRequest)
+ http.Error(w, "bogus dir", http.StatusBadRequest)
return
}
- zw := gzip.NewWriter(mtuWriter{rw})
+ zw := gzip.NewWriter(w)
tw := tar.NewWriter(zw)
- base := filepath.Join(*scratchDir, filepath.FromSlash(dir))
+ base := filepath.Join(*workDir, filepath.FromSlash(dir))
err := filepath.Walk(base, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
@@ -374,7 +333,7 @@
log.Printf("Walk error: %v", err)
// Decent way to signal failure to the caller, since it'll break
// the chunked response, rather than have a valid EOF.
- conn, _, _ := rw.(http.Hijacker).Hijack()
+ conn, _, _ := w.(http.Hijacker).Hijack()
conn.Close()
}
tw.Close()
@@ -409,7 +368,7 @@
}
urlParam, _ := url.ParseQuery(r.URL.RawQuery)
- baseDir := *scratchDir
+ baseDir := *workDir
if dir := urlParam.Get("dir"); dir != "" {
if !validRelativeDir(dir) {
http.Error(w, "bogus dir", http.StatusBadRequest)
@@ -527,7 +486,7 @@
http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest)
return
}
- absCmd = filepath.Join(*scratchDir, filepath.FromSlash(cmdPath))
+ absCmd = filepath.Join(*workDir, filepath.FromSlash(cmdPath))
}
if f, ok := w.(http.Flusher); ok {
@@ -536,13 +495,14 @@
cmd := exec.Command(absCmd, r.PostForm["cmdArg"]...)
if sysMode {
- cmd.Dir = *scratchDir
+ cmd.Dir = *workDir
} else {
cmd.Dir = filepath.Dir(absCmd)
}
- cmdOutput := mtuWriter{w}
+ cmdOutput := flushWriter{w}
cmd.Stdout = cmdOutput
cmd.Stderr = cmdOutput
+ cmd.Env = append(os.Environ(), r.PostForm["env"]...)
err := cmd.Start()
if err == nil {
go func() {
diff --git a/cmd/coordinator/coordinator.go b/cmd/coordinator/coordinator.go
index 2398142..9462b68 100644
--- a/cmd/coordinator/coordinator.go
+++ b/cmd/coordinator/coordinator.go
@@ -42,8 +42,6 @@
)
func init() {
- // Disabled until its networking/MTU problems are fixed:
- // https://github.com/golang/go/issues/9491#issuecomment-70881865
delete(dashboard.Builders, "plan9-386-gcepartial")
}
@@ -797,6 +795,7 @@
remoteErr, err := bc.Exec(path.Join("go", conf.AllScript()), buildlet.ExecOpts{
Output: st,
OnStartExec: func() { st.logEventTime("running_exec") },
+ ExtraEnv: conf.Env(),
})
if err != nil {
return err
diff --git a/cmd/gomote/run.go b/cmd/gomote/run.go
index aeaa0a9..f959c8a 100644
--- a/cmd/gomote/run.go
+++ b/cmd/gomote/run.go
@@ -22,6 +22,8 @@
}
var sys bool
fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
+ var env stringSlice
+ fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
fs.Parse(args)
if fs.NArg() < 2 {
@@ -37,9 +39,26 @@
SystemLevel: sys || strings.HasPrefix(cmd, "/"),
Output: os.Stdout,
Args: fs.Args()[2:],
+ ExtraEnv: []string(env),
})
if execErr != nil {
return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr)
}
return remoteErr
}
+
+// stringSlice implements flag.Value, specifically for storing environment
+// variable key=value pairs.
+type stringSlice []string
+
+func (*stringSlice) String() string { return "" } // default value
+
+func (ss *stringSlice) Set(v string) error {
+ if v != "" {
+ if !strings.Contains(v, "=") {
+ return fmt.Errorf("-e argument %q doesn't contains an '=' sign.", v)
+ }
+ *ss = append(*ss, v)
+ }
+ return nil
+}
diff --git a/dashboard/builders.go b/dashboard/builders.go
index 685aafc..f079a29 100644
--- a/dashboard/builders.go
+++ b/dashboard/builders.go
@@ -30,13 +30,17 @@
Go14URL string // URL to built Go 1.4 tar.gz
// Docker-specific settings: (used if VMImage == "")
- Image string // Docker image to use to build
- cmd string // optional -cmd flag (relative to go/src/)
- env []string // extra environment ("key=value") pairs
- dashURL string // url of the build dashboard
- tool string // the tool this configuration is for
+ Image string // Docker image to use to build
+ cmd string // optional -cmd flag (relative to go/src/)
+ dashURL string // url of the build dashboard
+ tool string // the tool this configuration is for
+
+ // Use by both VMs and Docker:
+ env []string // extra environment ("key=value") pairs
}
+func (c *BuildConfig) Env() []string { return append([]string(nil), c.env...) }
+
func (c *BuildConfig) GOOS() string { return c.Name[:strings.Index(c.Name, "-")] }
func (c *BuildConfig) GOARCH() string {
@@ -52,14 +56,18 @@
// do the build and run its standard set of tests.
// Example values are "src/all.bash", "src/all.bat", "src/all.rc".
func (c *BuildConfig) AllScript() string {
+ if strings.HasSuffix(c.Name, "-race") {
+ if strings.HasPrefix(c.Name, "windows-") {
+ return "src/race.bat"
+ }
+ return "src/race.bash"
+ }
if strings.HasPrefix(c.Name, "windows-") {
return "src/all.bat"
}
if strings.HasPrefix(c.Name, "plan9-") {
return "src/all.rc"
}
- // TODO(bradfitz): race.bash, etc, once the race builder runs
- // via the buildlet.
return "src/all.bash"
}
@@ -149,19 +157,41 @@
// VMs:
addBuilder(BuildConfig{
+ Name: "freebsd-amd64-gce101",
+ VMImage: "freebsd-amd64-gce101",
+ machineType: "n1-highcpu-2",
+ Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
+ env: []string{"CC=clang"},
+ })
+ addBuilder(BuildConfig{
+ Name: "freebsd-amd64-race",
+ VMImage: "freebsd-amd64-gce101",
+ machineType: "n1-highcpu-4",
+ Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
+ env: []string{"CC=clang"},
+ })
+ addBuilder(BuildConfig{
+ Name: "freebsd-386-gce101",
+ VMImage: "freebsd-amd64-gce101",
+ machineType: "n1-highcpu-2",
+ Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
+ env: []string{"GOARCH=386", "CC=clang"},
+ })
+ addBuilder(BuildConfig{
Name: "openbsd-amd64-gce56",
VMImage: "openbsd-amd64-56",
machineType: "n1-highcpu-2",
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-openbsd-amd64.tar.gz",
})
addBuilder(BuildConfig{
+ Name: "plan9-386-gcepartial",
+ VMImage: "plan9-386",
+ Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-plan9-386.tar.gz",
// It's named "partial" because the buildlet sets
// GOTESTONLY=std to stop after the "go test std"
// tests because it's so slow otherwise.
- // TODO(braditz): move that env variable to the
- // coordinator and into this config.
- Name: "plan9-386-gcepartial",
- VMImage: "plan9-386",
+ env: []string{"GOTESTONLY=std"},
+
// We *were* using n1-standard-1 because Plan 9 can only
// reliably use a single CPU. Using 2 or 4 and we see
// test failures. See: