1 // Copyright 2014 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.
8 // This program can be used as go_android_GOARCH_exec by the Go tool.
9 // It executes binaries on an android device using adb.
30 func run(args ...string) (string, error) {
31 cmd := adbCmd(args...)
32 buf := new(strings.Builder)
33 cmd.Stdout = io.MultiWriter(os.Stdout, buf)
34 // If the adb subprocess somehow hangs, go test will kill this wrapper
35 // and wait for our os.Stderr (and os.Stdout) to close as a result.
36 // However, if the os.Stderr (or os.Stdout) file descriptors are
37 // passed on, the hanging adb subprocess will hold them open and
38 // go test will hang forever.
40 // Avoid that by wrapping stderr, breaking the short circuit and
41 // forcing cmd.Run to use another pipe and goroutine to pass
42 // along stderr from adb.
43 cmd.Stderr = struct{ io.Writer }{os.Stderr}
46 return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
48 return buf.String(), nil
51 func adb(args ...string) error {
52 if out, err := adbCmd(args...).CombinedOutput(); err != nil {
53 fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
59 func adbCmd(args ...string) *exec.Cmd {
60 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
61 args = append(strings.Split(flags, " "), args...)
63 return exec.Command("adb", args...)
67 deviceRoot = "/data/local/tmp/go_android_exec"
68 deviceGoroot = deviceRoot + "/goroot"
73 log.SetPrefix("go_android_exec: ")
74 exitCode, err := runMain()
81 func runMain() (int, error) {
82 // Concurrent use of adb is flaky, so serialize adb commands.
83 // See https://github.com/golang/go/issues/23795 or
84 // https://issuetracker.google.com/issues/73230216.
85 lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
86 lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
91 if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
95 // In case we're booting a device or emulator alongside all.bash, wait for
96 // it to be ready. adb wait-for-device is not enough, we have to
97 // wait for sys.boot_completed.
98 if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
102 // Done once per make.bash.
103 if err := adbCopyGoroot(); err != nil {
107 // Prepare a temporary directory that will be cleaned up at the end.
108 // Binary names can conflict.
109 // E.g. template.test from the {html,text}/template packages.
110 binName := filepath.Base(os.Args[1])
111 deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
112 deviceGopath := deviceGotmp + "/gopath"
113 defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
115 // Determine the package by examining the current working
116 // directory, which will look something like
117 // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
118 // We extract everything after the $GOROOT or $GOPATH to run on the
119 // same relative directory on the target device.
120 importPath, isStd, err := pkgPath()
126 // Note that we use path.Join here instead of filepath.Join:
127 // The device paths should be slash-separated even if the go_android_exec
128 // wrapper itself is compiled for Windows.
129 deviceCwd = path.Join(deviceGoroot, "src", importPath)
131 deviceCwd = path.Join(deviceGopath, "src", importPath)
132 if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
135 if err := adbCopyTree(deviceCwd, importPath); err != nil {
139 // Copy .go files from the package.
140 goFiles, err := filepath.Glob("*.go")
144 if len(goFiles) > 0 {
145 args := append(append([]string{"push"}, goFiles...), deviceCwd)
146 if err := adb(args...); err != nil {
152 deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
153 if err := adb("push", os.Args[1], deviceBin); err != nil {
157 // Forward SIGQUIT from the go command to show backtraces from
158 // the binary instead of from this wrapper.
159 quit := make(chan os.Signal, 1)
160 signal.Notify(quit, syscall.SIGQUIT)
163 // We don't have the PID of the running process; use the
164 // binary name instead.
165 adb("exec-out", "killall -QUIT "+binName)
169 // https://code.google.com/p/android/issues/detail?id=3254
170 // dont trust the exitcode of adb. Instead, append the exitcode to
171 // the output and parse it from there.
172 const exitstr = "exitcode="
173 cmd := `export TMPDIR="` + deviceGotmp + `"` +
174 `; export GOROOT="` + deviceGoroot + `"` +
175 `; export GOPATH="` + deviceGopath + `"` +
176 `; export CGO_ENABLED=0` +
177 `; export GOPROXY=` + os.Getenv("GOPROXY") +
178 `; export GOCACHE="` + deviceRoot + `/gocache"` +
179 `; export PATH="` + deviceGoroot + `/bin":$PATH` +
180 `; cd "` + deviceCwd + `"` +
181 "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
182 "; echo -n " + exitstr + "$?"
183 output, err := run("exec-out", cmd)
184 signal.Reset(syscall.SIGQUIT)
190 exitIdx := strings.LastIndex(output, exitstr)
192 return 0, fmt.Errorf("no exit code: %q", output)
194 code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
196 return 0, fmt.Errorf("bad exit code: %v", err)
201 // pkgPath determines the package import path of the current working directory,
202 // and indicates whether it is
203 // and returns the path to the package source relative to $GOROOT (or $GOPATH).
204 func pkgPath() (importPath string, isStd bool, err error) {
205 goTool, err := goTool()
207 return "", false, err
209 cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}", ".")
210 out, err := cmd.Output()
212 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
213 return "", false, fmt.Errorf("%v: %s", cmd, ee.Stderr)
215 return "", false, fmt.Errorf("%v: %w", cmd, err)
218 s := string(bytes.TrimSpace(out))
219 importPath, isStdStr, ok := strings.Cut(s, ":")
221 return "", false, fmt.Errorf("%v: missing ':' in output: %q", cmd, out)
223 if importPath == "" || importPath == "." {
224 return "", false, fmt.Errorf("current directory does not have a Go import path")
226 isStd, err = strconv.ParseBool(isStdStr)
228 return "", false, fmt.Errorf("%v: non-boolean .Standard in output: %q", cmd, out)
231 return importPath, isStd, nil
234 // adbCopyTree copies testdata, go.mod, go.sum files from subdir
235 // and from parent directories all the way up to the root of subdir.
236 // go.mod and go.sum files are needed for the go tool modules queries,
237 // and the testdata directories for tests. It is common for tests to
238 // reach out into testdata from parent packages.
239 func adbCopyTree(deviceCwd, subdir string) error {
242 for _, name := range []string{"testdata", "go.mod", "go.sum"} {
243 hostPath := filepath.Join(dir, name)
244 if _, err := os.Stat(hostPath); err != nil {
247 devicePath := path.Join(deviceCwd, dir)
248 if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
251 if err := adb("push", hostPath, devicePath); err != nil {
258 subdir = filepath.Dir(subdir)
259 dir = path.Join(dir, "..")
264 // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
265 // and temporary data. Then, it copies relevant parts of GOROOT to the device,
266 // including the go tool built for android.
267 // A lock file ensures this only happens once, even with concurrent exec
269 func adbCopyGoroot() error {
270 goTool, err := goTool()
274 cmd := exec.Command(goTool, "version")
275 cmd.Stderr = os.Stderr
276 out, err := cmd.Output()
278 return fmt.Errorf("%v: %w", cmd, err)
280 goVersion := string(out)
282 // Also known by cmd/dist. The bootstrap command deletes the file.
283 statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
284 stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
289 // Serialize check and copying.
290 if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
293 s, err := io.ReadAll(stat)
297 if string(s) == goVersion {
301 goroot, err := findGoroot()
306 // Delete the device's GOROOT, GOPATH and any leftover test data,
307 // and recreate GOROOT.
308 if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
312 // Build Go for Android.
313 cmd = exec.Command(goTool, "install", "cmd")
314 out, err = cmd.CombinedOutput()
316 if len(bytes.TrimSpace(out)) > 0 {
317 log.Printf("\n%s", out)
319 return fmt.Errorf("%v: %w", cmd, err)
321 if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
325 // Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
326 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
327 cmd.Stderr = os.Stderr
328 out, err = cmd.Output()
330 return fmt.Errorf("%v: %w", cmd, err)
332 platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
333 if platformBin == "." {
334 return errors.New("failed to locate cmd/go for target platform")
336 if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
340 // Copy only the relevant subdirectories from pkg: pkg/include and the
341 // platform-native binaries in pkg/tool.
342 if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
345 if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
349 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
350 cmd.Stderr = os.Stderr
351 out, err = cmd.Output()
353 return fmt.Errorf("%v: %w", cmd, err)
355 platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
356 if platformToolDir == "." {
357 return errors.New("failed to locate cmd/compile for target platform")
359 relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
363 if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
367 // Copy all other files from GOROOT.
368 dirents, err := os.ReadDir(goroot)
372 for _, de := range dirents {
375 // We already created GOROOT/bin and GOROOT/pkg above; skip those.
378 if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
383 if _, err := stat.WriteString(goVersion); err != nil {
389 func findGoroot() (string, error) {
390 gorootOnce.Do(func() {
391 // If runtime.GOROOT reports a non-empty path, assume that it is valid.
392 // (It may be empty if this binary was built with -trimpath.)
393 gorootPath = runtime.GOROOT()
394 if gorootPath != "" {
398 // runtime.GOROOT is empty — perhaps go_android_exec was built with
399 // -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
400 // assuming that the 'go' command in $PATH is the correct one.
402 cmd := exec.Command("go", "env", "GOROOT")
403 cmd.Stderr = os.Stderr
404 out, err := cmd.Output()
406 gorootErr = fmt.Errorf("%v: %w", cmd, err)
409 gorootPath = string(bytes.TrimSpace(out))
410 if gorootPath == "" {
411 gorootErr = errors.New("GOROOT not found")
415 return gorootPath, gorootErr
418 func goTool() (string, error) {
419 goroot, err := findGoroot()
423 return filepath.Join(goroot, "bin", "go"), nil