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.
5 // This wrapper uses syscall.Flock to prevent concurrent adb commands,
6 // so for now it only builds on platforms that support that system call.
7 // TODO(#33974): use a more portable library for file locking.
9 //go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
11 // This program can be used as go_android_GOARCH_exec by the Go tool.
12 // It executes binaries on an android device using adb.
34 func adbRun(args string) (int, error) {
35 // The exit code of adb is often wrong. In theory it was fixed in 2016
36 // (https://code.google.com/p/android/issues/detail?id=3254), but it's
37 // still broken on our builders in 2023. Instead, append the exitcode to
38 // the output and parse it from there.
39 filter, exitStr := newExitCodeFilter(os.Stdout)
40 args += "; echo -n " + exitStr + "$?"
42 cmd := adbCmd("exec-out", args)
44 // If the adb subprocess somehow hangs, go test will kill this wrapper
45 // and wait for our os.Stderr (and os.Stdout) to close as a result.
46 // However, if the os.Stderr (or os.Stdout) file descriptors are
47 // passed on, the hanging adb subprocess will hold them open and
48 // go test will hang forever.
50 // Avoid that by wrapping stderr, breaking the short circuit and
51 // forcing cmd.Run to use another pipe and goroutine to pass
52 // along stderr from adb.
53 cmd.Stderr = struct{ io.Writer }{os.Stderr}
56 // Before we process err, flush any further output and get the exit code.
57 exitCode, err2 := filter.Finish()
60 return 0, fmt.Errorf("adb exec-out %s: %v", args, err)
65 func adb(args ...string) error {
66 if out, err := adbCmd(args...).CombinedOutput(); err != nil {
67 fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
73 func adbCmd(args ...string) *exec.Cmd {
74 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
75 args = append(strings.Split(flags, " "), args...)
77 return exec.Command("adb", args...)
81 deviceRoot = "/data/local/tmp/go_android_exec"
82 deviceGoroot = deviceRoot + "/goroot"
87 log.SetPrefix("go_android_exec: ")
88 exitCode, err := runMain()
95 func runMain() (int, error) {
96 // Concurrent use of adb is flaky, so serialize adb commands.
97 // See https://github.com/golang/go/issues/23795 or
98 // https://issuetracker.google.com/issues/73230216.
99 lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
100 lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
105 if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
109 // In case we're booting a device or emulator alongside all.bash, wait for
110 // it to be ready. adb wait-for-device is not enough, we have to
111 // wait for sys.boot_completed.
112 if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
116 // Done once per make.bash.
117 if err := adbCopyGoroot(); err != nil {
121 // Prepare a temporary directory that will be cleaned up at the end.
122 // Binary names can conflict.
123 // E.g. template.test from the {html,text}/template packages.
124 binName := filepath.Base(os.Args[1])
125 deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
126 deviceGopath := deviceGotmp + "/gopath"
127 defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
129 // Determine the package by examining the current working
130 // directory, which will look something like
131 // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
132 // We extract everything after the $GOROOT or $GOPATH to run on the
133 // same relative directory on the target device.
134 importPath, isStd, modPath, modDir, err := pkgPath()
140 // Note that we use path.Join here instead of filepath.Join:
141 // The device paths should be slash-separated even if the go_android_exec
142 // wrapper itself is compiled for Windows.
143 deviceCwd = path.Join(deviceGoroot, "src", importPath)
145 deviceCwd = path.Join(deviceGopath, "src", importPath)
147 // In module mode, the user may reasonably expect the entire module
148 // to be present. Copy it over.
149 deviceModDir := path.Join(deviceGopath, "src", modPath)
150 if err := adb("exec-out", "mkdir", "-p", path.Dir(deviceModDir)); err != nil {
153 // We use a single recursive 'adb push' of the module root instead of
154 // walking the tree and copying it piecewise. If the directory tree
155 // contains nested modules this could push a lot of unnecessary contents,
156 // but for the golang.org/x repos it seems to be significantly (~2x)
157 // faster than copying one file at a time (via filepath.WalkDir),
158 // apparently due to high latency in 'adb' commands.
159 if err := adb("push", modDir, deviceModDir); err != nil {
163 if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
166 if err := adbCopyTree(deviceCwd, importPath); err != nil {
170 // Copy .go files from the package.
171 goFiles, err := filepath.Glob("*.go")
175 if len(goFiles) > 0 {
176 args := append(append([]string{"push"}, goFiles...), deviceCwd)
177 if err := adb(args...); err != nil {
184 deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
185 if err := adb("push", os.Args[1], deviceBin); err != nil {
189 // Forward SIGQUIT from the go command to show backtraces from
190 // the binary instead of from this wrapper.
191 quit := make(chan os.Signal, 1)
192 signal.Notify(quit, syscall.SIGQUIT)
195 // We don't have the PID of the running process; use the
196 // binary name instead.
197 adb("exec-out", "killall -QUIT "+binName)
200 cmd := `export TMPDIR="` + deviceGotmp + `"` +
201 `; export GOROOT="` + deviceGoroot + `"` +
202 `; export GOPATH="` + deviceGopath + `"` +
203 `; export CGO_ENABLED=0` +
204 `; export GOPROXY=` + os.Getenv("GOPROXY") +
205 `; export GOCACHE="` + deviceRoot + `/gocache"` +
206 `; export PATH="` + deviceGoroot + `/bin":$PATH` +
207 `; export HOME="` + deviceRoot + `/home"` +
208 `; cd "` + deviceCwd + `"` +
209 "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ")
210 code, err := adbRun(cmd)
211 signal.Reset(syscall.SIGQUIT)
216 type exitCodeFilter struct {
217 w io.Writer // Pass through to w
218 exitRe *regexp.Regexp
222 func newExitCodeFilter(w io.Writer) (*exitCodeFilter, string) {
223 const exitStr = "exitcode="
225 // Build a regexp that matches any prefix of the exit string at the end of
226 // the input. We do it this way to avoid assuming anything about the
227 // subcommand output (e.g., it might not be \n-terminated).
228 var exitReStr strings.Builder
229 for i := 1; i <= len(exitStr); i++ {
230 fmt.Fprintf(&exitReStr, "%s$|", exitStr[:i])
232 // Finally, match the exit string along with an exit code.
233 // This is the only case we use a group, and we'll use this
234 // group to extract the numeric code.
235 fmt.Fprintf(&exitReStr, "%s([0-9]+)$", exitStr)
236 exitRe := regexp.MustCompile(exitReStr.String())
238 return &exitCodeFilter{w: w, exitRe: exitRe}, exitStr
241 func (f *exitCodeFilter) Write(data []byte) (int, error) {
244 // Flush to w until a potential match of exitRe
246 match := f.exitRe.FindIndex(b)
248 // Flush all of the buffer.
249 _, err := f.w.Write(b)
255 // Flush up to the beginning of the (potential) match.
256 _, err := f.w.Write(b[:match[0]])
265 func (f *exitCodeFilter) Finish() (int, error) {
266 // f.buf could be empty, contain a partial match of exitRe, or
267 // contain a full match.
270 match := f.exitRe.FindSubmatch(b)
271 if len(match) < 2 || match[1] == nil {
272 // Not a full match. Flush.
273 if _, err := f.w.Write(b); err != nil {
276 return 0, fmt.Errorf("no exit code (in %q)", string(b))
279 // Parse the exit code.
280 code, err := strconv.Atoi(string(match[1]))
282 // Something is malformed. Flush.
283 if _, err := f.w.Write(b); err != nil {
286 return 0, fmt.Errorf("bad exit code: %v (in %q)", err, string(b))
291 // pkgPath determines the package import path of the current working directory,
292 // and indicates whether it is
293 // and returns the path to the package source relative to $GOROOT (or $GOPATH).
294 func pkgPath() (importPath string, isStd bool, modPath, modDir string, err error) {
295 errorf := func(format string, args ...any) (string, bool, string, string, error) {
296 return "", false, "", "", fmt.Errorf(format, args...)
298 goTool, err := goTool()
300 return errorf("%w", err)
302 cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}{{with .Module}}:{{.Path}}:{{.Dir}}{{end}}", ".")
303 out, err := cmd.Output()
305 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
306 return errorf("%v: %s", cmd, ee.Stderr)
308 return errorf("%v: %w", cmd, err)
311 parts := strings.SplitN(string(bytes.TrimSpace(out)), ":", 4)
313 return errorf("%v: missing ':' in output: %q", cmd, out)
315 importPath = parts[0]
316 if importPath == "" || importPath == "." {
317 return errorf("current directory does not have a Go import path")
319 isStd, err = strconv.ParseBool(parts[1])
321 return errorf("%v: non-boolean .Standard in output: %q", cmd, out)
328 return importPath, isStd, modPath, modDir, nil
331 // adbCopyTree copies testdata, go.mod, go.sum files from subdir
332 // and from parent directories all the way up to the root of subdir.
333 // go.mod and go.sum files are needed for the go tool modules queries,
334 // and the testdata directories for tests. It is common for tests to
335 // reach out into testdata from parent packages.
336 func adbCopyTree(deviceCwd, subdir string) error {
339 for _, name := range []string{"testdata", "go.mod", "go.sum"} {
340 hostPath := filepath.Join(dir, name)
341 if _, err := os.Stat(hostPath); err != nil {
344 devicePath := path.Join(deviceCwd, dir)
345 if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
348 if err := adb("push", hostPath, devicePath); err != nil {
355 subdir = filepath.Dir(subdir)
356 dir = path.Join(dir, "..")
361 // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
362 // and temporary data. Then, it copies relevant parts of GOROOT to the device,
363 // including the go tool built for android.
364 // A lock file ensures this only happens once, even with concurrent exec
366 func adbCopyGoroot() error {
367 goTool, err := goTool()
371 cmd := exec.Command(goTool, "version")
372 cmd.Stderr = os.Stderr
373 out, err := cmd.Output()
375 return fmt.Errorf("%v: %w", cmd, err)
377 goVersion := string(out)
379 // Also known by cmd/dist. The bootstrap command deletes the file.
380 statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
381 stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
386 // Serialize check and copying.
387 if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
390 s, err := io.ReadAll(stat)
394 if string(s) == goVersion {
398 goroot, err := findGoroot()
403 // Delete the device's GOROOT, GOPATH and any leftover test data,
404 // and recreate GOROOT.
405 if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
409 // Build Go for Android.
410 cmd = exec.Command(goTool, "install", "cmd")
411 out, err = cmd.CombinedOutput()
413 if len(bytes.TrimSpace(out)) > 0 {
414 log.Printf("\n%s", out)
416 return fmt.Errorf("%v: %w", cmd, err)
418 if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
422 // Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
423 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
424 cmd.Stderr = os.Stderr
425 out, err = cmd.Output()
427 return fmt.Errorf("%v: %w", cmd, err)
429 platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
430 if platformBin == "." {
431 return errors.New("failed to locate cmd/go for target platform")
433 if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
437 // Copy only the relevant subdirectories from pkg: pkg/include and the
438 // platform-native binaries in pkg/tool.
439 if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
442 if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
446 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
447 cmd.Stderr = os.Stderr
448 out, err = cmd.Output()
450 return fmt.Errorf("%v: %w", cmd, err)
452 platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
453 if platformToolDir == "." {
454 return errors.New("failed to locate cmd/compile for target platform")
456 relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
460 if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
464 // Copy all other files from GOROOT.
465 dirents, err := os.ReadDir(goroot)
469 for _, de := range dirents {
472 // We already created GOROOT/bin and GOROOT/pkg above; skip those.
475 if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
480 if _, err := stat.WriteString(goVersion); err != nil {
486 func findGoroot() (string, error) {
487 gorootOnce.Do(func() {
488 // If runtime.GOROOT reports a non-empty path, assume that it is valid.
489 // (It may be empty if this binary was built with -trimpath.)
490 gorootPath = runtime.GOROOT()
491 if gorootPath != "" {
495 // runtime.GOROOT is empty — perhaps go_android_exec was built with
496 // -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
497 // assuming that the 'go' command in $PATH is the correct one.
499 cmd := exec.Command("go", "env", "GOROOT")
500 cmd.Stderr = os.Stderr
501 out, err := cmd.Output()
503 gorootErr = fmt.Errorf("%v: %w", cmd, err)
506 gorootPath = string(bytes.TrimSpace(out))
507 if gorootPath == "" {
508 gorootErr = errors.New("GOROOT not found")
512 return gorootPath, gorootErr
515 func goTool() (string, error) {
516 goroot, err := findGoroot()
520 return filepath.Join(goroot, "bin", "go"), nil