| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This program can be used as go_android_GOARCH_exec by the Go tool. |
| // It executes binaries on an android device using adb. |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/build" |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| "os/signal" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "strings" |
| "syscall" |
| ) |
| |
| func run(args ...string) string { |
| if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" { |
| args = append(strings.Split(flags, " "), args...) |
| } |
| buf := new(bytes.Buffer) |
| cmd := exec.Command("adb", args...) |
| cmd.Stdout = io.MultiWriter(os.Stdout, buf) |
| // If the adb subprocess somehow hangs, go test will kill this wrapper |
| // and wait for our os.Stderr (and os.Stdout) to close as a result. |
| // However, if the os.Stderr (or os.Stdout) file descriptors are |
| // passed on, the hanging adb subprocess will hold them open and |
| // go test will hang forever. |
| // |
| // Avoid that by wrapping stderr, breaking the short circuit and |
| // forcing cmd.Run to use another pipe and goroutine to pass |
| // along stderr from adb. |
| cmd.Stderr = struct{ io.Writer }{os.Stderr} |
| log.Printf("adb %s", strings.Join(args, " ")) |
| err := cmd.Run() |
| if err != nil { |
| log.Fatalf("adb %s: %v", strings.Join(args, " "), err) |
| } |
| return buf.String() |
| } |
| |
| const ( |
| // Directory structure on the target device androidtest.bash assumes. |
| deviceGoroot = "/data/local/tmp/goroot" |
| deviceGopath = "/data/local/tmp/gopath" |
| ) |
| |
| func main() { |
| log.SetFlags(0) |
| log.SetPrefix("go_android_exec: ") |
| |
| // Prepare a temporary directory that will be cleaned up at the end. |
| deviceGotmp := fmt.Sprintf("/data/local/tmp/%s-%d", |
| filepath.Base(os.Args[1]), os.Getpid()) |
| run("shell", "mkdir", "-p", deviceGotmp) |
| |
| // Determine the package by examining the current working |
| // directory, which will look something like |
| // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile". |
| // We extract everything after the $GOROOT or $GOPATH to run on the |
| // same relative directory on the target device. |
| subdir, inGoRoot := subdir() |
| deviceCwd := filepath.Join(deviceGoroot, subdir) |
| if !inGoRoot { |
| deviceCwd = filepath.Join(deviceGopath, subdir) |
| } |
| |
| // Binary names can conflict. |
| // E.g. template.test from the {html,text}/template packages. |
| binName := fmt.Sprintf("%s-%d", filepath.Base(os.Args[1]), os.Getpid()) |
| deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName) |
| |
| // The push of the binary happens in parallel with other tests. |
| // Unfortunately, a simultaneous call to adb shell hold open |
| // file descriptors, so it is necessary to push then move to |
| // avoid a "text file busy" error on execution. |
| // https://code.google.com/p/android/issues/detail?id=65857 |
| run("push", os.Args[1], deviceBin+"-tmp") |
| run("shell", "cp '"+deviceBin+"-tmp' '"+deviceBin+"'") |
| run("shell", "rm '"+deviceBin+"-tmp'") |
| |
| // Forward SIGQUIT from the go command to show backtraces from |
| // the binary instead of from this wrapper. |
| quit := make(chan os.Signal, 1) |
| signal.Notify(quit, syscall.SIGQUIT) |
| go func() { |
| for range quit { |
| // We don't have the PID of the running process; use the |
| // binary name instead. |
| run("shell", "killall -QUIT "+binName) |
| } |
| }() |
| // The adb shell command will return an exit code of 0 regardless |
| // of the command run. E.g. |
| // $ adb shell false |
| // $ echo $? |
| // 0 |
| // https://code.google.com/p/android/issues/detail?id=3254 |
| // So we append the exitcode to the output and parse it from there. |
| const exitstr = "exitcode=" |
| cmd := `export TMPDIR="` + deviceGotmp + `"` + |
| `; export GOROOT="` + deviceGoroot + `"` + |
| `; export GOPATH="` + deviceGopath + `"` + |
| `; cd "` + deviceCwd + `"` + |
| "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") + |
| "; echo -n " + exitstr + "$?" |
| output := run("shell", cmd) |
| signal.Reset(syscall.SIGQUIT) |
| close(quit) |
| |
| run("shell", "rm", "-rf", deviceGotmp) // Clean up. |
| |
| exitIdx := strings.LastIndex(output, exitstr) |
| if exitIdx == -1 { |
| log.Fatalf("no exit code: %q", output) |
| } |
| code, err := strconv.Atoi(output[exitIdx+len(exitstr):]) |
| if err != nil { |
| log.Fatalf("bad exit code: %v", err) |
| } |
| os.Exit(code) |
| } |
| |
| // subdir determines the package based on the current working directory, |
| // and returns the path to the package source relative to $GOROOT (or $GOPATH). |
| func subdir() (pkgpath string, underGoRoot bool) { |
| cwd, err := os.Getwd() |
| if err != nil { |
| log.Fatal(err) |
| } |
| if root := runtime.GOROOT(); strings.HasPrefix(cwd, root) { |
| subdir, err := filepath.Rel(root, cwd) |
| if err != nil { |
| log.Fatal(err) |
| } |
| return subdir, true |
| } |
| |
| for _, p := range filepath.SplitList(build.Default.GOPATH) { |
| if !strings.HasPrefix(cwd, p) { |
| continue |
| } |
| subdir, err := filepath.Rel(p, cwd) |
| if err == nil { |
| return subdir, false |
| } |
| } |
| log.Fatalf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)", |
| cwd, runtime.GOROOT(), build.Default.GOPATH) |
| return "", false |
| } |