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.
4
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.
8
9//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
10
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.
13package main
14
15import (
16	"bytes"
17	"errors"
18	"fmt"
19	"io"
20	"log"
21	"os"
22	"os/exec"
23	"os/signal"
24	"path"
25	"path/filepath"
26	"regexp"
27	"runtime"
28	"strconv"
29	"strings"
30	"sync"
31	"syscall"
32)
33
34func 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 + "$?"
41
42	cmd := adbCmd("exec-out", args)
43	cmd.Stdout = filter
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.
49	//
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}
54	err := cmd.Run()
55
56	// Before we process err, flush any further output and get the exit code.
57	exitCode, err2 := filter.Finish()
58
59	if err != nil {
60		return 0, fmt.Errorf("adb exec-out %s: %v", args, err)
61	}
62	return exitCode, err2
63}
64
65func 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)
68		return err
69	}
70	return nil
71}
72
73func adbCmd(args ...string) *exec.Cmd {
74	if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
75		args = append(strings.Split(flags, " "), args...)
76	}
77	return exec.Command("adb", args...)
78}
79
80const (
81	deviceRoot   = "/data/local/tmp/go_android_exec"
82	deviceGoroot = deviceRoot + "/goroot"
83)
84
85func main() {
86	log.SetFlags(0)
87	log.SetPrefix("go_android_exec: ")
88	exitCode, err := runMain()
89	if err != nil {
90		log.Fatal(err)
91	}
92	os.Exit(exitCode)
93}
94
95func 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)
101	if err != nil {
102		return 0, err
103	}
104	defer lock.Close()
105	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
106		return 0, err
107	}
108
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 {
113		return 0, err
114	}
115
116	// Done once per make.bash.
117	if err := adbCopyGoroot(); err != nil {
118		return 0, err
119	}
120
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.
128
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()
135	if err != nil {
136		return 0, err
137	}
138	var deviceCwd string
139	if isStd {
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)
144	} else {
145		deviceCwd = path.Join(deviceGopath, "src", importPath)
146		if modDir != "" {
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 {
151				return 0, err
152			}
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 {
160				return 0, err
161			}
162		} else {
163			if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
164				return 0, err
165			}
166			if err := adbCopyTree(deviceCwd, importPath); err != nil {
167				return 0, err
168			}
169
170			// Copy .go files from the package.
171			goFiles, err := filepath.Glob("*.go")
172			if err != nil {
173				return 0, err
174			}
175			if len(goFiles) > 0 {
176				args := append(append([]string{"push"}, goFiles...), deviceCwd)
177				if err := adb(args...); err != nil {
178					return 0, err
179				}
180			}
181		}
182	}
183
184	deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
185	if err := adb("push", os.Args[1], deviceBin); err != nil {
186		return 0, err
187	}
188
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)
193	go func() {
194		for range quit {
195			// We don't have the PID of the running process; use the
196			// binary name instead.
197			adb("exec-out", "killall -QUIT "+binName)
198		}
199	}()
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)
212	close(quit)
213	return code, err
214}
215
216type exitCodeFilter struct {
217	w      io.Writer // Pass through to w
218	exitRe *regexp.Regexp
219	buf    bytes.Buffer
220}
221
222func newExitCodeFilter(w io.Writer) (*exitCodeFilter, string) {
223	const exitStr = "exitcode="
224
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])
231	}
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())
237
238	return &exitCodeFilter{w: w, exitRe: exitRe}, exitStr
239}
240
241func (f *exitCodeFilter) Write(data []byte) (int, error) {
242	n := len(data)
243	f.buf.Write(data)
244	// Flush to w until a potential match of exitRe
245	b := f.buf.Bytes()
246	match := f.exitRe.FindIndex(b)
247	if match == nil {
248		// Flush all of the buffer.
249		_, err := f.w.Write(b)
250		f.buf.Reset()
251		if err != nil {
252			return n, err
253		}
254	} else {
255		// Flush up to the beginning of the (potential) match.
256		_, err := f.w.Write(b[:match[0]])
257		f.buf.Next(match[0])
258		if err != nil {
259			return n, err
260		}
261	}
262	return n, nil
263}
264
265func (f *exitCodeFilter) Finish() (int, error) {
266	// f.buf could be empty, contain a partial match of exitRe, or
267	// contain a full match.
268	b := f.buf.Bytes()
269	defer f.buf.Reset()
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 {
274			return 0, err
275		}
276		return 0, fmt.Errorf("no exit code (in %q)", string(b))
277	}
278
279	// Parse the exit code.
280	code, err := strconv.Atoi(string(match[1]))
281	if err != nil {
282		// Something is malformed. Flush.
283		if _, err := f.w.Write(b); err != nil {
284			return 0, err
285		}
286		return 0, fmt.Errorf("bad exit code: %v (in %q)", err, string(b))
287	}
288	return code, nil
289}
290
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).
294func 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...)
297	}
298	goTool, err := goTool()
299	if err != nil {
300		return errorf("%w", err)
301	}
302	cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}{{with .Module}}:{{.Path}}:{{.Dir}}{{end}}", ".")
303	out, err := cmd.Output()
304	if err != nil {
305		if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
306			return errorf("%v: %s", cmd, ee.Stderr)
307		}
308		return errorf("%v: %w", cmd, err)
309	}
310
311	parts := strings.SplitN(string(bytes.TrimSpace(out)), ":", 4)
312	if len(parts) < 2 {
313		return errorf("%v: missing ':' in output: %q", cmd, out)
314	}
315	importPath = parts[0]
316	if importPath == "" || importPath == "." {
317		return errorf("current directory does not have a Go import path")
318	}
319	isStd, err = strconv.ParseBool(parts[1])
320	if err != nil {
321		return errorf("%v: non-boolean .Standard in output: %q", cmd, out)
322	}
323	if len(parts) >= 4 {
324		modPath = parts[2]
325		modDir = parts[3]
326	}
327
328	return importPath, isStd, modPath, modDir, nil
329}
330
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.
336func adbCopyTree(deviceCwd, subdir string) error {
337	dir := ""
338	for {
339		for _, name := range []string{"testdata", "go.mod", "go.sum"} {
340			hostPath := filepath.Join(dir, name)
341			if _, err := os.Stat(hostPath); err != nil {
342				continue
343			}
344			devicePath := path.Join(deviceCwd, dir)
345			if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
346				return err
347			}
348			if err := adb("push", hostPath, devicePath); err != nil {
349				return err
350			}
351		}
352		if subdir == "." {
353			break
354		}
355		subdir = filepath.Dir(subdir)
356		dir = path.Join(dir, "..")
357	}
358	return nil
359}
360
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
365// wrappers.
366func adbCopyGoroot() error {
367	goTool, err := goTool()
368	if err != nil {
369		return err
370	}
371	cmd := exec.Command(goTool, "version")
372	cmd.Stderr = os.Stderr
373	out, err := cmd.Output()
374	if err != nil {
375		return fmt.Errorf("%v: %w", cmd, err)
376	}
377	goVersion := string(out)
378
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)
382	if err != nil {
383		return err
384	}
385	defer stat.Close()
386	// Serialize check and copying.
387	if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
388		return err
389	}
390	s, err := io.ReadAll(stat)
391	if err != nil {
392		return err
393	}
394	if string(s) == goVersion {
395		return nil
396	}
397
398	goroot, err := findGoroot()
399	if err != nil {
400		return err
401	}
402
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 {
406		return err
407	}
408
409	// Build Go for Android.
410	cmd = exec.Command(goTool, "install", "cmd")
411	out, err = cmd.CombinedOutput()
412	if err != nil {
413		if len(bytes.TrimSpace(out)) > 0 {
414			log.Printf("\n%s", out)
415		}
416		return fmt.Errorf("%v: %w", cmd, err)
417	}
418	if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
419		return err
420	}
421
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()
426	if err != nil {
427		return fmt.Errorf("%v: %w", cmd, err)
428	}
429	platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
430	if platformBin == "." {
431		return errors.New("failed to locate cmd/go for target platform")
432	}
433	if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
434		return err
435	}
436
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 {
440		return err
441	}
442	if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
443		return err
444	}
445
446	cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
447	cmd.Stderr = os.Stderr
448	out, err = cmd.Output()
449	if err != nil {
450		return fmt.Errorf("%v: %w", cmd, err)
451	}
452	platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
453	if platformToolDir == "." {
454		return errors.New("failed to locate cmd/compile for target platform")
455	}
456	relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
457	if err != nil {
458		return err
459	}
460	if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
461		return err
462	}
463
464	// Copy all other files from GOROOT.
465	dirents, err := os.ReadDir(goroot)
466	if err != nil {
467		return err
468	}
469	for _, de := range dirents {
470		switch de.Name() {
471		case "bin", "pkg":
472			// We already created GOROOT/bin and GOROOT/pkg above; skip those.
473			continue
474		}
475		if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
476			return err
477		}
478	}
479
480	if _, err := stat.WriteString(goVersion); err != nil {
481		return err
482	}
483	return nil
484}
485
486func 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 != "" {
492			return
493		}
494
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.
498
499		cmd := exec.Command("go", "env", "GOROOT")
500		cmd.Stderr = os.Stderr
501		out, err := cmd.Output()
502		if err != nil {
503			gorootErr = fmt.Errorf("%v: %w", cmd, err)
504		}
505
506		gorootPath = string(bytes.TrimSpace(out))
507		if gorootPath == "" {
508			gorootErr = errors.New("GOROOT not found")
509		}
510	})
511
512	return gorootPath, gorootErr
513}
514
515func goTool() (string, error) {
516	goroot, err := findGoroot()
517	if err != nil {
518		return "", err
519	}
520	return filepath.Join(goroot, "bin", "go"), nil
521}
522
523var (
524	gorootOnce sync.Once
525	gorootPath string
526	gorootErr  error
527)
528