// 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. // +build ignore // This program can be used as go_android_GOARCH_exec by the Go tool. // It executes binaries on an android device using adb. // This program is supposed to be called by // golang.org/x/mobile/build/androidtest.bash that arranges to copy // the tested repository source tree to the android device and invokes // go test. This program depends on PKG and DEVICEDIR environment variables // to identify the tested repository (e.g. golang.org/x/mobile) and // to find the source directory in the android device. The androidtest.bash // script is responsible for setting the environment variables. package main // This is adopted from golang.org/x/go/misc/android/go_android_exec.go. import ( "bytes" "fmt" "go/build" "io" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "text/template" ) func run(args ...string) string { buf := new(bytes.Buffer) cmd := exec.Command("adb", args...) cmd.Stdout = io.MultiWriter(os.Stdout, buf) cmd.Stderr = 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() } func rel(cwd, pkg string) (string, error) { paths := build.Default.GOPATH for _, p := range filepath.SplitList(paths) { r, err := filepath.Rel(filepath.Join(p, "src", pkg), cwd) if err == nil { return r, nil } } return "", fmt.Errorf("%q is not under GOPATH(%q)", cwd, paths) } func main() { log.SetFlags(0) log.SetPrefix("go_android_exec: ") deviceRoot := "/data/local/tmp/" if v := os.Getenv("DEVICEDIR"); v != "" { deviceRoot = v } pkg := "golang.org/x/mobile" if v := os.Getenv("PKG"); v != "" { pkg = v } // Binary names can conflict. // E.g. template.test from the {html,text}/template packages. binName := filepath.Base(os.Args[1]) deviceBin := fmt.Sprintf("%s/%s-%d", deviceRoot, binName, os.Getpid()) // 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", "-p", os.Args[1], deviceBin+"-tmp") run("shell", "cp '"+deviceBin+"-tmp' '"+deviceBin+"'") run("shell", "rm '"+deviceBin+"-tmp'") cwd, err := os.Getwd() if err != nil { log.Fatal(err) } subdir, err := rel(cwd, pkg) if err != nil { log.Fatal(err) } subdir = filepath.Join(deviceRoot, pkg, subdir) // 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. t := template.Must(template.New("cmd").Parse( `export TMPDIR={{.Root}}/tmp; \ mkdir -p "$TMPDIR"; \ cd "{{.SubDir}}"; \ {{.Bin}} {{.Args}}; \ echo -n {{.ExitStr}}$?`)) var cmd bytes.Buffer const exitstr = "exitcode=" if err := t.Execute(&cmd, struct { Root, SubDir, Bin, Args, ExitStr string }{ Root: deviceRoot, SubDir: subdir, Bin: deviceBin, Args: strings.Join(os.Args[2:], " "), ExitStr: exitstr, }); err != nil { log.Panicf("template error: %v", err) } output := run("shell", cmd.String()) output = output[strings.LastIndex(output, "\n")+1:] if !strings.HasPrefix(output, exitstr) { log.Fatalf("no exit code: %q", output) } code, err := strconv.Atoi(output[len(exitstr):]) if err != nil { log.Fatalf("bad exit code: %v", err) } os.Exit(code) }