ranger-compiler 3.1.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2527 -0
- package/LICENSE +28 -0
- package/LICENSE-MIT +21 -0
- package/README.md +1650 -1875
- package/dist/Lang.rgr +10946 -5662
- package/dist/README.md +3 -2
- package/dist/api.d.ts +2023 -61
- package/dist/api.js +68930 -27355
- package/dist/lib/ACEEditor.rgr +2 -0
- package/dist/lib/Ajax.rgr +2 -0
- package/dist/lib/CmdParams.rgr +2 -0
- package/dist/lib/Crypto.rgr +2 -0
- package/dist/lib/DOMLib.rgr +2 -0
- package/dist/lib/Engine3D.rgr +2 -0
- package/dist/lib/ImmutableVector.rgr +2 -0
- package/dist/lib/IndexedDB.rgr +2 -0
- package/dist/lib/IsoDate/DateMath.rgr +2 -0
- package/dist/lib/IsoDate/IsoCalendar.rgr +2 -0
- package/dist/lib/IsoDate/IsoDateParse.rgr +2 -0
- package/dist/lib/IsoDateLib.rgr +2 -0
- package/dist/lib/JSON.rgr +4906 -48
- package/dist/lib/JinxProcess.rgr +2 -0
- package/dist/lib/RangerProcess.rgr +2 -0
- package/dist/lib/Regex/RegexMatch.rgr +2 -0
- package/dist/lib/RegexLib.rgr +2 -0
- package/dist/lib/SQL.rgr +2 -0
- package/dist/lib/ServiceLib.rgr +2 -0
- package/dist/lib/Shell.rgr +326 -0
- package/dist/lib/Storage.rgr +2 -0
- package/dist/lib/Time.rgr +2 -0
- package/dist/lib/Timers.rgr +2 -0
- package/dist/lib/TypedArrays.rgr +2 -0
- package/dist/lib/ViewLib.rgr +2 -0
- package/dist/lib/WebLib.rgr +2 -0
- package/dist/lib/WebServerLib.rgr +2 -0
- package/dist/lib/apple/AppleAppBuilder.rgr +572 -0
- package/dist/lib/apple/AppleAppSpec.rgr +201 -0
- package/dist/lib/apple/AppleDevice.rgr +295 -0
- package/dist/lib/apple/AppleDeviceDoctor.rgr +453 -0
- package/dist/lib/apple/AppleSigning.rgr +379 -0
- package/dist/lib/apple/AppleSimulator.rgr +248 -0
- package/dist/lib/apple/AppleTarget.rgr +185 -0
- package/dist/lib/apple/AppleToolchain.rgr +458 -0
- package/dist/lib/apple/README.md +311 -0
- package/dist/lib/apple/apple_test.rgr +669 -0
- package/dist/lib/core/README.md +251 -0
- package/dist/lib/core/RgBase.rgr +313 -0
- package/dist/lib/core/RgNum.rgr +653 -0
- package/dist/lib/core/RgText.rgr +680 -0
- package/dist/lib/core/RgU32.rgr +309 -0
- package/dist/lib/ranger-dir.rgr +27 -5
- package/dist/lib/shell_test.rgr +193 -0
- package/dist/lib/stdlib.rgr +1176 -665
- package/dist/lib/stdops.rgr +2 -0
- package/dist/package.json +1 -1
- package/dist/rgrc.js +73013 -37819
- package/dist/stdops.rgr +2 -0
- package/package.json +1121 -205
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; =============================================================================
|
|
4
|
+
; AppleAppBuilder.rgr -- Swift sources in, a runnable .app out. No Xcode.
|
|
5
|
+
; =============================================================================
|
|
6
|
+
;
|
|
7
|
+
; This is the whole pipeline Xcode runs when you press Play, minus the parts
|
|
8
|
+
; that only matter to a project file:
|
|
9
|
+
;
|
|
10
|
+
; 1. is there a toolchain, and does it have this platform's SDK
|
|
11
|
+
; 2. resolve the SDK path and the triple
|
|
12
|
+
; 3. make the bundle directory
|
|
13
|
+
; 4. write Info.plist, and VALIDATE it -- an unparseable plist does not fail
|
|
14
|
+
; the install with a message, it fails it with a shrug
|
|
15
|
+
; 5. compile every Swift source into one executable, inside the bundle,
|
|
16
|
+
; named exactly what CFBundleExecutable says
|
|
17
|
+
; 6. copy the resources in
|
|
18
|
+
; 7. sign it -- ad hoc for a simulator, which needs no account and no profile
|
|
19
|
+
;
|
|
20
|
+
; and then, if asked, boot a simulator, install and launch.
|
|
21
|
+
;
|
|
22
|
+
; What is deliberately NOT here: an .xcodeproj, xcodebuild, a scheme, a
|
|
23
|
+
; workspace, a derived data directory. swiftc can build an iOS executable
|
|
24
|
+
; directly, and everything above it is project bookkeeping that a driver
|
|
25
|
+
; already knows the answers to.
|
|
26
|
+
;
|
|
27
|
+
; Every step goes through `Shell`, so setting `dryRun` gives the whole plan --
|
|
28
|
+
; every program, every argument, in order -- without a Mac. That is what the
|
|
29
|
+
; tests here check, because the interesting failures in a build driver are
|
|
30
|
+
; decisions, not I/O.
|
|
31
|
+
; =============================================================================
|
|
32
|
+
|
|
33
|
+
Import "../Shell.rgr"
|
|
34
|
+
Import "AppleTarget.rgr"
|
|
35
|
+
Import "AppleAppSpec.rgr"
|
|
36
|
+
Import "AppleToolchain.rgr"
|
|
37
|
+
Import "AppleSimulator.rgr"
|
|
38
|
+
Import "AppleDevice.rgr"
|
|
39
|
+
Import "AppleSigning.rgr"
|
|
40
|
+
|
|
41
|
+
class AppleBuildResult {
|
|
42
|
+
|
|
43
|
+
def ok:boolean false
|
|
44
|
+
; The finished bundle, e.g. build/ios-simulator/RangerDashboard.app
|
|
45
|
+
def appPath:string ""
|
|
46
|
+
; What went wrong, when something did.
|
|
47
|
+
def message:string ""
|
|
48
|
+
; One line per step, in order, whether or not anything ran.
|
|
49
|
+
def steps:[string]
|
|
50
|
+
|
|
51
|
+
Constructor () {
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn note:void (line:string) {
|
|
55
|
+
push steps line
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn fail:void (why:string) {
|
|
59
|
+
ok = false
|
|
60
|
+
message = why
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class AppleAppBuilder {
|
|
65
|
+
|
|
66
|
+
def sh:Shell
|
|
67
|
+
def tools:AppleToolchain
|
|
68
|
+
; Where bundles are assembled. One subdirectory per target key, so an
|
|
69
|
+
; iPhone build and a watch build of the same app do not overwrite each
|
|
70
|
+
; other.
|
|
71
|
+
def buildRoot:string "build"
|
|
72
|
+
; Print each step as it happens.
|
|
73
|
+
def verbose:boolean true
|
|
74
|
+
|
|
75
|
+
Constructor (shell:Shell toolchain:AppleToolchain) {
|
|
76
|
+
sh = shell
|
|
77
|
+
tools = toolchain
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn targetDir:string (target:AppleTarget) {
|
|
81
|
+
return (buildRoot + "/" + target.key)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn bundlePath:string (spec:AppleAppSpec target:AppleTarget) {
|
|
85
|
+
return ((this.targetDir(target)) + "/" + (spec.bundleName()))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn say:void (line:string) {
|
|
89
|
+
if verbose {
|
|
90
|
+
print line
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
; --- the build ------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
fn build:AppleBuildResult (spec:AppleAppSpec target:AppleTarget) {
|
|
97
|
+
def res:AppleBuildResult (new AppleBuildResult)
|
|
98
|
+
def appPath:string (this.bundlePath(spec target))
|
|
99
|
+
res.appPath = appPath
|
|
100
|
+
|
|
101
|
+
; 1. the toolchain
|
|
102
|
+
if (sh.dryRun == false) {
|
|
103
|
+
if ((tools.isAvailable()) == false) {
|
|
104
|
+
res.fail("xcrun was not found. Install Xcode or the Apple Command Line Tools (xcode-select --install).")
|
|
105
|
+
return res
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
res.note("toolchain " + target.key)
|
|
109
|
+
this.say("* target " + (target.describe()))
|
|
110
|
+
|
|
111
|
+
; 2. the SDK
|
|
112
|
+
def sdkPath:string ""
|
|
113
|
+
if (sh.dryRun == false) {
|
|
114
|
+
sdkPath = (tools.sdkPath(target.sdk))
|
|
115
|
+
if ((strlen sdkPath) == 0) {
|
|
116
|
+
res.fail("the " + target.sdk + " SDK is not installed in the selected developer directory.\n`xcrun --sdk " + target.sdk + " --show-sdk-path` answered nothing.\n\nThe usual cause is that only the Command Line Tools are installed: they carry\nno platform SDK at all. Run the environment check -- for this app that is\n`npm run ui:ios:check` -- which says which it is and what to do.")
|
|
117
|
+
return res
|
|
118
|
+
}
|
|
119
|
+
this.say("* sdk " + sdkPath + " (" + (tools.sdkVersion(target.sdk)) + ")")
|
|
120
|
+
} {
|
|
121
|
+
; A dry run resolves nothing, so the plan carries a placeholder that
|
|
122
|
+
; is obviously one rather than a shell substitution that would
|
|
123
|
+
; look runnable and not be.
|
|
124
|
+
sdkPath = ("<" + target.sdk + "-sdk-path>")
|
|
125
|
+
}
|
|
126
|
+
res.note("sdk " + target.sdk)
|
|
127
|
+
|
|
128
|
+
; 3. the bundle directory
|
|
129
|
+
if (sh.dryRun == false) {
|
|
130
|
+
create_dir (this.targetDir(target))
|
|
131
|
+
create_dir appPath
|
|
132
|
+
}
|
|
133
|
+
res.note("bundle " + appPath)
|
|
134
|
+
|
|
135
|
+
; 4. Info.plist
|
|
136
|
+
def plist:string (AppleInfoPlist.build(spec target))
|
|
137
|
+
if (sh.dryRun == false) {
|
|
138
|
+
write_file appPath "Info.plist" plist
|
|
139
|
+
; PkgInfo is vestigial and four bytes, and its absence is one of
|
|
140
|
+
; the things an old install check still complains about.
|
|
141
|
+
write_file appPath "PkgInfo" "APPL????"
|
|
142
|
+
}
|
|
143
|
+
; The lint goes through the Shell rather than inside the guard above,
|
|
144
|
+
; so that it is part of the recorded plan and its ORDER can be checked:
|
|
145
|
+
; a plist has to be found unparseable before an hour of compiling, not
|
|
146
|
+
; after it.
|
|
147
|
+
def lint:ShellResult (tools.lintPlist(appPath + "/Info.plist"))
|
|
148
|
+
if (lint.failed()) {
|
|
149
|
+
res.fail("the generated Info.plist did not parse: " + (lint.describeFailure()))
|
|
150
|
+
return res
|
|
151
|
+
}
|
|
152
|
+
res.note("Info.plist " + spec.bundleId)
|
|
153
|
+
this.say("* plist " + spec.bundleId + " v" + spec.version + " (" + spec.build + ")")
|
|
154
|
+
|
|
155
|
+
; 5. the executable
|
|
156
|
+
def compile:ShellResult (this.compileSwift(spec target sdkPath appPath))
|
|
157
|
+
res.note("swiftc " + (array_length spec.sources) + " sources")
|
|
158
|
+
if (compile.failed()) {
|
|
159
|
+
res.fail("swiftc failed.\n" + (compile.describeFailure()))
|
|
160
|
+
return res
|
|
161
|
+
}
|
|
162
|
+
this.say("* executable " + appPath + "/" + spec.name)
|
|
163
|
+
|
|
164
|
+
; 6. resources
|
|
165
|
+
for spec.resources r:string i {
|
|
166
|
+
def cpArgs:[string]
|
|
167
|
+
push cpArgs "-R"
|
|
168
|
+
push cpArgs r
|
|
169
|
+
push cpArgs appPath
|
|
170
|
+
def copy:ShellResult (sh.capture("cp" cpArgs))
|
|
171
|
+
if (copy.failed()) {
|
|
172
|
+
res.fail("could not copy the resource " + r + ": " + (copy.describeFailure()))
|
|
173
|
+
return res
|
|
174
|
+
}
|
|
175
|
+
res.note("resource " + r)
|
|
176
|
+
}
|
|
177
|
+
if ((array_length spec.resources) > 0) {
|
|
178
|
+
this.say("* resources " + (array_length spec.resources))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
; 7. signing
|
|
182
|
+
;
|
|
183
|
+
; A simulator bundle is signed ad hoc, which needs no account, no
|
|
184
|
+
; profile and no keychain. A DEVICE bundle needs three things that come
|
|
185
|
+
; as a set: the profile inside the bundle under the name the loader
|
|
186
|
+
; looks for, the entitlements that profile grants, and an identity the
|
|
187
|
+
; profile was issued to. The entitlements are read OUT OF the profile
|
|
188
|
+
; rather than written by hand, because a bundle signed with
|
|
189
|
+
; entitlements the profile does not grant installs and then refuses to
|
|
190
|
+
; launch, with no message anyone can act on.
|
|
191
|
+
def entitlements:string ""
|
|
192
|
+
if (target.isSimulator == false) {
|
|
193
|
+
if ((strlen spec.provisioningProfile) > 0) {
|
|
194
|
+
def cpArgs:[string]
|
|
195
|
+
push cpArgs spec.provisioningProfile
|
|
196
|
+
push cpArgs (appPath + "/embedded.mobileprovision")
|
|
197
|
+
def embed:ShellResult (sh.capture("cp" cpArgs))
|
|
198
|
+
if (embed.failed()) {
|
|
199
|
+
res.fail("could not put the provisioning profile in the bundle: " + (embed.describeFailure()))
|
|
200
|
+
return res
|
|
201
|
+
}
|
|
202
|
+
res.note("embedded.mobileprovision")
|
|
203
|
+
entitlements = ((this.targetDir(target)) + "/entitlements.plist")
|
|
204
|
+
def prepared:ShellResult (this.extractEntitlements(spec.provisioningProfile (this.targetDir(target)) entitlements))
|
|
205
|
+
if (prepared.failed()) {
|
|
206
|
+
res.fail("could not read the entitlements out of " + spec.provisioningProfile + ".\n" + (prepared.describeFailure()))
|
|
207
|
+
return res
|
|
208
|
+
}
|
|
209
|
+
res.note("entitlements " + entitlements)
|
|
210
|
+
} {
|
|
211
|
+
this.say("! no provisioning profile: this bundle will install only on a device the identity is already provisioned for")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
def sign:ShellResult (tools.codesign(target.signIdentity appPath entitlements))
|
|
215
|
+
res.note("codesign " + target.signIdentity)
|
|
216
|
+
if (sign.failed()) {
|
|
217
|
+
res.fail("codesign failed.\n" + (sign.describeFailure()))
|
|
218
|
+
return res
|
|
219
|
+
}
|
|
220
|
+
this.say("* signed " + target.signIdentity)
|
|
221
|
+
|
|
222
|
+
res.ok = true
|
|
223
|
+
this.say("* built " + appPath)
|
|
224
|
+
return res
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
; One swiftc invocation for the whole app. There is no incremental mode
|
|
228
|
+
; here on purpose: whole-module is what makes cross-file inlining possible,
|
|
229
|
+
; the generated Ranger file is one translation unit anyway, and a build
|
|
230
|
+
; that cannot go stale is worth more than one that is occasionally faster.
|
|
231
|
+
fn compileSwift:ShellResult (spec:AppleAppSpec target:AppleTarget sdkPath:string appPath:string) {
|
|
232
|
+
def argv:[string]
|
|
233
|
+
push argv "--sdk"
|
|
234
|
+
push argv target.sdk
|
|
235
|
+
push argv "swiftc"
|
|
236
|
+
push argv "-sdk"
|
|
237
|
+
push argv sdkPath
|
|
238
|
+
push argv "-target"
|
|
239
|
+
push argv (target.swiftTriple())
|
|
240
|
+
push argv "-emit-executable"
|
|
241
|
+
; Whole-module rather than the default per-file mode: the generated
|
|
242
|
+
; file is tens of thousands of lines in one module and the cross-file
|
|
243
|
+
; call graph is most of the program.
|
|
244
|
+
push argv "-wmo"
|
|
245
|
+
push argv "-o"
|
|
246
|
+
push argv (appPath + "/" + spec.name)
|
|
247
|
+
for spec.frameworks f:string i {
|
|
248
|
+
push argv "-framework"
|
|
249
|
+
push argv f
|
|
250
|
+
}
|
|
251
|
+
for spec.swiftFlags fl:string i {
|
|
252
|
+
push argv fl
|
|
253
|
+
}
|
|
254
|
+
for spec.sources s:string i {
|
|
255
|
+
push argv s
|
|
256
|
+
}
|
|
257
|
+
return (sh.run("xcrun" argv))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
; A .mobileprovision is a CMS envelope around a plist, and the entitlements
|
|
262
|
+
; are one key inside it. Two tools, no shell: `security cms -D` decodes to
|
|
263
|
+
; stdout, which is written out as a file, and `plutil -extract` pulls the
|
|
264
|
+
; one key back out of it.
|
|
265
|
+
fn extractEntitlements:ShellResult (profilePath:string workDir:string outPath:string) {
|
|
266
|
+
def decodeArgs:[string]
|
|
267
|
+
push decodeArgs "cms"
|
|
268
|
+
push decodeArgs "-D"
|
|
269
|
+
push decodeArgs "-i"
|
|
270
|
+
push decodeArgs profilePath
|
|
271
|
+
def decoded:ShellResult (sh.capture("security" decodeArgs))
|
|
272
|
+
if (decoded.failed()) {
|
|
273
|
+
return decoded
|
|
274
|
+
}
|
|
275
|
+
def decodedPath:string (workDir + "/profile.plist")
|
|
276
|
+
if (sh.dryRun == false) {
|
|
277
|
+
write_file workDir "profile.plist" decoded.out
|
|
278
|
+
}
|
|
279
|
+
def extractArgs:[string]
|
|
280
|
+
push extractArgs "-extract"
|
|
281
|
+
push extractArgs "Entitlements"
|
|
282
|
+
push extractArgs "xml1"
|
|
283
|
+
push extractArgs "-o"
|
|
284
|
+
push extractArgs outPath
|
|
285
|
+
push extractArgs decodedPath
|
|
286
|
+
return (sh.capture("plutil" extractArgs))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
; --- putting it on something ---------------------------------------------
|
|
290
|
+
|
|
291
|
+
; Boot a simulator if needed, install, launch. `nameWanted` is a substring
|
|
292
|
+
; of a device name ("iPad Pro"), a UDID, or "" for whatever is already
|
|
293
|
+
; running.
|
|
294
|
+
fn runOnSimulator:AppleBuildResult (spec:AppleAppSpec target:AppleTarget nameWanted:string) {
|
|
295
|
+
def res:AppleBuildResult (new AppleBuildResult)
|
|
296
|
+
res.appPath = (this.bundlePath(spec target))
|
|
297
|
+
|
|
298
|
+
if (target.isSimulator == false) {
|
|
299
|
+
res.fail("runOnSimulator was given the device target " + target.key + ".")
|
|
300
|
+
return res
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if sh.dryRun {
|
|
304
|
+
; The dry run still has to produce a plan, and a plan needs a
|
|
305
|
+
; device. Nothing is installed, so a placeholder udid is enough to
|
|
306
|
+
; make the command lines real.
|
|
307
|
+
def fake:AppleSimulator (new AppleSimulator)
|
|
308
|
+
fake.name = "(dry run)"
|
|
309
|
+
fake.udid = "DRY-RUN-UDID"
|
|
310
|
+
fake.state = "Shutdown"
|
|
311
|
+
fake.runtime = "iOS"
|
|
312
|
+
return (this.installAndLaunch(spec fake res))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
def picked@(optional):AppleSimulator (tools.pickSimulator(target nameWanted))
|
|
316
|
+
if (null? picked) {
|
|
317
|
+
def which:string "iOS"
|
|
318
|
+
if (target.isWatch()) {
|
|
319
|
+
which = "watchOS"
|
|
320
|
+
}
|
|
321
|
+
if ((strlen nameWanted) > 0) {
|
|
322
|
+
res.fail("no available " + which + " simulator matches \"" + nameWanted + "\". `xcrun simctl list devices available` shows what there is.")
|
|
323
|
+
return res
|
|
324
|
+
}
|
|
325
|
+
res.fail("no " + which + " simulator is available. Install a runtime in Xcode > Settings > Platforms.")
|
|
326
|
+
return res
|
|
327
|
+
}
|
|
328
|
+
def dev:AppleSimulator (unwrap picked)
|
|
329
|
+
this.say("* simulator " + (dev.describe()))
|
|
330
|
+
return (this.installAndLaunch(spec dev res))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
; A real iPhone or iPad on a cable. Two commands where the simulator needs
|
|
334
|
+
; five: there is nothing to boot and no window to bring forward.
|
|
335
|
+
;
|
|
336
|
+
; `attach` keeps the launched process in the foreground with its output
|
|
337
|
+
; coming back here, which is what a test build on a cable is FOR -- a device
|
|
338
|
+
; has no console you can otherwise see.
|
|
339
|
+
fn runOnDevice:AppleBuildResult (spec:AppleAppSpec target:AppleTarget nameWanted:string attach:boolean) {
|
|
340
|
+
def res:AppleBuildResult (new AppleBuildResult)
|
|
341
|
+
res.appPath = (this.bundlePath(spec target))
|
|
342
|
+
|
|
343
|
+
if target.isSimulator {
|
|
344
|
+
res.fail("runOnDevice was given the simulator target " + target.key + ".")
|
|
345
|
+
return res
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
def udid:string "DRY-RUN-DEVICE"
|
|
349
|
+
if (sh.dryRun == false) {
|
|
350
|
+
if ((tools.hasDeviceCtl()) == false) {
|
|
351
|
+
res.fail("xcrun devicectl is not in this Xcode. It ships with Xcode 15 and later; an older one needs a third party installer, which this does not wrap.")
|
|
352
|
+
return res
|
|
353
|
+
}
|
|
354
|
+
def picked@(optional):AppleDeviceInfo (tools.pickDevice(nameWanted))
|
|
355
|
+
if (null? picked) {
|
|
356
|
+
if ((strlen nameWanted) > 0) {
|
|
357
|
+
res.fail("no device matches \"" + nameWanted + "\". `xcrun devicectl list devices` shows what there is; --device takes a substring of the name or the identifier.")
|
|
358
|
+
return res
|
|
359
|
+
}
|
|
360
|
+
res.fail("no device is usable. Plug one in, unlock it, and trust this computer -- `xcrun devicectl list devices` shows what it can see. A device listed as \"available (paired)\" is usable and one listed as \"unavailable\" is not.")
|
|
361
|
+
return res
|
|
362
|
+
}
|
|
363
|
+
def dev:AppleDeviceInfo (unwrap picked)
|
|
364
|
+
udid = dev.identifier
|
|
365
|
+
this.say("* device " + (dev.describe()))
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
def install:ShellResult (tools.installOnDevice(udid res.appPath))
|
|
369
|
+
res.note("install " + res.appPath)
|
|
370
|
+
if (install.failed()) {
|
|
371
|
+
res.fail("devicectl could not install the app.\n" + (install.describeFailure()) + "\n\nAn install that is refused here is almost always the signing: the device has to be in the provisioning profile the bundle was signed with.")
|
|
372
|
+
return res
|
|
373
|
+
}
|
|
374
|
+
this.say("* installed " + res.appPath)
|
|
375
|
+
|
|
376
|
+
def launch:ShellResult (tools.launchOnDevice(udid spec.bundleId attach))
|
|
377
|
+
res.note("launch " + spec.bundleId)
|
|
378
|
+
if (launch.failed()) {
|
|
379
|
+
res.fail("devicectl could not launch " + spec.bundleId + ".\n" + (launch.describeFailure()))
|
|
380
|
+
return res
|
|
381
|
+
}
|
|
382
|
+
this.say("* launched " + spec.bundleId)
|
|
383
|
+
res.ok = true
|
|
384
|
+
return res
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
fn installAndLaunch:AppleBuildResult (spec:AppleAppSpec dev:AppleSimulator res:AppleBuildResult) {
|
|
388
|
+
if ((tools.bootSimulator(dev)) == false) {
|
|
389
|
+
res.fail("could not boot the simulator " + dev.name + " (" + dev.udid + ").")
|
|
390
|
+
return res
|
|
391
|
+
}
|
|
392
|
+
res.note("boot " + dev.udid)
|
|
393
|
+
tools.openSimulatorUi()
|
|
394
|
+
tools.waitForBoot(dev)
|
|
395
|
+
res.note("bootstatus " + dev.udid)
|
|
396
|
+
|
|
397
|
+
def install:ShellResult (tools.installApp(dev res.appPath))
|
|
398
|
+
res.note("install " + res.appPath)
|
|
399
|
+
if (install.failed()) {
|
|
400
|
+
res.fail("simctl could not install the app.\n" + (install.describeFailure()))
|
|
401
|
+
return res
|
|
402
|
+
}
|
|
403
|
+
this.say("* installed " + res.appPath)
|
|
404
|
+
|
|
405
|
+
def launch:ShellResult (tools.launchApp(dev spec.bundleId))
|
|
406
|
+
res.note("launch " + spec.bundleId)
|
|
407
|
+
if (launch.failed()) {
|
|
408
|
+
res.fail("simctl could not launch " + spec.bundleId + ".\n" + (launch.describeFailure()))
|
|
409
|
+
return res
|
|
410
|
+
}
|
|
411
|
+
this.say("* launched " + spec.bundleId)
|
|
412
|
+
res.ok = true
|
|
413
|
+
return res
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
; --- what a machine can be asked before any of this -----------------------
|
|
417
|
+
|
|
418
|
+
; A one-shot readiness report. Run before a build so that "this Mac cannot
|
|
419
|
+
; build a watch app" is a sentence rather than a compiler error.
|
|
420
|
+
; Right-pad, so the report reads as columns. There is no format operator and
|
|
421
|
+
; a diagnostic nobody can scan is a diagnostic nobody reads.
|
|
422
|
+
fn pad:string (text:string width:int) {
|
|
423
|
+
def out:string text
|
|
424
|
+
while ((strlen out) < width) {
|
|
425
|
+
out = (out + " ")
|
|
426
|
+
}
|
|
427
|
+
return out
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
fn checkEnvironment:[string] () {
|
|
431
|
+
def out:[string]
|
|
432
|
+
if ((tools.isAvailable()) == false) {
|
|
433
|
+
push out "xcrun MISSING"
|
|
434
|
+
push out ""
|
|
435
|
+
push out "Install Xcode from the App Store, or run `xcode-select --install` for the"
|
|
436
|
+
push out "Command Line Tools -- but note that the Command Line Tools alone cannot"
|
|
437
|
+
push out "build an app: see below."
|
|
438
|
+
return out
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
def dev:string (tools.developerDir())
|
|
442
|
+
push out ("developer dir " + dev)
|
|
443
|
+
def swiftProbe:ShellResult (tools.probeSwift())
|
|
444
|
+
if (swiftProbe.ok()) {
|
|
445
|
+
push out ("swift " + (swiftProbe.outFirstLine()))
|
|
446
|
+
} {
|
|
447
|
+
push out "swift xcrun could not answer -- see below"
|
|
448
|
+
}
|
|
449
|
+
push out ("host arch " + (tools.hostArch()))
|
|
450
|
+
|
|
451
|
+
; THE thing that goes wrong, and it goes wrong silently. The Command
|
|
452
|
+
; Line Tools are a compiler, a linker and git. Xcode.app is what carries
|
|
453
|
+
; the iOS and watchOS SDKs, the simulator runtimes and devicectl -- so a
|
|
454
|
+
; machine with only the Tools reports a working `xcrun` and then has
|
|
455
|
+
; nothing to build an app with. `xcode-select -p` is the tell.
|
|
456
|
+
def toolsOnly:boolean ((indexOf dev "CommandLineTools") >= 0)
|
|
457
|
+
|
|
458
|
+
def sdks:[string]
|
|
459
|
+
push sdks "iphonesimulator"
|
|
460
|
+
push sdks "iphoneos"
|
|
461
|
+
push sdks "watchsimulator"
|
|
462
|
+
push sdks "watchos"
|
|
463
|
+
def haveAnySdk:boolean false
|
|
464
|
+
; Whatever xcrun said when it could not answer. One reason is enough --
|
|
465
|
+
; the four SDKs fail for the same reason when they fail at all.
|
|
466
|
+
def whyNot:string ""
|
|
467
|
+
push out ""
|
|
468
|
+
for sdks sdk:string i {
|
|
469
|
+
def label:string (this.pad(sdk 17))
|
|
470
|
+
def probe:ShellResult (tools.probeSdk(sdk))
|
|
471
|
+
def p:string (trim probe.out)
|
|
472
|
+
if (probe.ok()) {
|
|
473
|
+
if ((strlen p) > 0) {
|
|
474
|
+
haveAnySdk = true
|
|
475
|
+
push out (label + (this.pad((tools.sdkVersion(sdk)) 8)) + p)
|
|
476
|
+
} {
|
|
477
|
+
push out (label + "not installed")
|
|
478
|
+
}
|
|
479
|
+
} {
|
|
480
|
+
push out (label + "not installed")
|
|
481
|
+
if ((strlen whyNot) == 0) {
|
|
482
|
+
whyNot = (probe.errText())
|
|
483
|
+
if ((strlen whyNot) == 0) {
|
|
484
|
+
whyNot = (probe.outText())
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
def sims:[AppleSimulator] (tools.listSimulators())
|
|
491
|
+
def haveCtl:boolean (tools.hasDeviceCtl())
|
|
492
|
+
push out ""
|
|
493
|
+
push out ("simulators " + (array_length sims) + " available")
|
|
494
|
+
if haveCtl {
|
|
495
|
+
push out "devicectl yes (a device on a cable can be installed onto)"
|
|
496
|
+
} {
|
|
497
|
+
push out "devicectl MISSING (no install onto a real device)"
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
push out ""
|
|
501
|
+
if toolsOnly {
|
|
502
|
+
push out "This is the COMMAND LINE TOOLS, not Xcode."
|
|
503
|
+
push out ""
|
|
504
|
+
push out "They carry clang, swiftc for the Mac, and git. They do NOT carry the iOS or"
|
|
505
|
+
push out "watchOS SDKs, any simulator runtime, or devicectl -- so nothing here can"
|
|
506
|
+
push out "build an app for a phone, a tablet or a watch, and `xcrun` reporting fine"
|
|
507
|
+
push out "is what makes that confusing."
|
|
508
|
+
push out ""
|
|
509
|
+
; Xcode being INSTALLED and Xcode being SELECTED are two different
|
|
510
|
+
; things, and the second is one command. Telling someone who already
|
|
511
|
+
; has it to spend an hour downloading it again is the wrong answer,
|
|
512
|
+
; so the answer depends on which of the two is missing.
|
|
513
|
+
if (dir_exists "/Applications/Xcode.app") {
|
|
514
|
+
push out "Xcode IS installed at /Applications/Xcode.app -- it is simply not the one"
|
|
515
|
+
push out "selected. One command:"
|
|
516
|
+
push out ""
|
|
517
|
+
push out " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
|
|
518
|
+
push out ""
|
|
519
|
+
push out "Then `npm run ui:ios:check` again."
|
|
520
|
+
} {
|
|
521
|
+
push out " 1. install Xcode from the App Store (it is large -- allow ~15 GB)"
|
|
522
|
+
push out " 2. open it once and let it finish installing components"
|
|
523
|
+
push out " 3. point the toolchain at it:"
|
|
524
|
+
push out " sudo xcode-select -s /Applications/Xcode.app/Contents/Developer"
|
|
525
|
+
push out " 4. get a simulator runtime, if step 2 did not:"
|
|
526
|
+
push out " xcodebuild -downloadPlatform iOS"
|
|
527
|
+
push out " (or Xcode > Settings > Platforms > iOS +)"
|
|
528
|
+
push out ""
|
|
529
|
+
push out "Then `npm run ui:ios:check` again, and `npm run ui:ios:run`."
|
|
530
|
+
}
|
|
531
|
+
return out
|
|
532
|
+
}
|
|
533
|
+
if (haveAnySdk == false) {
|
|
534
|
+
push out "Xcode is selected and no platform SDK answered."
|
|
535
|
+
if ((strlen whyNot) > 0) {
|
|
536
|
+
push out ""
|
|
537
|
+
push out "xcrun said:"
|
|
538
|
+
push out (" " + whyNot)
|
|
539
|
+
}
|
|
540
|
+
push out ""
|
|
541
|
+
; The three things that put a correctly selected Xcode in this
|
|
542
|
+
; state, in the order they are worth trying. The licence is first
|
|
543
|
+
; because it blocks every other tool with the same silence.
|
|
544
|
+
push out "In order of likelihood:"
|
|
545
|
+
push out ""
|
|
546
|
+
push out " 1. the licence has not been accepted -- this blocks every tool, quietly:"
|
|
547
|
+
push out " sudo xcodebuild -license accept"
|
|
548
|
+
push out " 2. Xcode has never finished its first launch:"
|
|
549
|
+
push out " sudo xcodebuild -runFirstLaunch"
|
|
550
|
+
push out " (or open Xcode once and let it install its components)"
|
|
551
|
+
push out " 3. the platform itself was never downloaded -- Xcode 15 and later ship"
|
|
552
|
+
push out " without the iOS platform and fetch it on demand:"
|
|
553
|
+
push out " xcodebuild -downloadPlatform iOS"
|
|
554
|
+
push out " xcodebuild -downloadPlatform watchOS"
|
|
555
|
+
push out " (or Xcode > Settings > Platforms > +)"
|
|
556
|
+
push out ""
|
|
557
|
+
push out "Then `npm run ui:ios:check` again."
|
|
558
|
+
return out
|
|
559
|
+
}
|
|
560
|
+
if ((array_length sims) == 0) {
|
|
561
|
+
push out "SDKs are here but no simulator runtime is. `npm run ui:ios` builds the app;"
|
|
562
|
+
push out "`npm run ui:ios:run` needs a runtime:"
|
|
563
|
+
push out " xcodebuild -downloadPlatform iOS (or Xcode > Settings > Platforms)"
|
|
564
|
+
return out
|
|
565
|
+
}
|
|
566
|
+
push out "Ready: npm run ui:ios:run puts the app on a simulator."
|
|
567
|
+
if (haveCtl == false) {
|
|
568
|
+
push out "A real device needs Xcode 15 or later, for devicectl."
|
|
569
|
+
}
|
|
570
|
+
return out
|
|
571
|
+
}
|
|
572
|
+
}
|