2016-04-10 15:32:26 -06:00
|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
2014-07-09 04:56:49 -06:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2019-02-21 07:20:42 -07:00
|
|
|
// +build ignore
|
|
|
|
|
2014-07-09 04:56:49 -06:00
|
|
|
// 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"
|
2015-01-15 14:47:41 -07:00
|
|
|
"go/build"
|
2014-07-09 04:56:49 -06:00
|
|
|
"io"
|
2019-02-24 07:18:02 -07:00
|
|
|
"io/ioutil"
|
2014-07-09 04:56:49 -06:00
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2018-05-23 08:31:36 -06:00
|
|
|
"os/signal"
|
2014-07-09 04:56:49 -06:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2018-05-23 08:31:36 -06:00
|
|
|
"syscall"
|
2014-07-09 04:56:49 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
func run(args ...string) string {
|
2018-01-20 11:56:51 -07:00
|
|
|
if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
|
|
|
|
args = append(strings.Split(flags, " "), args...)
|
|
|
|
}
|
2014-07-09 04:56:49 -06:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
cmd := exec.Command("adb", args...)
|
|
|
|
cmd.Stdout = io.MultiWriter(os.Stdout, buf)
|
2017-05-01 12:35:08 -06:00
|
|
|
// 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}
|
2014-07-09 04:56:49 -06:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2015-01-15 14:47:41 -07:00
|
|
|
const (
|
2019-03-06 04:53:56 -07:00
|
|
|
deviceRoot = "/data/local/tmp/go_exec_android"
|
|
|
|
deviceGoroot = deviceRoot + "/goroot"
|
2015-01-15 14:47:41 -07:00
|
|
|
)
|
|
|
|
|
2014-07-09 04:56:49 -06:00
|
|
|
func main() {
|
|
|
|
log.SetFlags(0)
|
|
|
|
log.SetPrefix("go_android_exec: ")
|
|
|
|
|
2019-02-25 02:52:42 -07:00
|
|
|
// Concurrent use of adb is flaky, so serialize adb commands.
|
|
|
|
// See https://github.com/golang/go/issues/23795 or
|
|
|
|
// https://issuetracker.google.com/issues/73230216.
|
|
|
|
lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
|
|
|
|
lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer lock.Close()
|
|
|
|
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-03-01 04:08:00 -07:00
|
|
|
// In case we're booting a device or emulator alongside all.bash, wait for
|
|
|
|
// it to be ready. adb wait-for-device is not enough, we have to
|
2019-02-24 08:46:23 -07:00
|
|
|
// wait for sys.boot_completed.
|
2019-03-02 05:07:54 -07:00
|
|
|
run("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;")
|
2019-02-24 08:46:23 -07:00
|
|
|
|
2019-03-06 04:53:56 -07:00
|
|
|
// Done once per make.bash.
|
|
|
|
adbCopyGoroot()
|
|
|
|
|
2015-01-15 14:47:41 -07:00
|
|
|
// Prepare a temporary directory that will be cleaned up at the end.
|
2019-03-06 04:53:56 -07:00
|
|
|
// Binary names can conflict.
|
|
|
|
// E.g. template.test from the {html,text}/template packages.
|
|
|
|
binName := filepath.Base(os.Args[1])
|
|
|
|
deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
|
|
|
|
deviceGopath := deviceGotmp + "/gopath"
|
|
|
|
defer run("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
|
2015-01-15 14:47:41 -07:00
|
|
|
|
|
|
|
// Determine the package by examining the current working
|
2014-07-09 04:56:49 -06:00
|
|
|
// directory, which will look something like
|
2015-01-15 14:47:41 -07:00
|
|
|
// "$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()
|
2019-03-06 04:53:56 -07:00
|
|
|
deviceCwd := filepath.Join(deviceGopath, subdir)
|
|
|
|
if inGoRoot {
|
|
|
|
deviceCwd = filepath.Join(deviceGoroot, subdir)
|
2019-02-24 07:18:02 -07:00
|
|
|
} else {
|
2019-03-06 04:53:56 -07:00
|
|
|
run("exec-out", "mkdir", "-p", deviceCwd)
|
|
|
|
adbCopyTestdata(deviceCwd, subdir)
|
|
|
|
|
|
|
|
// Copy .go files from the package.
|
|
|
|
goFiles, err := filepath.Glob("*.go")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(goFiles) > 0 {
|
|
|
|
args := append(append([]string{"push"}, goFiles...), deviceCwd)
|
|
|
|
run(args...)
|
|
|
|
}
|
2014-07-09 04:56:49 -06:00
|
|
|
}
|
|
|
|
|
2018-05-23 08:31:36 -06:00
|
|
|
deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
|
2019-02-25 02:52:42 -07:00
|
|
|
run("push", os.Args[1], deviceBin)
|
2014-07-09 04:56:49 -06:00
|
|
|
|
2018-05-23 08:31:36 -06:00
|
|
|
// 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.
|
2019-03-02 05:07:54 -07:00
|
|
|
run("exec-out", "killall -QUIT "+binName)
|
2018-05-23 08:31:36 -06:00
|
|
|
}
|
|
|
|
}()
|
2019-03-02 05:07:54 -07:00
|
|
|
// In light of
|
2014-07-09 04:56:49 -06:00
|
|
|
// https://code.google.com/p/android/issues/detail?id=3254
|
2019-03-02 05:07:54 -07:00
|
|
|
// dont trust the exitcode of adb. Instead, append the exitcode to
|
|
|
|
// the output and parse it from there.
|
2014-07-09 04:56:49 -06:00
|
|
|
const exitstr = "exitcode="
|
2015-01-15 14:47:41 -07:00
|
|
|
cmd := `export TMPDIR="` + deviceGotmp + `"` +
|
2014-07-09 04:56:49 -06:00
|
|
|
`; export GOROOT="` + deviceGoroot + `"` +
|
2015-01-15 14:47:41 -07:00
|
|
|
`; export GOPATH="` + deviceGopath + `"` +
|
2019-03-06 04:53:56 -07:00
|
|
|
`; export CGO_ENABLED=0` +
|
|
|
|
`; export GOCACHE="` + deviceRoot + `/gocache"` +
|
|
|
|
`; export PATH=$PATH:"` + deviceGoroot + `/bin"` +
|
2015-01-15 14:47:41 -07:00
|
|
|
`; cd "` + deviceCwd + `"` +
|
2014-07-09 04:56:49 -06:00
|
|
|
"; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
|
|
|
|
"; echo -n " + exitstr + "$?"
|
2019-03-02 05:07:54 -07:00
|
|
|
output := run("exec-out", cmd)
|
2018-05-23 08:31:36 -06:00
|
|
|
signal.Reset(syscall.SIGQUIT)
|
|
|
|
close(quit)
|
2015-01-15 14:47:41 -07:00
|
|
|
|
2016-06-03 03:41:26 -06:00
|
|
|
exitIdx := strings.LastIndex(output, exitstr)
|
|
|
|
if exitIdx == -1 {
|
2014-07-09 04:56:49 -06:00
|
|
|
log.Fatalf("no exit code: %q", output)
|
|
|
|
}
|
2016-06-03 03:41:26 -06:00
|
|
|
code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
|
2014-07-09 04:56:49 -06:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("bad exit code: %v", err)
|
|
|
|
}
|
|
|
|
os.Exit(code)
|
|
|
|
}
|
2015-01-15 14:47:41 -07:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2019-03-01 00:25:35 -07:00
|
|
|
cwd, err = filepath.EvalSymlinks(cwd)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2019-02-28 17:03:20 -07:00
|
|
|
goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(cwd, goroot) {
|
|
|
|
subdir, err := filepath.Rel(goroot, cwd)
|
2015-01-15 14:47:41 -07:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
return subdir, true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
2019-02-28 17:03:20 -07:00
|
|
|
pabs, err := filepath.EvalSymlinks(p)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(cwd, pabs) {
|
2015-01-15 14:47:41 -07:00
|
|
|
continue
|
|
|
|
}
|
2019-02-28 17:03:20 -07:00
|
|
|
subdir, err := filepath.Rel(pabs, cwd)
|
2015-01-15 14:47:41 -07:00
|
|
|
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
|
|
|
|
}
|
2019-02-24 07:18:02 -07:00
|
|
|
|
2019-03-06 04:53:56 -07:00
|
|
|
// adbCopyTestdata copies testdata directories from subdir to deviceCwd
|
|
|
|
// on the device.
|
|
|
|
// It is common for tests to reach out into testdata from parent
|
|
|
|
// packages, so copy testdata directories all the way up to the root
|
|
|
|
// of subdir.
|
|
|
|
func adbCopyTestdata(deviceCwd, subdir string) {
|
|
|
|
dir := ""
|
|
|
|
for {
|
|
|
|
testdata := filepath.Join(dir, "testdata")
|
|
|
|
if _, err := os.Stat(testdata); err == nil {
|
|
|
|
devicePath := filepath.Join(deviceCwd, dir)
|
|
|
|
run("exec-out", "mkdir", "-p", devicePath)
|
|
|
|
run("push", testdata, devicePath)
|
|
|
|
}
|
|
|
|
if subdir == "." {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
subdir = filepath.Dir(subdir)
|
|
|
|
dir = filepath.Join(dir, "..")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
|
|
|
|
// and temporary data. Then, it copies relevant parts of GOROOT to the device,
|
|
|
|
// including the go tool built for android.
|
|
|
|
// A lock file ensures this only happens once, even with concurrent exec
|
|
|
|
// wrappers.
|
|
|
|
func adbCopyGoroot() {
|
2019-02-24 07:18:02 -07:00
|
|
|
// Also known by cmd/dist. The bootstrap command deletes the file.
|
|
|
|
statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
|
|
|
|
stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer stat.Close()
|
2019-03-06 04:53:56 -07:00
|
|
|
// Serialize check and copying.
|
2019-02-24 07:18:02 -07:00
|
|
|
if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
s, err := ioutil.ReadAll(stat)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if string(s) == "done" {
|
|
|
|
return
|
|
|
|
}
|
2019-03-06 04:53:56 -07:00
|
|
|
// Delete GOROOT, GOPATH and any leftover test data.
|
|
|
|
run("exec-out", "rm", "-rf", deviceRoot)
|
|
|
|
deviceBin := filepath.Join(deviceGoroot, "bin")
|
|
|
|
run("exec-out", "mkdir", "-p", deviceBin)
|
2019-02-24 07:18:02 -07:00
|
|
|
goroot := runtime.GOROOT()
|
2019-03-06 04:53:56 -07:00
|
|
|
// Build go for android.
|
2019-02-24 07:18:02 -07:00
|
|
|
goCmd := filepath.Join(goroot, "bin", "go")
|
2019-03-06 04:53:56 -07:00
|
|
|
tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*")
|
2019-02-24 07:18:02 -07:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2019-03-06 04:53:56 -07:00
|
|
|
tmpGo.Close()
|
|
|
|
defer os.Remove(tmpGo.Name())
|
|
|
|
|
|
|
|
if out, err := exec.Command(goCmd, "build", "-o", tmpGo.Name(), "cmd/go").CombinedOutput(); err != nil {
|
|
|
|
log.Fatalf("failed to build go tool for device: %s\n%v", out, err)
|
2019-02-24 07:18:02 -07:00
|
|
|
}
|
2019-03-06 04:53:56 -07:00
|
|
|
deviceGo := filepath.Join(deviceBin, "go")
|
|
|
|
run("push", tmpGo.Name(), deviceGo)
|
|
|
|
for _, dir := range []string{"pkg", "src", "test", "lib", "api"} {
|
|
|
|
run("push", filepath.Join(goroot, dir), filepath.Join(deviceGoroot))
|
2019-02-24 07:18:02 -07:00
|
|
|
}
|
2019-03-06 04:53:56 -07:00
|
|
|
|
2019-02-24 07:18:02 -07:00
|
|
|
if _, err := stat.Write([]byte("done")); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|