go_android_exec.go 9.77 KB
Newer Older
1
// Copyright 2014 The Go Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

5 6
// +build ignore

7 8 9 10 11 12
// This program can be used as go_android_GOARCH_exec by the Go tool.
// It executes binaries on an android device using adb.
package main

import (
	"bytes"
13
	"errors"
14
	"fmt"
15
	"go/build"
16
	"io"
17
	"io/ioutil"
18 19 20
	"log"
	"os"
	"os/exec"
21
	"os/signal"
22 23 24 25
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
26
	"syscall"
27 28
)

29
func run(args ...string) (string, error) {
30 31 32
	if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
		args = append(strings.Split(flags, " "), args...)
	}
33 34 35
	buf := new(bytes.Buffer)
	cmd := exec.Command("adb", args...)
	cmd.Stdout = io.MultiWriter(os.Stdout, buf)
36 37 38 39 40 41 42 43 44 45
	// If the adb subprocess somehow hangs, go test will kill this wrapper
	// and wait for our os.Stderr (and os.Stdout) to close as a result.
	// However, if the os.Stderr (or os.Stdout) file descriptors are
	// passed on, the hanging adb subprocess will hold them open and
	// go test will hang forever.
	//
	// Avoid that by wrapping stderr, breaking the short circuit and
	// forcing cmd.Run to use another pipe and goroutine to pass
	// along stderr from adb.
	cmd.Stderr = struct{ io.Writer }{os.Stderr}
46 47 48
	log.Printf("adb %s", strings.Join(args, " "))
	err := cmd.Run()
	if err != nil {
49
		return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
50
	}
51
	return buf.String(), nil
52 53
}

54
const (
55
	deviceRoot   = "/data/local/tmp/go_android_exec"
56
	deviceGoroot = deviceRoot + "/goroot"
57 58
)

59 60 61
func main() {
	log.SetFlags(0)
	log.SetPrefix("go_android_exec: ")
62 63 64 65 66 67
	exitCode, err := runMain()
	if err != nil {
		log.Fatal(err)
	}
	os.Exit(exitCode)
}
68

69
func runMain() (int, error) {
70 71 72 73 74 75
	// Concurrent use of adb is flaky, so serialize adb commands.
	// See https://github.com/golang/go/issues/23795 or
	// https://issuetracker.google.com/issues/73230216.
	lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
	lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
76
		return 0, err
77 78 79
	}
	defer lock.Close()
	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
80
		return 0, err
81 82
	}

Elias Naur's avatar
Elias Naur committed
83 84
	// In case we're booting a device or emulator alongside all.bash, wait for
	// it to be ready. adb wait-for-device is not enough, we have to
85
	// wait for sys.boot_completed.
86 87 88
	if _, err := run("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
		return 0, err
	}
89

90
	// Done once per make.bash.
91 92 93
	if err := adbCopyGoroot(); err != nil {
		return 0, err
	}
94

95
	// Prepare a temporary directory that will be cleaned up at the end.
96 97 98 99 100 101
	// Binary names can conflict.
	// E.g. template.test from the {html,text}/template packages.
	binName := filepath.Base(os.Args[1])
	deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
	deviceGopath := deviceGotmp + "/gopath"
	defer run("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
102 103

	// Determine the package by examining the current working
104
	// directory, which will look something like
105 106 107
	// "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
	// We extract everything after the $GOROOT or $GOPATH to run on the
	// same relative directory on the target device.
108 109 110 111
	subdir, inGoRoot, err := subdir()
	if err != nil {
		return 0, err
	}
112 113 114
	deviceCwd := filepath.Join(deviceGopath, subdir)
	if inGoRoot {
		deviceCwd = filepath.Join(deviceGoroot, subdir)
115
	} else {
116 117 118
		if _, err := run("exec-out", "mkdir", "-p", deviceCwd); err != nil {
			return 0, err
		}
119
		if err := adbCopyTree(deviceCwd, subdir); err != nil {
120 121
			return 0, err
		}
122 123 124 125

		// Copy .go files from the package.
		goFiles, err := filepath.Glob("*.go")
		if err != nil {
126
			return 0, err
127 128 129
		}
		if len(goFiles) > 0 {
			args := append(append([]string{"push"}, goFiles...), deviceCwd)
130 131 132
			if _, err := run(args...); err != nil {
				return 0, err
			}
133
		}
134 135
	}

136
	deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
137 138 139
	if _, err := run("push", os.Args[1], deviceBin); err != nil {
		return 0, err
	}
140

141 142 143 144 145 146 147 148
	// Forward SIGQUIT from the go command to show backtraces from
	// the binary instead of from this wrapper.
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGQUIT)
	go func() {
		for range quit {
			// We don't have the PID of the running process; use the
			// binary name instead.
149
			run("exec-out", "killall -QUIT "+binName)
150 151
		}
	}()
152
	// In light of
153
	// https://code.google.com/p/android/issues/detail?id=3254
154 155
	// dont trust the exitcode of adb. Instead, append the exitcode to
	// the output and parse it from there.
156
	const exitstr = "exitcode="
157
	cmd := `export TMPDIR="` + deviceGotmp + `"` +
158
		`; export GOROOT="` + deviceGoroot + `"` +
159
		`; export GOPATH="` + deviceGopath + `"` +
160 161 162
		`; export CGO_ENABLED=0` +
		`; export GOCACHE="` + deviceRoot + `/gocache"` +
		`; export PATH=$PATH:"` + deviceGoroot + `/bin"` +
163
		`; cd "` + deviceCwd + `"` +
164 165
		"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
		"; echo -n " + exitstr + "$?"
166
	output, err := run("exec-out", cmd)
167 168
	signal.Reset(syscall.SIGQUIT)
	close(quit)
169 170 171
	if err != nil {
		return 0, err
	}
172

173 174
	exitIdx := strings.LastIndex(output, exitstr)
	if exitIdx == -1 {
175
		return 0, fmt.Errorf("no exit code: %q", output)
176
	}
177
	code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
178
	if err != nil {
179
		return 0, fmt.Errorf("bad exit code: %v", err)
180
	}
181
	return code, nil
182
}
183 184 185

// subdir determines the package based on the current working directory,
// and returns the path to the package source relative to $GOROOT (or $GOPATH).
186
func subdir() (pkgpath string, underGoRoot bool, err error) {
187 188
	cwd, err := os.Getwd()
	if err != nil {
189
		return "", false, err
190
	}
191 192
	cwd, err = filepath.EvalSymlinks(cwd)
	if err != nil {
193
		return "", false, err
194
	}
195 196
	goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
	if err != nil {
197
		return "", false, err
198
	}
199 200 201
	if subdir, err := filepath.Rel(goroot, cwd); err == nil {
		if !strings.Contains(subdir, "..") {
			return subdir, true, nil
202 203 204 205
		}
	}

	for _, p := range filepath.SplitList(build.Default.GOPATH) {
206 207
		pabs, err := filepath.EvalSymlinks(p)
		if err != nil {
208
			return "", false, err
209
		}
210 211 212 213
		if subdir, err := filepath.Rel(pabs, cwd); err == nil {
			if !strings.Contains(subdir, "..") {
				return subdir, false, nil
			}
214 215
		}
	}
216
	return "", false, fmt.Errorf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)",
217 218
		cwd, runtime.GOROOT(), build.Default.GOPATH)
}
219

220 221 222 223 224 225
// adbCopyTree copies testdata, go.mod, go.sum files from subdir
// and from parent directories all the way up to the root of subdir.
// go.mod and go.sum files are needed for the go tool modules queries,
// and the testdata directories for tests.  It is common for tests to
// reach out into testdata from parent packages.
func adbCopyTree(deviceCwd, subdir string) error {
226 227
	dir := ""
	for {
228 229 230 231 232
		for _, path := range []string{"testdata", "go.mod", "go.sum"} {
			path := filepath.Join(dir, path)
			if _, err := os.Stat(path); err != nil {
				continue
			}
233
			devicePath := filepath.Join(deviceCwd, dir)
234 235 236
			if _, err := run("exec-out", "mkdir", "-p", devicePath); err != nil {
				return err
			}
237
			if _, err := run("push", path, devicePath); err != nil {
238 239
				return err
			}
240 241 242 243 244 245 246
		}
		if subdir == "." {
			break
		}
		subdir = filepath.Dir(subdir)
		dir = filepath.Join(dir, "..")
	}
247
	return nil
248 249 250 251 252 253 254
}

// adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
// and temporary data. Then, it copies relevant parts of GOROOT to the device,
// including the go tool built for android.
// A lock file ensures this only happens once, even with concurrent exec
// wrappers.
255
func adbCopyGoroot() error {
256 257 258 259
	// Also known by cmd/dist. The bootstrap command deletes the file.
	statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
	stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
260
		return err
261 262
	}
	defer stat.Close()
263
	// Serialize check and copying.
264
	if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
265
		return err
266 267 268
	}
	s, err := ioutil.ReadAll(stat)
	if err != nil {
269
		return err
270 271
	}
	if string(s) == "done" {
272
		return nil
273
	}
274
	// Delete GOROOT, GOPATH and any leftover test data.
275 276 277
	if _, err := run("exec-out", "rm", "-rf", deviceRoot); err != nil {
		return err
	}
278
	deviceBin := filepath.Join(deviceGoroot, "bin")
279 280 281
	if _, err := run("exec-out", "mkdir", "-p", deviceBin); err != nil {
		return err
	}
282
	goroot := runtime.GOROOT()
283
	// Build go for android.
284
	goCmd := filepath.Join(goroot, "bin", "go")
285
	tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*")
286
	if err != nil {
287
		return err
288
	}
289 290 291 292
	tmpGo.Close()
	defer os.Remove(tmpGo.Name())

	if out, err := exec.Command(goCmd, "build", "-o", tmpGo.Name(), "cmd/go").CombinedOutput(); err != nil {
293
		return fmt.Errorf("failed to build go tool for device: %s\n%v", out, err)
294
	}
295
	deviceGo := filepath.Join(deviceBin, "go")
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
	if _, err := run("push", tmpGo.Name(), deviceGo); err != nil {
		return err
	}
	for _, dir := range []string{"src", "test", "lib", "api"} {
		if _, err := run("push", filepath.Join(goroot, dir), filepath.Join(deviceGoroot)); err != nil {
			return err
		}
	}

	// Copy only the relevant from pkg.
	if _, err := run("exec-out", "mkdir", "-p", filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
		return err
	}
	if _, err := run("push", filepath.Join(goroot, "pkg", "include"), filepath.Join(deviceGoroot, "pkg")); err != nil {
		return err
	}
	runtimea, err := exec.Command(goCmd, "list", "-f", "{{.Target}}", "runtime").Output()
	pkgdir := filepath.Dir(string(runtimea))
	if pkgdir == "" {
		return errors.New("could not find android pkg dir")
	}
	if _, err := run("push", pkgdir, filepath.Join(deviceGoroot, "pkg")); err != nil {
		return err
	}
	tooldir := filepath.Join(goroot, "pkg", "tool", filepath.Base(pkgdir))
	if _, err := run("push", tooldir, filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
		return err
323
	}
324

325
	if _, err := stat.Write([]byte("done")); err != nil {
326
		return err
327
	}
328
	return nil
329
}