github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/misc/android/go_android_exec.go (about)

     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 program can be used as go_android_GOARCH_exec by the Go tool.
     6  // It executes binaries on an android device using adb.
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/build"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"os/signal"
    18  	"path/filepath"
    19  	"runtime"
    20  	"strconv"
    21  	"strings"
    22  	"syscall"
    23  )
    24  
    25  func run(args ...string) string {
    26  	if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
    27  		args = append(strings.Split(flags, " "), args...)
    28  	}
    29  	buf := new(bytes.Buffer)
    30  	cmd := exec.Command("adb", args...)
    31  	cmd.Stdout = io.MultiWriter(os.Stdout, buf)
    32  	// If the adb subprocess somehow hangs, go test will kill this wrapper
    33  	// and wait for our os.Stderr (and os.Stdout) to close as a result.
    34  	// However, if the os.Stderr (or os.Stdout) file descriptors are
    35  	// passed on, the hanging adb subprocess will hold them open and
    36  	// go test will hang forever.
    37  	//
    38  	// Avoid that by wrapping stderr, breaking the short circuit and
    39  	// forcing cmd.Run to use another pipe and goroutine to pass
    40  	// along stderr from adb.
    41  	cmd.Stderr = struct{ io.Writer }{os.Stderr}
    42  	log.Printf("adb %s", strings.Join(args, " "))
    43  	err := cmd.Run()
    44  	if err != nil {
    45  		log.Fatalf("adb %s: %v", strings.Join(args, " "), err)
    46  	}
    47  	return buf.String()
    48  }
    49  
    50  const (
    51  	// Directory structure on the target device androidtest.bash assumes.
    52  	deviceGoroot = "/data/local/tmp/goroot"
    53  	deviceGopath = "/data/local/tmp/gopath"
    54  )
    55  
    56  func main() {
    57  	log.SetFlags(0)
    58  	log.SetPrefix("go_android_exec: ")
    59  
    60  	// Prepare a temporary directory that will be cleaned up at the end.
    61  	deviceGotmp := fmt.Sprintf("/data/local/tmp/%s-%d",
    62  		filepath.Base(os.Args[1]), os.Getpid())
    63  	run("shell", "mkdir", "-p", deviceGotmp)
    64  
    65  	// Determine the package by examining the current working
    66  	// directory, which will look something like
    67  	// "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
    68  	// We extract everything after the $GOROOT or $GOPATH to run on the
    69  	// same relative directory on the target device.
    70  	subdir, inGoRoot := subdir()
    71  	deviceCwd := filepath.Join(deviceGoroot, subdir)
    72  	if !inGoRoot {
    73  		deviceCwd = filepath.Join(deviceGopath, subdir)
    74  	}
    75  
    76  	// Binary names can conflict.
    77  	// E.g. template.test from the {html,text}/template packages.
    78  	binName := fmt.Sprintf("%s-%d", filepath.Base(os.Args[1]), os.Getpid())
    79  	deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
    80  
    81  	// The push of the binary happens in parallel with other tests.
    82  	// Unfortunately, a simultaneous call to adb shell hold open
    83  	// file descriptors, so it is necessary to push then move to
    84  	// avoid a "text file busy" error on execution.
    85  	// https://code.google.com/p/android/issues/detail?id=65857
    86  	run("push", os.Args[1], deviceBin+"-tmp")
    87  	run("shell", "cp '"+deviceBin+"-tmp' '"+deviceBin+"'")
    88  	run("shell", "rm '"+deviceBin+"-tmp'")
    89  
    90  	// Forward SIGQUIT from the go command to show backtraces from
    91  	// the binary instead of from this wrapper.
    92  	quit := make(chan os.Signal, 1)
    93  	signal.Notify(quit, syscall.SIGQUIT)
    94  	go func() {
    95  		for range quit {
    96  			// We don't have the PID of the running process; use the
    97  			// binary name instead.
    98  			run("shell", "killall -QUIT "+binName)
    99  		}
   100  	}()
   101  	// The adb shell command will return an exit code of 0 regardless
   102  	// of the command run. E.g.
   103  	//      $ adb shell false
   104  	//      $ echo $?
   105  	//      0
   106  	// https://code.google.com/p/android/issues/detail?id=3254
   107  	// So we append the exitcode to the output and parse it from there.
   108  	const exitstr = "exitcode="
   109  	cmd := `export TMPDIR="` + deviceGotmp + `"` +
   110  		`; export GOROOT="` + deviceGoroot + `"` +
   111  		`; export GOPATH="` + deviceGopath + `"` +
   112  		`; cd "` + deviceCwd + `"` +
   113  		"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
   114  		"; echo -n " + exitstr + "$?"
   115  	output := run("shell", cmd)
   116  	signal.Reset(syscall.SIGQUIT)
   117  	close(quit)
   118  
   119  	run("shell", "rm", "-rf", deviceGotmp) // Clean up.
   120  
   121  	exitIdx := strings.LastIndex(output, exitstr)
   122  	if exitIdx == -1 {
   123  		log.Fatalf("no exit code: %q", output)
   124  	}
   125  	code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
   126  	if err != nil {
   127  		log.Fatalf("bad exit code: %v", err)
   128  	}
   129  	os.Exit(code)
   130  }
   131  
   132  // subdir determines the package based on the current working directory,
   133  // and returns the path to the package source relative to $GOROOT (or $GOPATH).
   134  func subdir() (pkgpath string, underGoRoot bool) {
   135  	cwd, err := os.Getwd()
   136  	if err != nil {
   137  		log.Fatal(err)
   138  	}
   139  	if root := runtime.GOROOT(); strings.HasPrefix(cwd, root) {
   140  		subdir, err := filepath.Rel(root, cwd)
   141  		if err != nil {
   142  			log.Fatal(err)
   143  		}
   144  		return subdir, true
   145  	}
   146  
   147  	for _, p := range filepath.SplitList(build.Default.GOPATH) {
   148  		if !strings.HasPrefix(cwd, p) {
   149  			continue
   150  		}
   151  		subdir, err := filepath.Rel(p, cwd)
   152  		if err == nil {
   153  			return subdir, false
   154  		}
   155  	}
   156  	log.Fatalf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)",
   157  		cwd, runtime.GOROOT(), build.Default.GOPATH)
   158  	return "", false
   159  }