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