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.
Files changed (58) hide show
  1. package/CHANGELOG.md +2527 -0
  2. package/LICENSE +28 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +1650 -1875
  5. package/dist/Lang.rgr +10946 -5662
  6. package/dist/README.md +3 -2
  7. package/dist/api.d.ts +2023 -61
  8. package/dist/api.js +68930 -27355
  9. package/dist/lib/ACEEditor.rgr +2 -0
  10. package/dist/lib/Ajax.rgr +2 -0
  11. package/dist/lib/CmdParams.rgr +2 -0
  12. package/dist/lib/Crypto.rgr +2 -0
  13. package/dist/lib/DOMLib.rgr +2 -0
  14. package/dist/lib/Engine3D.rgr +2 -0
  15. package/dist/lib/ImmutableVector.rgr +2 -0
  16. package/dist/lib/IndexedDB.rgr +2 -0
  17. package/dist/lib/IsoDate/DateMath.rgr +2 -0
  18. package/dist/lib/IsoDate/IsoCalendar.rgr +2 -0
  19. package/dist/lib/IsoDate/IsoDateParse.rgr +2 -0
  20. package/dist/lib/IsoDateLib.rgr +2 -0
  21. package/dist/lib/JSON.rgr +4906 -48
  22. package/dist/lib/JinxProcess.rgr +2 -0
  23. package/dist/lib/RangerProcess.rgr +2 -0
  24. package/dist/lib/Regex/RegexMatch.rgr +2 -0
  25. package/dist/lib/RegexLib.rgr +2 -0
  26. package/dist/lib/SQL.rgr +2 -0
  27. package/dist/lib/ServiceLib.rgr +2 -0
  28. package/dist/lib/Shell.rgr +326 -0
  29. package/dist/lib/Storage.rgr +2 -0
  30. package/dist/lib/Time.rgr +2 -0
  31. package/dist/lib/Timers.rgr +2 -0
  32. package/dist/lib/TypedArrays.rgr +2 -0
  33. package/dist/lib/ViewLib.rgr +2 -0
  34. package/dist/lib/WebLib.rgr +2 -0
  35. package/dist/lib/WebServerLib.rgr +2 -0
  36. package/dist/lib/apple/AppleAppBuilder.rgr +572 -0
  37. package/dist/lib/apple/AppleAppSpec.rgr +201 -0
  38. package/dist/lib/apple/AppleDevice.rgr +295 -0
  39. package/dist/lib/apple/AppleDeviceDoctor.rgr +453 -0
  40. package/dist/lib/apple/AppleSigning.rgr +379 -0
  41. package/dist/lib/apple/AppleSimulator.rgr +248 -0
  42. package/dist/lib/apple/AppleTarget.rgr +185 -0
  43. package/dist/lib/apple/AppleToolchain.rgr +458 -0
  44. package/dist/lib/apple/README.md +311 -0
  45. package/dist/lib/apple/apple_test.rgr +669 -0
  46. package/dist/lib/core/README.md +251 -0
  47. package/dist/lib/core/RgBase.rgr +313 -0
  48. package/dist/lib/core/RgNum.rgr +653 -0
  49. package/dist/lib/core/RgText.rgr +680 -0
  50. package/dist/lib/core/RgU32.rgr +309 -0
  51. package/dist/lib/ranger-dir.rgr +27 -5
  52. package/dist/lib/shell_test.rgr +193 -0
  53. package/dist/lib/stdlib.rgr +1176 -665
  54. package/dist/lib/stdops.rgr +2 -0
  55. package/dist/package.json +1 -1
  56. package/dist/rgrc.js +73013 -37819
  57. package/dist/stdops.rgr +2 -0
  58. package/package.json +1121 -205
@@ -0,0 +1,311 @@
1
+ # `lib/apple` — building an Apple app from Ranger, with no Xcode project
2
+
3
+ **License: MIT.** Nothing here imports `gallery/`.
4
+
5
+ An iOS, iPadOS or watchOS app is a directory with an executable in it and a
6
+ property list explaining that executable to the system. Xcode assembles both
7
+ from a project file, a target, a scheme and a build settings sheet. None of
8
+ that is required: `swiftc` can build an iOS executable directly, and everything
9
+ above it is bookkeeping a program can do instead.
10
+
11
+ This is that program's library. It drives Apple's own command line tools —
12
+ `xcrun`, `swiftc`, `plutil`, `codesign`, `simctl`, `security`, `actool` — over
13
+ [`lib/Shell.rgr`](../Shell.rgr), which means the whole thing can be run **dry**:
14
+ every decision made, every command line recorded, nothing executed. That is what
15
+ makes an Apple build testable on a machine that is not a Mac, and it is how
16
+ `apple_test.rgr` checks 151 things about it in this repository's CI, on Linux.
17
+
18
+ ```bash
19
+ npm run apple:test
20
+ ```
21
+
22
+ ## What is here
23
+
24
+ | File | What it is |
25
+ | --- | --- |
26
+ | `AppleTarget.rgr` | The four decisions Xcode hides behind a scheme pop-up: the SDK, the triple, the device families, the signing identity |
27
+ | `AppleAppSpec.rgr` | The app as everything but the code — name, bundle id, sources, resources — and the Info.plist writer |
28
+ | `AppleSimulator.rgr` | Reading `xcrun simctl list devices`, as a pure function over its text |
29
+ | `AppleDevice.rgr` | The same for `xcrun devicectl list devices`, and for `devicectl device info details` — the iPhones and iPads on a cable, and what each one says about itself |
30
+ | `AppleDeviceDoctor.rgr` | Why the phone will not take the app: the chain from `xcrun` to the provisioning profile, walked in order, with what to do about the first link that is missing |
31
+ | `AppleSigning.rgr` | Finding the identity and the `.mobileprovision`, so nobody types them |
32
+ | `AppleToolchain.rgr` | `xcrun` and the tools it resolves, as methods |
33
+ | `AppleAppBuilder.rgr` | The pipeline: SDK, bundle, plist, swiftc, resources, signing — then boot, install, launch |
34
+ | `apple_test.rgr` | All of the above, checked without a Mac |
35
+
36
+ ## The pipeline
37
+
38
+ ```
39
+ AppleAppSpec + AppleTarget
40
+ │
41
+ ├─ 1 xcrun --sdk NAME --show-sdk-path where the SDK is
42
+ ├─ 2 Info.plist written, then plutil -lint
43
+ ├─ 3 xcrun --sdk NAME swiftc -target … one executable, whole-module
44
+ ├─ 4 cp -R resources into the bundle
45
+ ├─ 5 security cms -D / plutil -extract a device build's entitlements
46
+ └─ 6 codesign --force --sign "-" for a simulator
47
+ │
48
+ ┌─────────┴──────────┐
49
+ xcrun simctl boot / install / launch xcrun devicectl device install app
50
+ ```
51
+
52
+ Nothing in that list is `xcodebuild`, and nothing in it is an `.xcodeproj`.
53
+
54
+ ## Putting it on a simulator
55
+
56
+ `AppleAppBuilder.runOnSimulator(spec target nameWanted)` is the second half, and
57
+ it is five commands:
58
+
59
+ ```
60
+ xcrun simctl boot <udid>
61
+ open -a Simulator
62
+ xcrun simctl bootstatus <udid>
63
+ xcrun simctl install <udid> build/ios-simulator/MyApp.app
64
+ xcrun simctl launch --terminate-running-process <udid> com.example.myapp
65
+ ```
66
+
67
+ Three of those are there for reasons that are not obvious until the day they
68
+ are missing:
69
+
70
+ * **`open -a Simulator`** — `simctl boot` starts the *device*, not the
71
+ application that shows it. Without this the app is running and there is no
72
+ window, which looks exactly like a build that did nothing.
73
+ * **`bootstatus`** — installing into a device that is still starting fails
74
+ intermittently, which is the worst kind of failure to debug.
75
+ * **`--terminate-running-process`** — so a second run replaces the first
76
+ instead of appearing to do nothing.
77
+
78
+ `boot` is skipped when the device is already `Booted`, and a
79
+ `Unable to boot device in current state: Booted` race with something else is
80
+ read as success rather than as an error.
81
+
82
+ **Which device.** `nameWanted` is a substring of a device name (`"iPad Pro"`,
83
+ `"iPhone 15"`) or a UDID. Given `""`, a device that is **already booted** wins:
84
+ booting a second simulator when one is open is slow and puts the app on a
85
+ window nobody is looking at. Given a name, the named one wins even over a
86
+ booted one — you asked for an iPad, you get the iPad. A name that matches
87
+ nothing is an error naming it, not a fallback to something else.
88
+
89
+ **A simulator target is all `runOnSimulator` takes.** A device goes through
90
+ `runOnDevice`, below; asking either for the other is an error that names it
91
+ rather than installing nowhere.
92
+
93
+ ## Putting it on a real iPhone or iPad
94
+
95
+ `AppleAppBuilder.runOnDevice(spec target nameWanted attach)` is two commands,
96
+ where a simulator needs five — there is nothing to boot and no window to bring
97
+ forward:
98
+
99
+ ```
100
+ xcrun devicectl device install app --device <id> build/ios-device/MyApp.app
101
+ xcrun devicectl device process launch --device <id> --terminate-existing com.example.myapp
102
+ ```
103
+
104
+ `attach` swaps `--terminate-existing` for `--console` and keeps the process in
105
+ the foreground with its output coming back, which is the whole point of a test
106
+ build on a cable: a device has no console you can otherwise see.
107
+
108
+ `nameWanted` is a substring of the device name or the identifier `devicectl`
109
+ assigns; `""` takes the first **usable** device, preferring one `devicectl`
110
+ already has a tunnel to.
111
+
112
+ "Usable" is wider than "connected". `devicectl` prints `available (paired)` for
113
+ a device it knows and has no tunnel to *at that moment*, and the tunnel comes up
114
+ by itself the first time anything asks the device something — so such a device
115
+ installs fine, and refusing it is the difference between a build that runs and
116
+ one that stops on a cable that is plugged in. Only `unavailable` is refused.
117
+ Beware that `unavailable` contains `available`: the negative is tested first.
118
+
119
+ ### When it will not install: `AppleDeviceDoctor`
120
+
121
+ A device build has a long chain in front of it, and every link answers a
122
+ different question with the same silence — nothing installs:
123
+
124
+ ```
125
+ xcrun -> a selected Xcode -> devicectl -> the iphoneos SDK ->
126
+ a cable -> a pairing -> Developer Mode -> a mounted developer disk image ->
127
+ a signing identity -> a provisioning profile that lists THIS device
128
+ ```
129
+
130
+ `AppleDeviceDoctor` walks it and stops at the first link that is not there,
131
+ because every check after a broken one is asking about a machine that does not
132
+ exist. It prints the check, what it found, and what to do — and only the failing
133
+ check gets advice, since a wall of remedies for problems nobody has is how a
134
+ diagnostic stops being read.
135
+
136
+ ```
137
+ $ npm run ui:ios:doctor
138
+ device check
139
+ ok xcode-select /Applications/Xcode.app/Contents/Developer
140
+ ok devicectl present
141
+ ok iphoneos SDK /Applications/Xcode.app/.../iPhoneOS.sdk
142
+ ok device Tero iPhone (iPhone 16) 5FB2... -- available (paired)
143
+ ok connection iPhone 16 iOS 26.3
144
+ FAIL developer mode disabled
145
+ ```
146
+
147
+ Two of the links repair themselves, and are therefore repaired rather than
148
+ reported: a device with no tunnel gets one the moment anything asks it
149
+ something, and a device with no developer disk image gets one the first time
150
+ anything wants a developer service. Both look exactly like a broken cable in
151
+ `devicectl list devices`, and neither is — which is why `build_ios.rgr` runs
152
+ the device half of this walk (`preflight`) before `swiftc` rather than letting
153
+ the install fail an hour later.
154
+
155
+ The one link nothing here can fix is Developer Mode: Apple made it a physical
156
+ confirmation on the device, so the doctor can only say where the setting is.
157
+
158
+ ### The three things it needs, and where they come from
159
+
160
+ `AppleSigning` finds all three, so a device build is one command rather than
161
+ three flags. `--identity` and `--profile` still win when given.
162
+
163
+ * **The identity** — `security find-identity -v -p codesigning`, preferring an
164
+ **Apple Development** certificate. This is testing on a cable; a distribution
165
+ certificate cannot do it.
166
+ * **The profile** — the `.mobileprovision` files Xcode leaves behind, in both
167
+ the directory it used before Xcode 16 and the one it uses now. A profile is a
168
+ candidate when its `application-identifier` covers the bundle id (an exact
169
+ one beats a wildcard, because the exact one is what Xcode made for this app)
170
+ **and** its `ProvisionedDevices` names this device.
171
+ * **The entitlements** — read out of the profile that was chosen, never written
172
+ by hand. A bundle signed with entitlements its profile does not grant
173
+ installs and then refuses to launch, with no message anyone can act on.
174
+
175
+ A `.mobileprovision` is a CMS envelope around a plist, so reading one is
176
+ `security cms -D` and then `plutil -extract` — two tools and no plist parser.
177
+
178
+ **What it cannot do is CREATE a profile.** That is a conversation with Apple's
179
+ developer portal, and the only command line tool that has it is `xcodebuild
180
+ -allowProvisioningUpdates`, which is what this directory exists to avoid. Open
181
+ the project in Xcode once and build to the device; after that the profile is on
182
+ the machine and this finds it. The error says exactly that.
183
+
184
+ `devicectl` needs Xcode 15 or later. An older toolchain needs a third-party
185
+ installer, which this does not wrap, and the error says so rather than failing
186
+ inside a tool that is not there.
187
+
188
+ ## The four decisions, and why each one is its own field
189
+
190
+ ```ranger
191
+ def target:AppleTarget (AppleTarget.iosSimulator("arm64"))
192
+ print (target.describe())
193
+ ; ios-simulator (arm64-apple-ios15.0-simulator, sdk iphonesimulator, iPhone + iPad)
194
+ ```
195
+
196
+ **The SDK** is what `xcrun --sdk` resolves and what `swiftc` compiles against.
197
+
198
+ **The triple** carries the architecture, the platform, the **deployment target**
199
+ and — for a simulator — the `-simulator` environment. Leaving that environment
200
+ off produces a device build that links and will not run on the simulator, with
201
+ no error that says so.
202
+
203
+ **The families** are one number list in Info.plist, and the only difference
204
+ between "an iPhone app" and "an iPad app" once the binary exists. A watch app is
205
+ its own family and its own kind of bundle: `WKApplication` and `WKWatchOnly`,
206
+ no `UIDeviceFamily`, no launch screen.
207
+
208
+ **Signing** is `-` for a simulator, which needs no developer account, no
209
+ keychain and no profile. A device build needs an identity and a
210
+ `.mobileprovision`, and the builder reads the entitlements OUT OF the profile
211
+ rather than letting a caller write them by hand — a bundle signed with
212
+ entitlements its profile does not grant installs and then refuses to launch,
213
+ with no message anyone can act on.
214
+
215
+ ## What the tests check, and why they are worth having
216
+
217
+ `apple_test.rgr` runs no Apple tool. It checks the part of a build driver that
218
+ has bugs in it: **which program, with which arguments, in which order.**
219
+
220
+ * the triples, for all four targets and both host architectures
221
+ * the plist: the executable key, the escaping, the launch screen an iPad needs,
222
+ the watch's `WKApplication`, and the keys each one must NOT have
223
+ * the simulator listing, against a fixture with the two traps in it — a device
224
+ name that contains brackets (`iPhone SE (3rd generation)`) and a runtime
225
+ marked unavailable
226
+ * which device gets picked: booted beats not-booted, named beats booted, and a
227
+ real device that is merely paired beats nothing at all while an `unavailable`
228
+ one is never picked
229
+ * the fields `devicectl device info details` reports, including that
230
+ `unavailable` is not read as `available`
231
+ * the whole plan for an iPhone, a watch and a device, including that the plist
232
+ is linted **before** an hour of compiling and that signing is **last**
233
+ * that a path with a space in it survives into the log quoted
234
+
235
+ 183 checks, on JavaScript, Python, Go, Rust, C++, Java and PHP — the same Ranger
236
+ source, run on seven runtimes.
237
+
238
+ ## Using it
239
+
240
+ ```ranger
241
+ Import "lib/Shell.rgr"
242
+ Import "lib/apple/AppleAppBuilder.rgr"
243
+
244
+ def sh:Shell (new Shell)
245
+ sh.dryRun = false
246
+ def tools:AppleToolchain (new AppleToolchain (sh))
247
+ def builder:AppleAppBuilder (new AppleAppBuilder (sh tools))
248
+
249
+ def spec:AppleAppSpec (new AppleAppSpec)
250
+ spec.name = "MyApp"
251
+ spec.bundleId = "com.example.myapp"
252
+ spec.addSource("generated/app.swift")
253
+ spec.addSource("host/Main.swift")
254
+
255
+ def target:AppleTarget (AppleTarget.iosSimulator((tools.hostArch())))
256
+ def res:AppleBuildResult (builder.build(spec target))
257
+ if res.ok {
258
+ builder.runOnSimulator(spec target "iPad Pro")
259
+ }
260
+ ```
261
+
262
+ ...and onto the phone on the cable, with nothing typed that can be found:
263
+
264
+ ```ranger
265
+ def signing:AppleSigning (new AppleSigning (sh))
266
+ def target:AppleTarget (AppleTarget.iosDevice("-"))
267
+
268
+ def udid:string ""
269
+ def devOpt@(optional):AppleDeviceInfo (tools.pickDevice(""))
270
+ if (!null? devOpt) {
271
+ def dev:AppleDeviceInfo (unwrap devOpt)
272
+ udid = dev.identifier
273
+ }
274
+
275
+ def ids:[AppleSigningIdentity] (signing.findIdentities())
276
+ def idOpt@(optional):AppleSigningIdentity (AppleSigning.pickIdentity(ids ""))
277
+ def profOpt@(optional):AppleProvisioningProfile (signing.pickProfile(spec.bundleId udid "build/ios-device"))
278
+ if ((!null? idOpt) && (!null? profOpt)) {
279
+ def id:AppleSigningIdentity (unwrap idOpt)
280
+ def prof:AppleProvisioningProfile (unwrap profOpt)
281
+ target.signIdentity = id.sha1
282
+ spec.provisioningProfile = prof.path
283
+ def built:AppleBuildResult (builder.build(spec target))
284
+ if built.ok {
285
+ builder.runOnDevice(spec target "" false)
286
+ }
287
+ }
288
+ ```
289
+
290
+ Every intermediate is bound rather than chained, and that is not style: a call
291
+ result that is immediately dereferenced loses an argument (ISSUES.md #85) and a
292
+ `(expr).field` read as a call argument does not resolve (#81). Both examples
293
+ above compile and run — they are checked, not written from memory.
294
+
295
+ A worked example, end to end, is
296
+ [`gallery/ui/ios`](../../gallery/ui/ios/README.md): the dashboard demo as an
297
+ iPhone, iPad and Apple Watch app.
298
+
299
+ ## What is not here
300
+
301
+ * **`xcodebuild`.** Deliberately. If you have a project file, use it; this is
302
+ for the case where you would rather not have one.
303
+ * **An asset catalog pipeline.** `AppleToolchain.compileAssets` wraps `actool`,
304
+ and nothing calls it yet — an app with no icon builds and runs.
305
+ * **App Store packaging.** No `.ipa`, no `exportOptions.plist`, no upload.
306
+ * **Creating a provisioning profile.** Found, matched and used; not created —
307
+ see above.
308
+ * **Wireless device install.** `devicectl` can do it and this asks for the
309
+ device it is given, so a device paired over the network works as long as
310
+ `devicectl` lists it at all. Nothing here pairs one — `AppleDeviceDoctor`
311
+ says how when there is nothing to install onto.