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