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 program can be used as go_android_GOARCH_exec by the Go tool.
6 // It executes binaries on an android device using adb.
27 func run(args ...string) (string, error) {
28 cmd := adbCmd(args...)
29 buf := new(strings.Builder)
30 cmd.Stdout = io.MultiWriter(os.Stdout, buf)
31 // If the adb subprocess somehow hangs, go test will kill this wrapper
32 // and wait for our os.Stderr (and os.Stdout) to close as a result.
33 // However, if the os.Stderr (or os.Stdout) file descriptors are
34 // passed on, the hanging adb subprocess will hold them open and
35 // go test will hang forever.
37 // Avoid that by wrapping stderr, breaking the short circuit and
38 // forcing cmd.Run to use another pipe and goroutine to pass
39 // along stderr from adb.
40 cmd.Stderr = struct{ io.Writer }{os.Stderr}
43 return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
45 return buf.String(), nil
48 func adb(args ...string) error {
49 if out, err := adbCmd(args...).CombinedOutput(); err != nil {
50 fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
56 func adbCmd(args ...string) *exec.Cmd {
57 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
58 args = append(strings.Split(flags, " "), args...)
60 return exec.Command("adb", args...)
64 deviceRoot = "/data/local/tmp/go_android_exec"
65 deviceGoroot = deviceRoot + "/goroot"
70 log.SetPrefix("go_android_exec: ")
71 exitCode, err := runMain()
78 func runMain() (int, error) {
79 // Concurrent use of adb is flaky, so serialize adb commands.
80 // See https://github.com/golang/go/issues/23795 or
81 // https://issuetracker.google.com/issues/73230216.
82 lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
83 lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
88 if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
92 // In case we're booting a device or emulator alongside all.bash, wait for
93 // it to be ready. adb wait-for-device is not enough, we have to
94 // wait for sys.boot_completed.
95 if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
99 // Done once per make.bash.
100 if err := adbCopyGoroot(); err != nil {
104 // Prepare a temporary directory that will be cleaned up at the end.
105 // Binary names can conflict.
106 // E.g. template.test from the {html,text}/template packages.
107 binName := filepath.Base(os.Args[1])
108 deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
109 deviceGopath := deviceGotmp + "/gopath"
110 defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
112 // Determine the package by examining the current working
113 // directory, which will look something like
114 // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
115 // We extract everything after the $GOROOT or $GOPATH to run on the
116 // same relative directory on the target device.
117 importPath, isStd, err := pkgPath()
123 // Note that we use path.Join here instead of filepath.Join:
124 // The device paths should be slash-separated even if the go_android_exec
125 // wrapper itself is compiled for Windows.
126 deviceCwd = path.Join(deviceGoroot, "src", importPath)
128 deviceCwd = path.Join(deviceGopath, "src", importPath)
129 if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
132 if err := adbCopyTree(deviceCwd, importPath); err != nil {
136 // Copy .go files from the package.
137 goFiles, err := filepath.Glob("*.go")
141 if len(goFiles) > 0 {
142 args := append(append([]string{"push"}, goFiles...), deviceCwd)
143 if err := adb(args...); err != nil {
149 deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
150 if err := adb("push", os.Args[1], deviceBin); err != nil {
154 // Forward SIGQUIT from the go command to show backtraces from
155 // the binary instead of from this wrapper.
156 quit := make(chan os.Signal, 1)
157 signal.Notify(quit, syscall.SIGQUIT)
160 // We don't have the PID of the running process; use the
161 // binary name instead.
162 adb("exec-out", "killall -QUIT "+binName)
166 // https://code.google.com/p/android/issues/detail?id=3254
167 // dont trust the exitcode of adb. Instead, append the exitcode to
168 // the output and parse it from there.
169 const exitstr = "exitcode="
170 cmd := `export TMPDIR="` + deviceGotmp + `"` +
171 `; export GOROOT="` + deviceGoroot + `"` +
172 `; export GOPATH="` + deviceGopath + `"` +
173 `; export CGO_ENABLED=0` +
174 `; export GOPROXY=` + os.Getenv("GOPROXY") +
175 `; export GOCACHE="` + deviceRoot + `/gocache"` +
176 `; export PATH="` + deviceGoroot + `/bin":$PATH` +
177 `; cd "` + deviceCwd + `"` +
178 "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
179 "; echo -n " + exitstr + "$?"
180 output, err := run("exec-out", cmd)
181 signal.Reset(syscall.SIGQUIT)
187 exitIdx := strings.LastIndex(output, exitstr)
189 return 0, fmt.Errorf("no exit code: %q", output)
191 code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
193 return 0, fmt.Errorf("bad exit code: %v", err)
198 // pkgPath determines the package import path of the current working directory,
199 // and indicates whether it is
200 // and returns the path to the package source relative to $GOROOT (or $GOPATH).
201 func pkgPath() (importPath string, isStd bool, err error) {
202 goTool, err := goTool()
204 return "", false, err
206 cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}", ".")
207 out, err := cmd.Output()
209 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
210 return "", false, fmt.Errorf("%v: %s", cmd, ee.Stderr)
212 return "", false, fmt.Errorf("%v: %w", cmd, err)
215 s := string(bytes.TrimSpace(out))
216 importPath, isStdStr, ok := strings.Cut(s, ":")
218 return "", false, fmt.Errorf("%v: missing ':' in output: %q", cmd, out)
220 if importPath == "" || importPath == "." {
221 return "", false, fmt.Errorf("current directory does not have a Go import path")
223 isStd, err = strconv.ParseBool(isStdStr)
225 return "", false, fmt.Errorf("%v: non-boolean .Standard in output: %q", cmd, out)
228 return importPath, isStd, nil
231 // adbCopyTree copies testdata, go.mod, go.sum files from subdir
232 // and from parent directories all the way up to the root of subdir.
233 // go.mod and go.sum files are needed for the go tool modules queries,
234 // and the testdata directories for tests. It is common for tests to
235 // reach out into testdata from parent packages.
236 func adbCopyTree(deviceCwd, subdir string) error {
239 for _, name := range []string{"testdata", "go.mod", "go.sum"} {
240 hostPath := filepath.Join(dir, name)
241 if _, err := os.Stat(hostPath); err != nil {
244 devicePath := path.Join(deviceCwd, dir)
245 if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
248 if err := adb("push", hostPath, devicePath); err != nil {
255 subdir = filepath.Dir(subdir)
256 dir = path.Join(dir, "..")
261 // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
262 // and temporary data. Then, it copies relevant parts of GOROOT to the device,
263 // including the go tool built for android.
264 // A lock file ensures this only happens once, even with concurrent exec
266 func adbCopyGoroot() error {
267 goTool, err := goTool()
271 cmd := exec.Command(goTool, "version")
272 cmd.Stderr = os.Stderr
273 out, err := cmd.Output()
275 return fmt.Errorf("%v: %w", cmd, err)
277 goVersion := string(out)
279 // Also known by cmd/dist. The bootstrap command deletes the file.
280 statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
281 stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
286 // Serialize check and copying.
287 if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
290 s, err := io.ReadAll(stat)
294 if string(s) == goVersion {
298 goroot, err := findGoroot()
303 // Delete the device's GOROOT, GOPATH and any leftover test data,
304 // and recreate GOROOT.
305 if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
309 // Build Go for Android.
310 cmd = exec.Command(goTool, "install", "cmd")
311 out, err = cmd.CombinedOutput()
313 if len(bytes.TrimSpace(out)) > 0 {
314 log.Printf("\n%s", out)
316 return fmt.Errorf("%v: %w", cmd, err)
318 if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
322 // Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
323 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
324 cmd.Stderr = os.Stderr
325 out, err = cmd.Output()
327 return fmt.Errorf("%v: %w", cmd, err)
329 platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
330 if platformBin == "." {
331 return errors.New("failed to locate cmd/go for target platform")
333 if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
337 // Copy only the relevant subdirectories from pkg: pkg/include and the
338 // platform-native binaries in pkg/tool.
339 if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
342 if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
346 cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
347 cmd.Stderr = os.Stderr
348 out, err = cmd.Output()
350 return fmt.Errorf("%v: %w", cmd, err)
352 platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
353 if platformToolDir == "." {
354 return errors.New("failed to locate cmd/compile for target platform")
356 relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
360 if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
364 // Copy all other files from GOROOT.
365 dirents, err := os.ReadDir(goroot)
369 for _, de := range dirents {
372 // We already created GOROOT/bin and GOROOT/pkg above; skip those.
375 if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
380 if _, err := stat.WriteString(goVersion); err != nil {
386 func findGoroot() (string, error) {
387 gorootOnce.Do(func() {
388 // If runtime.GOROOT reports a non-empty path, assume that it is valid.
389 // (It may be empty if this binary was built with -trimpath.)
390 gorootPath = runtime.GOROOT()
391 if gorootPath != "" {
395 // runtime.GOROOT is empty — perhaps go_android_exec was built with
396 // -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
397 // assuming that the 'go' command in $PATH is the correct one.
399 cmd := exec.Command("go", "env", "GOROOT")
400 cmd.Stderr = os.Stderr
401 out, err := cmd.Output()
403 gorootErr = fmt.Errorf("%v: %w", cmd, err)
406 gorootPath = string(bytes.TrimSpace(out))
407 if gorootPath == "" {
408 gorootErr = errors.New("GOROOT not found")
412 return gorootPath, gorootErr
415 func goTool() (string, error) {
416 goroot, err := findGoroot()
420 return filepath.Join(goroot, "bin", "go"), nil