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