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