github.com/MangoDowner/go-gm@v0.0.0-20180818020936-8baa2bd4408c/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 }