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