]> Cypherpunks.ru repositories - gostls13.git/blob - misc/android/go_android_exec.go
[dev.cmdgo] codereview.cfg: add config for dev.cmdgo
[gostls13.git] / misc / android / go_android_exec.go
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 // +build ignore
6
7 // This program can be used as go_android_GOARCH_exec by the Go tool.
8 // It executes binaries on an android device using adb.
9 package main
10
11 import (
12         "bytes"
13         "errors"
14         "fmt"
15         "go/build"
16         "io"
17         "io/ioutil"
18         "log"
19         "os"
20         "os/exec"
21         "os/signal"
22         "path/filepath"
23         "runtime"
24         "strconv"
25         "strings"
26         "syscall"
27 )
28
29 func run(args ...string) (string, error) {
30         cmd := adbCmd(args...)
31         buf := new(bytes.Buffer)
32         cmd.Stdout = io.MultiWriter(os.Stdout, buf)
33         // If the adb subprocess somehow hangs, go test will kill this wrapper
34         // and wait for our os.Stderr (and os.Stdout) to close as a result.
35         // However, if the os.Stderr (or os.Stdout) file descriptors are
36         // passed on, the hanging adb subprocess will hold them open and
37         // go test will hang forever.
38         //
39         // Avoid that by wrapping stderr, breaking the short circuit and
40         // forcing cmd.Run to use another pipe and goroutine to pass
41         // along stderr from adb.
42         cmd.Stderr = struct{ io.Writer }{os.Stderr}
43         err := cmd.Run()
44         if err != nil {
45                 return "", fmt.Errorf("adb %s: %v", strings.Join(args, " "), err)
46         }
47         return buf.String(), nil
48 }
49
50 func adb(args ...string) error {
51         if out, err := adbCmd(args...).CombinedOutput(); err != nil {
52                 fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
53                 return err
54         }
55         return nil
56 }
57
58 func adbCmd(args ...string) *exec.Cmd {
59         if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
60                 args = append(strings.Split(flags, " "), args...)
61         }
62         return exec.Command("adb", args...)
63 }
64
65 const (
66         deviceRoot   = "/data/local/tmp/go_android_exec"
67         deviceGoroot = deviceRoot + "/goroot"
68 )
69
70 func main() {
71         log.SetFlags(0)
72         log.SetPrefix("go_android_exec: ")
73         exitCode, err := runMain()
74         if err != nil {
75                 log.Fatal(err)
76         }
77         os.Exit(exitCode)
78 }
79
80 func runMain() (int, error) {
81         // Concurrent use of adb is flaky, so serialize adb commands.
82         // See https://github.com/golang/go/issues/23795 or
83         // https://issuetracker.google.com/issues/73230216.
84         lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
85         lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
86         if err != nil {
87                 return 0, err
88         }
89         defer lock.Close()
90         if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
91                 return 0, err
92         }
93
94         // In case we're booting a device or emulator alongside all.bash, wait for
95         // it to be ready. adb wait-for-device is not enough, we have to
96         // wait for sys.boot_completed.
97         if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
98                 return 0, err
99         }
100
101         // Done once per make.bash.
102         if err := adbCopyGoroot(); err != nil {
103                 return 0, err
104         }
105
106         // Prepare a temporary directory that will be cleaned up at the end.
107         // Binary names can conflict.
108         // E.g. template.test from the {html,text}/template packages.
109         binName := filepath.Base(os.Args[1])
110         deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
111         deviceGopath := deviceGotmp + "/gopath"
112         defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
113
114         // Determine the package by examining the current working
115         // directory, which will look something like
116         // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
117         // We extract everything after the $GOROOT or $GOPATH to run on the
118         // same relative directory on the target device.
119         subdir, inGoRoot, err := subdir()
120         if err != nil {
121                 return 0, err
122         }
123         deviceCwd := filepath.Join(deviceGopath, subdir)
124         if inGoRoot {
125                 deviceCwd = filepath.Join(deviceGoroot, subdir)
126         } else {
127                 if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
128                         return 0, err
129                 }
130                 if err := adbCopyTree(deviceCwd, subdir); err != nil {
131                         return 0, err
132                 }
133
134                 // Copy .go files from the package.
135                 goFiles, err := filepath.Glob("*.go")
136                 if err != nil {
137                         return 0, err
138                 }
139                 if len(goFiles) > 0 {
140                         args := append(append([]string{"push"}, goFiles...), deviceCwd)
141                         if err := adb(args...); err != nil {
142                                 return 0, err
143                         }
144                 }
145         }
146
147         deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
148         if err := adb("push", os.Args[1], deviceBin); err != nil {
149                 return 0, err
150         }
151
152         // Forward SIGQUIT from the go command to show backtraces from
153         // the binary instead of from this wrapper.
154         quit := make(chan os.Signal, 1)
155         signal.Notify(quit, syscall.SIGQUIT)
156         go func() {
157                 for range quit {
158                         // We don't have the PID of the running process; use the
159                         // binary name instead.
160                         adb("exec-out", "killall -QUIT "+binName)
161                 }
162         }()
163         // In light of
164         // https://code.google.com/p/android/issues/detail?id=3254
165         // dont trust the exitcode of adb. Instead, append the exitcode to
166         // the output and parse it from there.
167         const exitstr = "exitcode="
168         cmd := `export TMPDIR="` + deviceGotmp + `"` +
169                 `; export GOROOT="` + deviceGoroot + `"` +
170                 `; export GOPATH="` + deviceGopath + `"` +
171                 `; export CGO_ENABLED=0` +
172                 `; export GOPROXY=` + os.Getenv("GOPROXY") +
173                 `; export GOCACHE="` + deviceRoot + `/gocache"` +
174                 `; export PATH=$PATH:"` + deviceGoroot + `/bin"` +
175                 `; cd "` + deviceCwd + `"` +
176                 "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ") +
177                 "; echo -n " + exitstr + "$?"
178         output, err := run("exec-out", cmd)
179         signal.Reset(syscall.SIGQUIT)
180         close(quit)
181         if err != nil {
182                 return 0, err
183         }
184
185         exitIdx := strings.LastIndex(output, exitstr)
186         if exitIdx == -1 {
187                 return 0, fmt.Errorf("no exit code: %q", output)
188         }
189         code, err := strconv.Atoi(output[exitIdx+len(exitstr):])
190         if err != nil {
191                 return 0, fmt.Errorf("bad exit code: %v", err)
192         }
193         return code, nil
194 }
195
196 // subdir determines the package based on the current working directory,
197 // and returns the path to the package source relative to $GOROOT (or $GOPATH).
198 func subdir() (pkgpath string, underGoRoot bool, err error) {
199         cwd, err := os.Getwd()
200         if err != nil {
201                 return "", false, err
202         }
203         cwd, err = filepath.EvalSymlinks(cwd)
204         if err != nil {
205                 return "", false, err
206         }
207         goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
208         if err != nil {
209                 return "", false, err
210         }
211         if subdir, err := filepath.Rel(goroot, cwd); err == nil {
212                 if !strings.Contains(subdir, "..") {
213                         return subdir, true, nil
214                 }
215         }
216
217         for _, p := range filepath.SplitList(build.Default.GOPATH) {
218                 pabs, err := filepath.EvalSymlinks(p)
219                 if err != nil {
220                         return "", false, err
221                 }
222                 if subdir, err := filepath.Rel(pabs, cwd); err == nil {
223                         if !strings.Contains(subdir, "..") {
224                                 return subdir, false, nil
225                         }
226                 }
227         }
228         return "", false, fmt.Errorf("the current path %q is not in either GOROOT(%q) or GOPATH(%q)",
229                 cwd, runtime.GOROOT(), build.Default.GOPATH)
230 }
231
232 // adbCopyTree copies testdata, go.mod, go.sum files from subdir
233 // and from parent directories all the way up to the root of subdir.
234 // go.mod and go.sum files are needed for the go tool modules queries,
235 // and the testdata directories for tests.  It is common for tests to
236 // reach out into testdata from parent packages.
237 func adbCopyTree(deviceCwd, subdir string) error {
238         dir := ""
239         for {
240                 for _, path := range []string{"testdata", "go.mod", "go.sum"} {
241                         path := filepath.Join(dir, path)
242                         if _, err := os.Stat(path); err != nil {
243                                 continue
244                         }
245                         devicePath := filepath.Join(deviceCwd, dir)
246                         if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
247                                 return err
248                         }
249                         if err := adb("push", path, devicePath); err != nil {
250                                 return err
251                         }
252                 }
253                 if subdir == "." {
254                         break
255                 }
256                 subdir = filepath.Dir(subdir)
257                 dir = filepath.Join(dir, "..")
258         }
259         return nil
260 }
261
262 // adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
263 // and temporary data. Then, it copies relevant parts of GOROOT to the device,
264 // including the go tool built for android.
265 // A lock file ensures this only happens once, even with concurrent exec
266 // wrappers.
267 func adbCopyGoroot() error {
268         // Also known by cmd/dist. The bootstrap command deletes the file.
269         statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
270         stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
271         if err != nil {
272                 return err
273         }
274         defer stat.Close()
275         // Serialize check and copying.
276         if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
277                 return err
278         }
279         s, err := ioutil.ReadAll(stat)
280         if err != nil {
281                 return err
282         }
283         if string(s) == "done" {
284                 return nil
285         }
286         // Delete GOROOT, GOPATH and any leftover test data.
287         if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
288                 return err
289         }
290         deviceBin := filepath.Join(deviceGoroot, "bin")
291         if err := adb("exec-out", "mkdir", "-p", deviceBin); err != nil {
292                 return err
293         }
294         goroot := runtime.GOROOT()
295         // Build go for android.
296         goCmd := filepath.Join(goroot, "bin", "go")
297         tmpGo, err := ioutil.TempFile("", "go_android_exec-cmd-go-*")
298         if err != nil {
299                 return err
300         }
301         tmpGo.Close()
302         defer os.Remove(tmpGo.Name())
303
304         if out, err := exec.Command(goCmd, "build", "-o", tmpGo.Name(), "cmd/go").CombinedOutput(); err != nil {
305                 return fmt.Errorf("failed to build go tool for device: %s\n%v", out, err)
306         }
307         deviceGo := filepath.Join(deviceBin, "go")
308         if err := adb("push", tmpGo.Name(), deviceGo); err != nil {
309                 return err
310         }
311         for _, dir := range []string{"src", "test", "lib", "api"} {
312                 if err := adb("push", filepath.Join(goroot, dir), filepath.Join(deviceGoroot)); err != nil {
313                         return err
314                 }
315         }
316
317         // Copy only the relevant from pkg.
318         if err := adb("exec-out", "mkdir", "-p", filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
319                 return err
320         }
321         if err := adb("push", filepath.Join(goroot, "pkg", "include"), filepath.Join(deviceGoroot, "pkg")); err != nil {
322                 return err
323         }
324         runtimea, err := exec.Command(goCmd, "list", "-f", "{{.Target}}", "runtime").Output()
325         pkgdir := filepath.Dir(string(runtimea))
326         if pkgdir == "" {
327                 return errors.New("could not find android pkg dir")
328         }
329         if err := adb("push", pkgdir, filepath.Join(deviceGoroot, "pkg")); err != nil {
330                 return err
331         }
332         tooldir := filepath.Join(goroot, "pkg", "tool", filepath.Base(pkgdir))
333         if err := adb("push", tooldir, filepath.Join(deviceGoroot, "pkg", "tool")); err != nil {
334                 return err
335         }
336
337         if _, err := stat.Write([]byte("done")); err != nil {
338                 return err
339         }
340         return nil
341 }