Source file misc/android/go_android_exec.go

     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  //go:build ignore
     6  // +build ignore
     7  
     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.
    10  package main
    11  
    12  import (
    13  	"bytes"
    14  	"errors"
    15  	"fmt"
    16  	"go/build"
    17  	"io"
    18  	"log"
    19  	"os"
    20  	"os/exec"
    21  	"os/signal"
    22  	"path/filepath"
    23  	"runtime"
    24  	"strconv"
    25  	"strings"
    26  	"syscall"
    27  )
    28  
    29  func run(args ...string) (string, error) {
    30  	cmd := adbCmd(args...)
    31  	buf := new(bytes.Buffer)
    32  	cmd.Stdout = io.MultiWriter(os.Stdout, buf)
    33  	// If the adb subprocess somehow hangs, go test will kill this wrapper
    34  	// and wait for our os.Stderr (and os.Stdout) to close as a result.
    35  	// However, if the os.Stderr (or os.Stdout) file descriptors are
    36  	// passed on, the hanging adb subprocess will hold them open and
    37  	// go test will hang forever.
    38  	//
    39  	// Avoid that by wrapping stderr, breaking the short circuit and
    40  	// forcing cmd.Run to use another pipe and goroutine to pass
    41  	// along stderr from adb.
    42  	cmd.Stderr = struct{ io.Writer }{os.Stderr}
    43  	err := cmd.Run()
    44  	if err != nil {
    45  		return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
    46  	}
    47  	return buf.String(), nil
    48  }
    49  
    50  func adb(args ...string) error {
    51  	if out, err := adbCmd(args...).CombinedOutput(); err != nil {
    52  		fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
    53  		return err
    54  	}
    55  	return nil
    56  }
    57  
    58  func adbCmd(args ...string) *exec.Cmd {
    59  	if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
    60  		args = append(strings.Split(flags, " "), args...)
    61  	}
    62  	return exec.Command("adb", args...)
    63  }
    64  
    65  const (
    66  	deviceRoot   = "/data/local/tmp/go_android_exec"
    67  	deviceGoroot = deviceRoot + "/goroot"
    68  )
    69  
    70  func main() {
    71  	log.SetFlags(0)
    72  	log.SetPrefix("go_android_exec: ")
    73  	exitCode, err := runMain()
    74  	if err != nil {
    75  		log.Fatal(err)
    76  	}
    77  	os.Exit(exitCode)
    78  }
    79  
    80  func runMain() (int, error) {
    81  	// Concurrent use of adb is flaky, so serialize adb commands.
    82  	// See https://github.com/golang/go/issues/23795 or
    83  	// https://issuetracker.google.com/issues/73230216.
    84  	lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
    85  	lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
    86  	if err != nil {
    87  		return 0, err
    88  	}
    89  	defer lock.Close()
    90  	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
    91  		return 0, err
    92  	}
    93  
    94  	// In case we're booting a device or emulator alongside all.bash, wait for
    95  	// it to be ready. adb wait-for-device is not enough, we have to
    96  	// wait for sys.boot_completed.
    97  	if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
    98  		return 0, err
    99  	}
   100  
   101  	// Done once per make.bash.
   102  	if err := adbCopyGoroot(); err != nil {
   103  		return 0, err
   104  	}
   105  
   106  	// Prepare a temporary directory that will be cleaned up at the end.
   107  	// Binary names can conflict.
   108  	// E.g. template.test from the {html,text}/template packages.
   109  	binName := filepath.Base(os.Args[1])
   110  	deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
   111  	deviceGopath := deviceGotmp + "/gopath"
   112  	defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
   113  
   114  	// Determine the package by examining the current working
   115  	// directory, which will look something like
   116  	// "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
   117  	// We extract everything after the $GOROOT or $GOPATH to run on the
   118  	// same relative directory on the target device.
   119  	subdir, inGoRoot, err := subdir()
   120  	if err != nil {
   121  		return 0, err
   122  	}
   123  	deviceCwd := filepath.Join(deviceGopath, subdir)
   124  	if inGoRoot {
   125  		deviceCwd = filepath.Join(deviceGoroot, subdir)
   126  	} else {
   127  		if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
   128  			return 0, err
   129  		}
   130  		if err := adbCopyTree(deviceCwd, subdir); err != nil {
   131  			return 0, err
   132  		}
   133  
   134  		// Copy .go files from the package.
   135  		goFiles, err := filepath.Glob("*.go")
   136  		if err != nil {
   137  			return 0, err
   138  		}
   139  		if len(goFiles) > 0 {
   140  			args := append(append([]string{"push"}, goFiles...), deviceCwd)
   141  			if err := adb(args...); err != nil {
   142  				return 0, err
   143  			}
   144  		}
   145  	}
   146  
   147  	deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
   148  	if err := adb("push", os.Args[1], deviceBin); err != nil {
   149  		return 0, err
   150  	}
   151  
   152  	// Forward SIGQUIT from the go command to show backtraces from
   153  	// the binary instead of from this wrapper.
   154  	quit := make(chan os.Signal, 1)
   155  	signal.Notify(quit, syscall.SIGQUIT)
   156  	go func() {
   157  		for range quit {
   158  			// We don't have the PID of the running process; use the
   159  			// binary name instead.
   160  			adb("exec-out", "killall -QUIT "+binName)
   161  		}
   162  	}()
   163  	// In light of
   164  	// https://code.google.com/p/android/issues/detail?id=3254
   165  	// dont trust the exitcode of adb. Instead, append the exitcode to
   166  	// the output and parse it from there.
   167  	const exitstr = "exitcode="
   168  	cmd := `export TMPDIR="` + deviceGotmp + `"` +
   169  		`; export GOROOT="` + deviceGoroot + `"` +
   170  		`; export GOPATH="` + deviceGopath + `"` +
   171  		`; export CGO_ENABLED=0` +
   172  		`; export GOPROXY=` + os.Getenv("GOPROXY") +
   173  		`; export GOCACHE="` + deviceRoot + `/gocache"` +
   174  		`; export PATH=$PATH:"` + deviceGoroot + `/bin"` +
   175  		`; cd "` + deviceCwd + `"` +
   176  		"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
   177  		"; echo -n " + exitstr + "$?"
   178  	output, err := run("exec-out", cmd)
   179  	signal.Reset(syscall.SIGQUIT)
   180  	close(quit)
   181  	if err != nil {
   182  		return 0, err
   183  	}
   184  
   185  	exitIdx := strings.LastIndex(output, exitstr)
   186  	if exitIdx == -1 {
   187  		return 0, fmt.Errorf("no exit code: %q", output)
   188  	}
   189  	code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
   190  	if err != nil {
   191  		return 0, fmt.Errorf("bad exit code: %v", err)
   192  	}
   193  	return code, nil
   194  }
   195  
   196  // subdir determines the package based on the current working directory,
   197  // and returns the path to the package source relative to $GOROOT (or $GOPATH).
   198  func subdir() (pkgpath string, underGoRoot bool, err error) {
   199  	cwd, err := os.Getwd()
   200  	if err != nil {
   201  		return "", false, err
   202  	}
   203  	cwd, err = filepath.EvalSymlinks(cwd)
   204  	if err != nil {
   205  		return "", false, err
   206  	}
   207  	goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
   208  	if err != nil {
   209  		return "", false, err
   210  	}
   211  	if subdir, err := filepath.Rel(goroot, cwd); err == nil {
   212  		if !strings.Contains(subdir, "..") {
   213  			return subdir, true, nil
   214  		}
   215  	}
   216  
   217  	for _, p := range filepath.SplitList(build.Default.GOPATH) {
   218  		pabs, err := filepath.EvalSymlinks(p)
   219  		if err != nil {
   220  			return "", false, err
   221  		}
   222  		if subdir, err := filepath.Rel(pabs, cwd); err == nil {
   223  			if !strings.Contains(subdir, "..") {
   224  				return subdir, false, nil
   225  			}
   226  		}
   227  	}
   228  	return "", false, fmt.Errorf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)",
   229  		cwd, runtime.GOROOT(), build.Default.GOPATH)
   230  }
   231  
   232  // adbCopyTree copies testdata, go.mod, go.sum files from subdir
   233  // and from parent directories all the way up to the root of subdir.
   234  // go.mod and go.sum files are needed for the go tool modules queries,
   235  // and the testdata directories for tests.  It is common for tests to
   236  // reach out into testdata from parent packages.
   237  func adbCopyTree(deviceCwd, subdir string) error {
   238  	dir := ""
   239  	for {
   240  		for _, path := range []string{"testdata", "go.mod", "go.sum"} {
   241  			path := filepath.Join(dir, path)
   242  			if _, err := os.Stat(path); err != nil {
   243  				continue
   244  			}
   245  			devicePath := filepath.Join(deviceCwd, dir)
   246  			if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
   247  				return err
   248  			}
   249  			if err := adb("push", path, devicePath); err != nil {
   250  				return err
   251  			}
   252  		}
   253  		if subdir == "." {
   254  			break
   255  		}
   256  		subdir = filepath.Dir(subdir)
   257  		dir = filepath.Join(dir, "..")
   258  	}
   259  	return nil
   260  }
   261  
   262  // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
   263  // and temporary data. Then, it copies relevant parts of GOROOT to the device,
   264  // including the go tool built for android.
   265  // A lock file ensures this only happens once, even with concurrent exec
   266  // wrappers.
   267  func adbCopyGoroot() error {
   268  	// Also known by cmd/dist. The bootstrap command deletes the file.
   269  	statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
   270  	stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	defer stat.Close()
   275  	// Serialize check and copying.
   276  	if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
   277  		return err
   278  	}
   279  	s, err := io.ReadAll(stat)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	if string(s) == "done" {
   284  		return nil
   285  	}
   286  	// Delete GOROOT, GOPATH and any leftover test data.
   287  	if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
   288  		return err
   289  	}
   290  	deviceBin := filepath.Join(deviceGoroot, "bin")
   291  	if err := adb("exec-out", "mkdir", "-p", deviceBin); err != nil {
   292  		return err
   293  	}
   294  	goroot := runtime.GOROOT()
   295  	// Build go for android.
   296  	goCmd := filepath.Join(goroot, "bin", "go")
   297  	tmpGo, err := os.CreateTemp("", "go_android_exec-cmd-go-*")
   298  	if err != nil {
   299  		return err
   300  	}
   301  	tmpGo.Close()
   302  	defer os.Remove(tmpGo.Name())
   303  
   304  	if out, err := exec.Command(goCmd, "build", "-o", tmpGo.Name(), "cmd/go").CombinedOutput(); err != nil {
   305  		return fmt.Errorf("failed to build go tool for device: %s\n%v", out, err)
   306  	}
   307  	deviceGo := filepath.Join(deviceBin, "go")
   308  	if err := adb("push", tmpGo.Name(), deviceGo); err != nil {
   309  		return err
   310  	}
   311  	for _, dir := range []string{"src", "test", "lib", "api"} {
   312  		if err := adb("push", filepath.Join(goroot, dir), filepath.Join(deviceGoroot)); err != nil {
   313  			return err
   314  		}
   315  	}
   316  
   317  	// Copy only the relevant from pkg.
   318  	if err := adb("exec-out", "mkdir", "-p", filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
   319  		return err
   320  	}
   321  	if err := adb("push", filepath.Join(goroot, "pkg", "include"), filepath.Join(deviceGoroot, "pkg")); err != nil {
   322  		return err
   323  	}
   324  	runtimea, err := exec.Command(goCmd, "list", "-f", "{{.Target}}", "runtime").Output()
   325  	pkgdir := filepath.Dir(string(runtimea))
   326  	if pkgdir == "" {
   327  		return errors.New("could not find android pkg dir")
   328  	}
   329  	if err := adb("push", pkgdir, filepath.Join(deviceGoroot, "pkg")); err != nil {
   330  		return err
   331  	}
   332  	tooldir := filepath.Join(goroot, "pkg", "tool", filepath.Base(pkgdir))
   333  	if err := adb("push", tooldir, filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
   334  		return err
   335  	}
   336  
   337  	if _, err := stat.Write([]byte("done")); err != nil {
   338  		return err
   339  	}
   340  	return nil
   341  }
   342  

View as plain text