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,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.
|