Home | History | Annotate | Download | only in android
      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 	exitIdx := strings.LastIndex(output, exitstr)
     95 	if exitIdx == -1 {
     96 		log.Fatalf("no exit code: %q", output)
     97 	}
     98 	code, err := strconv.Atoi(output[exitIdx+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