Skip to content

Commit 32d0520

Browse files
committed
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 <[email protected]>
1 parent 7b0767c commit 32d0520

File tree

6 files changed

+105
-87
lines changed

6 files changed

+105
-87
lines changed

buildlet/buildletclient.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ type ExecOpts struct {
128128
// Args are the arguments to pass to the cmd given to Client.Exec.
129129
Args []string
130130

131+
// ExtraEnv are KEY=VALUE pairs to append to the buildlet
132+
// process's environment.
133+
ExtraEnv []string
134+
131135
// SystemLevel controls whether the command is run outside of
132136
// the buildlet's environment.
133137
SystemLevel bool
@@ -154,6 +158,7 @@ func (c *Client) Exec(cmd string, opts ExecOpts) (remoteErr, execErr error) {
154158
"cmd": {cmd},
155159
"mode": {mode},
156160
"cmdArg": opts.Args,
161+
"env": opts.ExtraEnv,
157162
}
158163
req, err := http.NewRequest("POST", c.URL()+"/exec", strings.NewReader(form.Encode()))
159164
if err != nil {

cmd/buildlet/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ buildlet.darwin-amd64: buildlet.go
77

88
buildlet.freebsd-amd64: buildlet.go
99
GOOS=freebsd GOARCH=amd64 go build -o $@
10-
cat $@ | (cd ../upload && go run upload.go --public go-builder-data/$@)
10+
11+
# We use the same binary for freebsd-386 as freebsd-amd64:
12+
.PHONY: freebsd
13+
freebsd: buildlet.freebsd-amd64
14+
cat buildlet.freebsd-amd64 | (cd ../upload && go run upload.go --public go-builder-data/buildlet.freebsd-amd64)
15+
cat buildlet.freebsd-amd64 | (cd ../upload && go run upload.go --public go-builder-data/buildlet.freebsd-386)
1116

1217
buildlet.linux-amd64: buildlet.go
1318
GOOS=linux GOARCH=amd64 go build -o $@

cmd/buildlet/buildlet.go

Lines changed: 33 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import (
3939

4040
var (
4141
haltEntireOS = flag.Bool("halt", true, "halt OS in /halt handler. If false, the buildlet process just ends.")
42-
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.")
42+
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.")
4343
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.")
4444
)
4545

@@ -72,33 +72,21 @@ func main() {
7272
if onGCE {
7373
fixMTU()
7474
}
75-
if runtime.GOOS == "plan9" {
76-
// Plan 9 is too slow on GCE, so stop running run.rc after the basics.
77-
// See https://golang.org/cl/2522 and https://golang.org/issue/9491
78-
// TODO(bradfitz): once the buildlet has environment variable support,
79-
// the coordinator can send this in, and this variable can be part of
80-
// the build configuration struct instead of hard-coded here.
81-
// But no need for environment variables quite yet.
82-
os.Setenv("GOTESTONLY", "std")
83-
}
84-
if *scratchDir == "" {
75+
if *workDir == "" {
8576
dir, err := ioutil.TempDir("", "buildlet-scatch")
8677
if err != nil {
87-
log.Fatalf("error creating scratchdir with ioutil.TempDir: %v", err)
78+
log.Fatalf("error creating workdir with ioutil.TempDir: %v", err)
8879
}
89-
*scratchDir = dir
80+
*workDir = dir
9081
}
91-
// TODO(bradfitz): if this becomes more of a general tool,
92-
// perhaps we want to remove this hard-coded here. Also,
93-
// if/once the exec handler ever gets generic environment
94-
// variable support, it would make sense to remove this too
95-
// and push it to the client. This hard-codes policy. But
96-
// that's okay for now.
97-
os.Setenv("GOROOT_BOOTSTRAP", filepath.Join(*scratchDir, "go1.4"))
98-
os.Setenv("WORKDIR", *scratchDir) // mostly for demos
82+
// This is hard-coded because the client-supplied environment has
83+
// no way to expand relative paths from the workDir.
84+
// TODO(bradfitz): if we ever need more than this, make some mechanism.
85+
os.Setenv("GOROOT_BOOTSTRAP", filepath.Join(*workDir, "go1.4"))
86+
os.Setenv("WORKDIR", *workDir) // mostly for demos
9987

100-
if _, err := os.Lstat(*scratchDir); err != nil {
101-
log.Fatalf("invalid --scratchdir %q: %v", *scratchDir, err)
88+
if _, err := os.Lstat(*workDir); err != nil {
89+
log.Fatalf("invalid --workdir %q: %v", *workDir, err)
10290
}
10391
http.HandleFunc("/", handleRoot)
10492
http.HandleFunc("/debug/goroutines", handleGoroutines)
@@ -208,7 +196,7 @@ func fixMTU_plan9() error {
208196
if err != nil {
209197
return err
210198
}
211-
if _, err := io.WriteString(f, "mtu 1400\n"); err != nil { // not 1460
199+
if _, err := io.WriteString(f, "mtu 1460\n"); err != nil {
212200
f.Close()
213201
return err
214202
}
@@ -230,46 +218,18 @@ func fixMTU() {
230218
}
231219
}
232220

233-
// mtuWriter is a hack for environments where we can't (or can't yet)
234-
// fix the machine's MTU.
235-
// Instead of telling the operating system the MTU, we just cut up our
236-
// writes into small pieces to make sure we don't get too near the
237-
// MTU, and we hope the kernel doesn't coalesce different flushed
238-
// writes back together into the same TCP IP packets.
239-
// TODO(bradfitz): this has never proven to work. See:
240-
// https://github.com/golang/go/issues/9491#issuecomment-70881865
241-
// But we do depend on its http.Flusher.Flush-per-Write behavior, so we
242-
// can't delete this entirely.
243-
type mtuWriter struct {
221+
// flushWriter is an io.Writer that Flushes after each Write if the
222+
// underlying Writer implements http.Flusher.
223+
type flushWriter struct {
244224
rw http.ResponseWriter
245225
}
246226

247-
func (mw mtuWriter) Write(p []byte) (n int, err error) {
248-
const mtu = 1000 // way less than 1460; since HTTP response headers might be in there too
249-
for len(p) > 0 {
250-
chunk := p
251-
if len(chunk) > mtu {
252-
chunk = p[:mtu]
253-
}
254-
n0, err := mw.rw.Write(chunk)
255-
n += n0
256-
if n0 != len(chunk) && err == nil {
257-
err = io.ErrShortWrite
258-
}
259-
if err != nil {
260-
return n, err
261-
}
262-
p = p[n0:]
263-
mw.rw.(http.Flusher).Flush()
264-
if len(p) > 0 && runtime.GOOS == "plan9" {
265-
// Try to prevent the kernel from Nagel-ing the IP packets
266-
// together into one that's too large.
267-
// This didn't fix anything, though.
268-
// See https://github.com/golang/go/issues/9491#issuecomment-70881865
269-
//time.Sleep(250 * time.Millisecond)
270-
}
227+
func (fw flushWriter) Write(p []byte) (n int, err error) {
228+
n, err = fw.rw.Write(p)
229+
if f, ok := fw.rw.(http.Flusher); ok {
230+
f.Flush()
271231
}
272-
return n, nil
232+
return
273233
}
274234

275235
func handleRoot(w http.ResponseWriter, r *http.Request) {
@@ -281,10 +241,9 @@ func handleRoot(w http.ResponseWriter, r *http.Request) {
281241
}
282242

283243
// unauthenticated /debug/goroutines handler
284-
func handleGoroutines(rw http.ResponseWriter, r *http.Request) {
285-
w := mtuWriter{rw}
244+
func handleGoroutines(w http.ResponseWriter, r *http.Request) {
286245
log.Printf("Dumping goroutines.")
287-
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
246+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
288247
buf := make([]byte, 2<<20)
289248
buf = buf[:runtime.Stack(buf, true)]
290249
w.Write(buf)
@@ -319,19 +278,19 @@ func validRelativeDir(dir string) bool {
319278
return true
320279
}
321280

322-
func handleGetTGZ(rw http.ResponseWriter, r *http.Request) {
281+
func handleGetTGZ(w http.ResponseWriter, r *http.Request) {
323282
if r.Method != "GET" {
324-
http.Error(rw, "requires GET method", http.StatusBadRequest)
283+
http.Error(w, "requires GET method", http.StatusBadRequest)
325284
return
326285
}
327286
dir := r.FormValue("dir")
328287
if !validRelativeDir(dir) {
329-
http.Error(rw, "bogus dir", http.StatusBadRequest)
288+
http.Error(w, "bogus dir", http.StatusBadRequest)
330289
return
331290
}
332-
zw := gzip.NewWriter(mtuWriter{rw})
291+
zw := gzip.NewWriter(w)
333292
tw := tar.NewWriter(zw)
334-
base := filepath.Join(*scratchDir, filepath.FromSlash(dir))
293+
base := filepath.Join(*workDir, filepath.FromSlash(dir))
335294
err := filepath.Walk(base, func(path string, fi os.FileInfo, err error) error {
336295
if err != nil {
337296
return err
@@ -374,7 +333,7 @@ func handleGetTGZ(rw http.ResponseWriter, r *http.Request) {
374333
log.Printf("Walk error: %v", err)
375334
// Decent way to signal failure to the caller, since it'll break
376335
// the chunked response, rather than have a valid EOF.
377-
conn, _, _ := rw.(http.Hijacker).Hijack()
336+
conn, _, _ := w.(http.Hijacker).Hijack()
378337
conn.Close()
379338
}
380339
tw.Close()
@@ -409,7 +368,7 @@ func handleWriteTGZ(w http.ResponseWriter, r *http.Request) {
409368
}
410369

411370
urlParam, _ := url.ParseQuery(r.URL.RawQuery)
412-
baseDir := *scratchDir
371+
baseDir := *workDir
413372
if dir := urlParam.Get("dir"); dir != "" {
414373
if !validRelativeDir(dir) {
415374
http.Error(w, "bogus dir", http.StatusBadRequest)
@@ -527,7 +486,7 @@ func handleExec(w http.ResponseWriter, r *http.Request) {
527486
http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest)
528487
return
529488
}
530-
absCmd = filepath.Join(*scratchDir, filepath.FromSlash(cmdPath))
489+
absCmd = filepath.Join(*workDir, filepath.FromSlash(cmdPath))
531490
}
532491

533492
if f, ok := w.(http.Flusher); ok {
@@ -536,13 +495,14 @@ func handleExec(w http.ResponseWriter, r *http.Request) {
536495

537496
cmd := exec.Command(absCmd, r.PostForm["cmdArg"]...)
538497
if sysMode {
539-
cmd.Dir = *scratchDir
498+
cmd.Dir = *workDir
540499
} else {
541500
cmd.Dir = filepath.Dir(absCmd)
542501
}
543-
cmdOutput := mtuWriter{w}
502+
cmdOutput := flushWriter{w}
544503
cmd.Stdout = cmdOutput
545504
cmd.Stderr = cmdOutput
505+
cmd.Env = append(os.Environ(), r.PostForm["env"]...)
546506
err := cmd.Start()
547507
if err == nil {
548508
go func() {

cmd/coordinator/coordinator.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ import (
4242
)
4343

4444
func init() {
45-
// Disabled until its networking/MTU problems are fixed:
46-
// https://github.com/golang/go/issues/9491#issuecomment-70881865
4745
delete(dashboard.Builders, "plan9-386-gcepartial")
4846
}
4947

@@ -797,6 +795,7 @@ func buildInVM(conf dashboard.BuildConfig, st *buildStatus) (retErr error) {
797795
remoteErr, err := bc.Exec(path.Join("go", conf.AllScript()), buildlet.ExecOpts{
798796
Output: st,
799797
OnStartExec: func() { st.logEventTime("running_exec") },
798+
ExtraEnv: conf.Env(),
800799
})
801800
if err != nil {
802801
return err

cmd/gomote/run.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func run(args []string) error {
2222
}
2323
var sys bool
2424
fs.BoolVar(&sys, "system", false, "run inside the system, and not inside the workdir; this is implicit if cmd starts with '/'")
25+
var env stringSlice
26+
fs.Var(&env, "e", "Environment variable KEY=value. The -e flag may be repeated multiple times to add multiple things to the environment.")
2527

2628
fs.Parse(args)
2729
if fs.NArg() < 2 {
@@ -37,9 +39,26 @@ func run(args []string) error {
3739
SystemLevel: sys || strings.HasPrefix(cmd, "/"),
3840
Output: os.Stdout,
3941
Args: fs.Args()[2:],
42+
ExtraEnv: []string(env),
4043
})
4144
if execErr != nil {
4245
return fmt.Errorf("Error trying to execute %s: %v", cmd, execErr)
4346
}
4447
return remoteErr
4548
}
49+
50+
// stringSlice implements flag.Value, specifically for storing environment
51+
// variable key=value pairs.
52+
type stringSlice []string
53+
54+
func (*stringSlice) String() string { return "" } // default value
55+
56+
func (ss *stringSlice) Set(v string) error {
57+
if v != "" {
58+
if !strings.Contains(v, "=") {
59+
return fmt.Errorf("-e argument %q doesn't contains an '=' sign.", v)
60+
}
61+
*ss = append(*ss, v)
62+
}
63+
return nil
64+
}

dashboard/builders.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ type BuildConfig struct {
3030
Go14URL string // URL to built Go 1.4 tar.gz
3131

3232
// Docker-specific settings: (used if VMImage == "")
33-
Image string // Docker image to use to build
34-
cmd string // optional -cmd flag (relative to go/src/)
35-
env []string // extra environment ("key=value") pairs
36-
dashURL string // url of the build dashboard
37-
tool string // the tool this configuration is for
33+
Image string // Docker image to use to build
34+
cmd string // optional -cmd flag (relative to go/src/)
35+
dashURL string // url of the build dashboard
36+
tool string // the tool this configuration is for
37+
38+
// Use by both VMs and Docker:
39+
env []string // extra environment ("key=value") pairs
3840
}
3941

42+
func (c *BuildConfig) Env() []string { return append([]string(nil), c.env...) }
43+
4044
func (c *BuildConfig) GOOS() string { return c.Name[:strings.Index(c.Name, "-")] }
4145

4246
func (c *BuildConfig) GOARCH() string {
@@ -52,14 +56,18 @@ func (c *BuildConfig) GOARCH() string {
5256
// do the build and run its standard set of tests.
5357
// Example values are "src/all.bash", "src/all.bat", "src/all.rc".
5458
func (c *BuildConfig) AllScript() string {
59+
if strings.HasSuffix(c.Name, "-race") {
60+
if strings.HasPrefix(c.Name, "windows-") {
61+
return "src/race.bat"
62+
}
63+
return "src/race.bash"
64+
}
5565
if strings.HasPrefix(c.Name, "windows-") {
5666
return "src/all.bat"
5767
}
5868
if strings.HasPrefix(c.Name, "plan9-") {
5969
return "src/all.rc"
6070
}
61-
// TODO(bradfitz): race.bash, etc, once the race builder runs
62-
// via the buildlet.
6371
return "src/all.bash"
6472
}
6573

@@ -148,20 +156,42 @@ func init() {
148156
addBuilder(BuildConfig{Name: "linux-amd64-clang", Image: "gobuilders/linux-x86-clang"})
149157

150158
// VMs:
159+
addBuilder(BuildConfig{
160+
Name: "freebsd-amd64-gce101",
161+
VMImage: "freebsd-amd64-gce101",
162+
machineType: "n1-highcpu-2",
163+
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
164+
env: []string{"CC=clang"},
165+
})
166+
addBuilder(BuildConfig{
167+
Name: "freebsd-amd64-race",
168+
VMImage: "freebsd-amd64-gce101",
169+
machineType: "n1-highcpu-4",
170+
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
171+
env: []string{"CC=clang"},
172+
})
173+
addBuilder(BuildConfig{
174+
Name: "freebsd-386-gce101",
175+
VMImage: "freebsd-amd64-gce101",
176+
machineType: "n1-highcpu-2",
177+
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-freebsd-amd64.tar.gz",
178+
env: []string{"GOARCH=386", "CC=clang"},
179+
})
151180
addBuilder(BuildConfig{
152181
Name: "openbsd-amd64-gce56",
153182
VMImage: "openbsd-amd64-56",
154183
machineType: "n1-highcpu-2",
155184
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-openbsd-amd64.tar.gz",
156185
})
157186
addBuilder(BuildConfig{
187+
Name: "plan9-386-gcepartial",
188+
VMImage: "plan9-386",
189+
Go14URL: "https://storage.googleapis.com/go-builder-data/go1.4-plan9-386.tar.gz",
158190
// It's named "partial" because the buildlet sets
159191
// GOTESTONLY=std to stop after the "go test std"
160192
// tests because it's so slow otherwise.
161-
// TODO(braditz): move that env variable to the
162-
// coordinator and into this config.
163-
Name: "plan9-386-gcepartial",
164-
VMImage: "plan9-386",
193+
env: []string{"GOTESTONLY=std"},
194+
165195
// We *were* using n1-standard-1 because Plan 9 can only
166196
// reliably use a single CPU. Using 2 or 4 and we see
167197
// test failures. See:

0 commit comments

Comments
 (0)