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 }