Source file
misc/ios/go_ios_exec.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "fmt"
11 "go/build"
12 "log"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "runtime"
17 "strings"
18 "syscall"
19 )
20
21 const debug = false
22
23 var tmpdir string
24
25 var (
26 devID string
27 appID string
28 teamID string
29 bundleID string
30 deviceID string
31 )
32
33
34
35
36 var lock *os.File
37
38 func main() {
39 log.SetFlags(0)
40 log.SetPrefix("go_ios_exec: ")
41 if debug {
42 log.Println(strings.Join(os.Args, " "))
43 }
44 if len(os.Args) < 2 {
45 log.Fatal("usage: go_ios_exec a.out")
46 }
47
48
49 bundleID = "golang.gotest"
50
51 exitCode, err := runMain()
52 if err != nil {
53 log.Fatalf("%v\n", err)
54 }
55 os.Exit(exitCode)
56 }
57
58 func runMain() (int, error) {
59 var err error
60 tmpdir, err = os.MkdirTemp("", "go_ios_exec_")
61 if err != nil {
62 return 1, err
63 }
64 if !debug {
65 defer os.RemoveAll(tmpdir)
66 }
67
68 appdir := filepath.Join(tmpdir, "gotest.app")
69 os.RemoveAll(appdir)
70
71 if err := assembleApp(appdir, os.Args[1]); err != nil {
72 return 1, err
73 }
74
75
76
77
78
79
80
81 lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock")
82 lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)
83 if err != nil {
84 return 1, err
85 }
86 if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
87 return 1, err
88 }
89
90 err = runOnSimulator(appdir)
91 if err != nil {
92 return 1, err
93 }
94 return 0, nil
95 }
96
97 func runOnSimulator(appdir string) error {
98 if err := installSimulator(appdir); err != nil {
99 return err
100 }
101
102 return runSimulator(appdir, bundleID, os.Args[2:])
103 }
104
105 func assembleApp(appdir, bin string) error {
106 if err := os.MkdirAll(appdir, 0755); err != nil {
107 return err
108 }
109
110 if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
111 return err
112 }
113
114 pkgpath, err := copyLocalData(appdir)
115 if err != nil {
116 return err
117 }
118
119 entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
120 if err := os.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil {
121 return err
122 }
123 if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {
124 return err
125 }
126 if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
127 return err
128 }
129 return nil
130 }
131
132 func installSimulator(appdir string) error {
133 cmd := exec.Command(
134 "xcrun", "simctl", "install",
135 "booted",
136 appdir,
137 )
138 if out, err := cmd.CombinedOutput(); err != nil {
139 os.Stderr.Write(out)
140 return fmt.Errorf("xcrun simctl install booted %q: %v", appdir, err)
141 }
142 return nil
143 }
144
145 func runSimulator(appdir, bundleID string, args []string) error {
146 xcrunArgs := []string{"simctl", "spawn",
147 "booted",
148 appdir + "/gotest",
149 }
150 xcrunArgs = append(xcrunArgs, args...)
151 cmd := exec.Command("xcrun", xcrunArgs...)
152 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
153 err := cmd.Run()
154 if err != nil {
155 return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err)
156 }
157
158 return nil
159 }
160
161 func copyLocalDir(dst, src string) error {
162 if err := os.Mkdir(dst, 0755); err != nil {
163 return err
164 }
165
166 d, err := os.Open(src)
167 if err != nil {
168 return err
169 }
170 defer d.Close()
171 fi, err := d.Readdir(-1)
172 if err != nil {
173 return err
174 }
175
176 for _, f := range fi {
177 if f.IsDir() {
178 if f.Name() == "testdata" {
179 if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
180 return err
181 }
182 }
183 continue
184 }
185 if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
186 return err
187 }
188 }
189 return nil
190 }
191
192 func cp(dst, src string) error {
193 out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()
194 if err != nil {
195 os.Stderr.Write(out)
196 }
197 return err
198 }
199
200 func copyLocalData(dstbase string) (pkgpath string, err error) {
201 cwd, err := os.Getwd()
202 if err != nil {
203 return "", err
204 }
205
206 finalPkgpath, underGoRoot, err := subdir()
207 if err != nil {
208 return "", err
209 }
210 cwd = strings.TrimSuffix(cwd, finalPkgpath)
211
212
213
214 pkgpath = ""
215 for _, element := range strings.Split(finalPkgpath, string(filepath.Separator)) {
216 if debug {
217 log.Printf("copying %s", pkgpath)
218 }
219 pkgpath = filepath.Join(pkgpath, element)
220 dst := filepath.Join(dstbase, pkgpath)
221 src := filepath.Join(cwd, pkgpath)
222 if err := copyLocalDir(dst, src); err != nil {
223 return "", err
224 }
225 }
226
227 if underGoRoot {
228
229
230
231
232
233
234 err := cp(
235 filepath.Join(dstbase, pkgpath),
236 filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),
237 )
238 if err != nil {
239 return "", err
240 }
241
242
243 runtimePath := filepath.Join(dstbase, "src", "runtime")
244 if err := os.MkdirAll(runtimePath, 0755); err != nil {
245 return "", err
246 }
247 err = cp(
248 filepath.Join(runtimePath, "textflag.h"),
249 filepath.Join(cwd, "src", "runtime", "textflag.h"),
250 )
251 if err != nil {
252 return "", err
253 }
254 }
255
256 return finalPkgpath, nil
257 }
258
259
260
261 func subdir() (pkgpath string, underGoRoot bool, err error) {
262 cwd, err := os.Getwd()
263 if err != nil {
264 return "", false, err
265 }
266 cwd, err = filepath.EvalSymlinks(cwd)
267 if err != nil {
268 log.Fatal(err)
269 }
270 goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
271 if err != nil {
272 return "", false, err
273 }
274 if strings.HasPrefix(cwd, goroot) {
275 subdir, err := filepath.Rel(goroot, cwd)
276 if err != nil {
277 return "", false, err
278 }
279 return subdir, true, nil
280 }
281
282 for _, p := range filepath.SplitList(build.Default.GOPATH) {
283 pabs, err := filepath.EvalSymlinks(p)
284 if err != nil {
285 return "", false, err
286 }
287 if !strings.HasPrefix(cwd, pabs) {
288 continue
289 }
290 subdir, err := filepath.Rel(pabs, cwd)
291 if err == nil {
292 return subdir, false, nil
293 }
294 }
295 return "", false, fmt.Errorf(
296 "working directory %q is not in either GOROOT(%q) or GOPATH(%q)",
297 cwd,
298 runtime.GOROOT(),
299 build.Default.GOPATH,
300 )
301 }
302
303 func infoPlist(pkgpath string) string {
304 return `<?xml version="1.0" encoding="UTF-8"?>
305 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
306 <plist version="1.0">
307 <dict>
308 <key>CFBundleName</key><string>golang.gotest</string>
309 <key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>
310 <key>CFBundleExecutable</key><string>gotest</string>
311 <key>CFBundleVersion</key><string>1.0</string>
312 <key>CFBundleShortVersionString</key><string>1.0</string>
313 <key>CFBundleIdentifier</key><string>` + bundleID + `</string>
314 <key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>
315 <key>LSRequiresIPhoneOS</key><true/>
316 <key>CFBundleDisplayName</key><string>gotest</string>
317 <key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string>
318 </dict>
319 </plist>
320 `
321 }
322
323 func entitlementsPlist() string {
324 return `<?xml version="1.0" encoding="UTF-8"?>
325 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
326 <plist version="1.0">
327 <dict>
328 <key>keychain-access-groups</key>
329 <array><string>` + appID + `</string></array>
330 <key>get-task-allow</key>
331 <true/>
332 <key>application-identifier</key>
333 <string>` + appID + `</string>
334 <key>com.apple.developer.team-identifier</key>
335 <string>` + teamID + `</string>
336 </dict>
337 </plist>
338 `
339 }
340
341 const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>
342 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
343 <plist version="1.0">
344 <dict>
345 <key>rules</key>
346 <dict>
347 <key>.*</key>
348 <true/>
349 <key>Info.plist</key>
350 <dict>
351 <key>omit</key>
352 <true/>
353 <key>weight</key>
354 <integer>10</integer>
355 </dict>
356 <key>ResourceRules.plist</key>
357 <dict>
358 <key>omit</key>
359 <true/>
360 <key>weight</key>
361 <integer>100</integer>
362 </dict>
363 </dict>
364 </dict>
365 </plist>
366 `
367
View as plain text