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 } 133