Source file
misc/android/go_android_exec.go
1
2
3
4
5
6
7
8
9
10 package main
11
12 import (
13 "bytes"
14 "errors"
15 "fmt"
16 "go/build"
17 "io"
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
34
35
36
37
38
39
40
41
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
82
83
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
95
96
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
102 if err := adbCopyGoroot(); err != nil {
103 return 0, err
104 }
105
106
107
108
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)
113
114
115
116
117
118
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
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
153
154 quit := make(chan os.Signal, 1)
155 signal.Notify(quit, syscall.SIGQUIT)
156 go func() {
157 for range quit {
158
159
160 adb("exec-out", "killall -QUIT "+binName)
161 }
162 }()
163
164
165
166
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
197
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
233
234
235
236
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
263
264
265
266
267 func adbCopyGoroot() error {
268
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
276 if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
277 return err
278 }
279 s, err := io.ReadAll(stat)
280 if err != nil {
281 return err
282 }
283 if string(s) == "done" {
284 return nil
285 }
286
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
296 goCmd := filepath.Join(goroot, "bin", "go")
297 tmpGo, err := os.CreateTemp("", "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
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 }
342
View as plain text