-
Notifications
You must be signed in to change notification settings - Fork 14
419 lines (395 loc) · 18.3 KB
/
Copy pathci-macos-ios.yml
File metadata and controls
419 lines (395 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
name: ci-macos-ios
# The iOS rows, measured on the only machine that can answer for them.
#
# iOS needs an ecosystem compiler and a LOCATED SDK: `xim:llvm` emits arm64
# Mach-O for an iOS deployment target, and only the machine's Xcode can supply
# the iPhoneOS / iPhoneSimulator headers and stub libraries, which are not
# redistributable. The simulator runtime is the same category. So every claim
# about these three rows is a claim about a macOS runner, and this job is where
# they are made.
#
# Kept out of ci-macos.yml deliberately: that job asserts mcpp's default quiet
# output shape and tacking a differently-shaped leg onto it has broken that
# assertion before.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ci-macos-ios-${{ github.ref }}
cancel-in-progress: true
jobs:
ios-host-surface:
name: iOS - what this runner actually provides
runs-on: macos-15
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-macos-llvm
# THE PREMISE, STATED AND THEN MEASURED. The design record schedules the
# iOS rows on one assumption: that a GitHub macOS runner ships both an
# iOS SDK and a bootable simulator. If it ships the SDK but no simulator,
# the device row is still verifiable here and the two simulator rows
# become a local-only claim -- which changes the tier they can reach and
# nothing else. This step is the difference between those two worlds.
# EVERY MEASUREMENT STEP CONTINUES, because the first version of this job
# did not and the four steps after the failing one were SKIPPED -- so one
# unmet assumption cost the whole measurement. A probe's value is the
# complete set of answers; a probe that stops at the first `no` is a
# build, not a measurement. (GitHub runs `run:` under `bash -e`, which is
# what turned a failing clang into a terminated step.)
- name: "Host surface: the two located SDKs and the simulator runtime"
continue-on-error: true
run: |
set -x
xcode-select -p
xcrun --sdk iphoneos --show-sdk-path
xcrun --sdk iphoneos --show-sdk-version
xcrun --sdk iphonesimulator --show-sdk-path
xcrun --sdk iphonesimulator --show-sdk-version
set +x
echo "--- runtimes ---"
xcrun simctl list runtimes
echo "--- devices available ---"
xcrun simctl list devices available
# WHETHER A BARE MACH-O CAN BE RUN AT ALL, which decides how much the
# runner program has to do. `simctl launch` needs an installed .app
# bundle; `simctl spawn` takes an executable. If spawn works, the
# `simctl-run` program is a boot-and-spawn wrapper; if it does not, it
# has to synthesise a bundle, sign it and install it. Measuring this is
# cheaper than designing for the harder case.
# THE PAYLOAD'S CLANG READS A CONFIG FILE, AND THAT CONFIG NAMES A
# DIFFERENT SDK. First run, with `-isysroot <iPhoneSimulator18.5.sdk>`
# on the command line:
#
# ld64.lld: error: /Library/Developer/CommandLineTools/SDKs/
# MacOSX.sdk/usr/lib/libc++.tbd(/usr/lib/libc++.1.dylib) is
# incompatible with arm64 (iOS Simulator18.0.0)
#
# mcpp's own post-install writes the located macOS SDK into the
# payload's `clang++.cfg` so that a native macOS build is deterministic,
# and that cfg is read before the command line for search purposes. So
# an Apple CROSS target has to suppress it -- which mcpp's cross path
# already does (`--no-default-config` in hostflags.cppm), and which this
# hand-written probe did not. Printed here because the explanation
# belongs next to the measurement.
- name: "The payload's default config, which is why --no-default-config"
continue-on-error: true
run: |
set -x
ls -la "$LLVM_ROOT/bin/"*.cfg || true
for f in "$LLVM_ROOT/bin/"*.cfg; do echo "=== $f"; cat "$f"; done || true
- name: "Device: is one bootable, and does spawn take a bare executable"
continue-on-error: true
run: |
set -uo pipefail
UDID=$(xcrun simctl list devices available \
| grep -A50 -- '-- iOS' \
| grep -m1 -oE '[0-9A-F]{8}-[0-9A-F-]{27}' || true)
echo "udid=[$UDID]"
if [ -z "$UDID" ]; then
echo "NO-IOS-SIMULATOR-DEVICE"
exit 0
fi
xcrun simctl boot "$UDID" || true
xcrun simctl bootstatus "$UDID" -b 2>&1 | tail -3 || true
cat > /tmp/hello.cpp << 'CPP'
#include <cstdio>
int main() { std::puts("1-2-3"); return 0; }
CPP
SDK=$(xcrun --sdk iphonesimulator --show-sdk-path)
if ! "$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0-simulator \
-isysroot "$SDK" -o /tmp/hello /tmp/hello.cpp; then
echo "SIM-COMPILE-FAILED"
exit 0
fi
file /tmp/hello
otool -l /tmp/hello | grep -A5 LC_BUILD_VERSION || true
echo "--- simctl spawn on a bare Mach-O ---"
if xcrun simctl spawn "$UDID" /tmp/hello; then
echo "SPAWN-OK"
else
echo "SPAWN-FAILED exit=$?"
fi
# THE DEVICE ROW'S ARTEFACT. Nothing runs it here -- that needs a
# signature the developer owns -- so the claim is about the ARTEFACT:
# arm64 Mach-O naming the iOS platform in LC_BUILD_VERSION. An artefact
# that says MACOS there is the failure this leg exists to catch, and it
# is invisible to a build that merely succeeds.
- name: "Device: the artefact names the iOS platform"
continue-on-error: true
run: |
set -euo pipefail
cat > /tmp/dev.cpp << 'CPP'
#include <cstdio>
int main() { std::puts("1-2-3"); return 0; }
CPP
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
"$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0 \
-isysroot "$SDK" -o /tmp/dev /tmp/dev.cpp
file /tmp/dev
otool -l /tmp/dev | grep -A5 LC_BUILD_VERSION
# THE C++ RUNTIME QUESTION, WHICH IS THE ONE THAT CAN SINK THIS. macOS
# links the PAYLOAD's static libc++ so that mcpp's deployment floor is
# real. That archive is built for macOS, and ld64 refuses an object built
# for one platform in a link for another -- so iOS may have to take
# libc++ from the SDK instead. Both are tried here, because "which one
# works" is the fact the flag builder needs and neither answer can be
# reasoned out from a Linux desk.
- name: "C++ runtime: SDK libc++ versus the payload static archive"
continue-on-error: true
run: |
set -x
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
cat > /tmp/cxx.cpp << 'CPP'
#include <string>
#include <cstdio>
int main() { std::string s = "1-2-3"; std::puts(s.c_str()); return 0; }
CPP
echo "--- (a) SDK libc++, dynamic ---"
if "$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0 \
-isysroot "$SDK" -o /tmp/cxx-sdk /tmp/cxx.cpp; then
otool -L /tmp/cxx-sdk
echo "SDK-LIBCXX-OK"
else
echo "SDK-LIBCXX-FAILED"
fi
echo "--- (b) payload static libc++ ---"
if "$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0 \
-isysroot "$SDK" -nostdlib++ \
"$LLVM_ROOT/lib/libc++.a" "$LLVM_ROOT/lib/libc++abi.a" \
-o /tmp/cxx-static /tmp/cxx.cpp; then
echo "PAYLOAD-STATIC-OK"
else
echo "PAYLOAD-STATIC-FAILED"
fi
set +x
# `import std` FOR iOS, which is mcpp's default and therefore the real
# bar. The module is precompiled from the PAYLOAD's libc++ headers
# against the LOCATED SDK's C library -- the same split macOS already
# uses, with a second SDK. If this cannot be made to work the iOS rows
# are a non-module tier and the documentation has to say so.
- name: "import std: precompile against the located SDK"
continue-on-error: true
run: |
set -x
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
STD_CPPM=$(find "$LLVM_ROOT" -name 'std.cppm' | head -1)
echo "std.cppm=$STD_CPPM"
MODDIR=$(dirname "$STD_CPPM")
if "$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0 \
-isysroot "$SDK" -Wno-reserved-module-identifier \
-Xclang -emit-reduced-module-interface \
--precompile -o /tmp/std.pcm "$STD_CPPM" -I"$MODDIR"; then
echo "PRECOMPILE-OK"
else
echo "PRECOMPILE-FAILED"
fi
cat > /tmp/mod.cpp << 'CPP'
import std;
int main() { std::println("1-2-3"); return 0; }
CPP
if "$LLVM_ROOT/bin/clang++" -std=c++23 --no-default-config \
-target arm64-apple-ios18.0 \
-isysroot "$SDK" -fmodule-file=std=/tmp/std.pcm \
-o /tmp/mod /tmp/mod.cpp /tmp/std.pcm; then
echo "MODULE-LINK-OK"
else
echo "MODULE-LINK-FAILED"
fi
file /tmp/mod || true
set +x
ios-engine:
name: iOS - mcpp builds and the simulator runs it
runs-on: macos-15
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: ./.github/actions/setup-macos-llvm
- name: Build mcpp from source (self-host)
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" build
echo "MCPP_DEV=$(ls -td "$PWD"/target/*/*/bin/mcpp | head -1)" >> "$GITHUB_ENV"
# THE THREE ROWS ARE `planned`, AND THAT IS THE STATE THIS JOB EXISTS TO
# CHANGE. A planned row is refused with its own escape hatch named:
#
# error: target 'aarch64-ios' is registered but not yet supported
# (planned) -- An explicit [target.aarch64-ios] toolchain
# override can opt in early.
#
# so the fixture writes that override. Once this job is green the tiers
# move and the override becomes unnecessary; keeping it until then is
# what makes the measurement possible before the claim is made.
- name: "Fixture: a project that imports std and prints 1-2-3"
run: |
set -euo pipefail
mkdir -p /tmp/iostest/src
cat > /tmp/iostest/mcpp.toml << 'TOML'
[package]
name = "iostest"
version = "0.1.0"
[build]
ios_deployment_target = "18.0"
[target.aarch64-ios]
toolchain = "llvm@22.1.8"
[target.aarch64-ios-sim]
toolchain = "llvm@22.1.8"
[target.x86_64-ios-sim]
toolchain = "llvm@22.1.8"
# D4: THE RUNNER IS AN ARGV PREFIX AND THE SESSION BELONGS TO A
# PACKAGE. `simctl-run` comes from `xim:apple-simulator-tools`; it
# chooses a device, boots it if it is not booted, waits, spawns, and
# returns the program's own exit status. The engine learns nothing
# about simulators, which is the boundary it keeps for every other
# emulated target.
#
# The device row keeps `runner` unset: an artefact cannot be run off
# an iOS device without a signature the developer owns.
#
# THE TOOL IS DECLARED AT THE TOP LEVEL HERE AND NOT IN
# examples/13, and the difference is which hosts the manifest has to
# work on. A tool declaration is not conditional on a target --
# measured: "[target.aarch64-ios-sim.xlings] does not accept 'deps'"
# -- and `xim:apple-simulator-tools` exists for macosx alone, so an
# unconditional declaration breaks a Linux build. This fixture is
# macOS-only, so it can say it.
#
# `[xlings.workspace]` and not `[xlings] deps`, which mcpp reports as
# superseded: "deps is superseded by [xlings.workspace] and will stop
# being read. It is honoured for now."
[xlings.workspace]
"xim:apple-simulator-tools" = ""
TOML
cat >> /tmp/iostest/mcpp.toml << 'TOML'
[target.aarch64-ios-sim]
runner = ["simctl-run"]
TOML
cat > /tmp/iostest/src/main.cpp << 'CPP'
import std;
int main() {
std::vector<int> v{3, 1, 2};
std::ranges::sort(v);
std::print("{}-{}-{}\n", v[0], v[1], v[2]);
}
CPP
cat /tmp/iostest/mcpp.toml
# D1/D2: THE DEVICE ROW'S ARTEFACT. Nothing runs it -- that needs a
# signature the developer owns -- so the claim is about the artefact:
# arm64 Mach-O naming the iOS platform and the project's deployment
# target in LC_BUILD_VERSION. A build that merely succeeds cannot tell
# those apart from a macOS binary.
- name: "D1: mcpp build --target aarch64-ios"
continue-on-error: true
run: |
set -x
cd /tmp/iostest
"$MCPP_DEV" build --target aarch64-ios
set +x
ART=$(find /tmp/iostest/target/aarch64-ios -name iostest -type f | head -1)
echo "artefact=$ART"
file "$ART"
otool -l "$ART" | grep -A5 LC_BUILD_VERSION
# D4's PREMISE: whether `simctl spawn` takes a bare Mach-O. If it does,
# the `simctl-run` program in xim:apple-simulator-tools is a
# boot-and-spawn wrapper; if it does not, it has to synthesise a bundle,
# sign it and install it. This is the measurement that decides which.
- name: "D4: mcpp build --target aarch64-ios-sim, then simctl spawn"
continue-on-error: true
run: |
set -x
cd /tmp/iostest
"$MCPP_DEV" build --target aarch64-ios-sim
set +x
ART=$(find /tmp/iostest/target/aarch64-ios-sim -name iostest -type f | head -1)
echo "artefact=$ART"
file "$ART"
otool -l "$ART" | grep -A5 LC_BUILD_VERSION
UDID=$(xcrun simctl list devices available \
| grep -A50 -- '-- iOS' \
| grep -m1 -oE '[0-9A-F]{8}-[0-9A-F-]{27}' || true)
echo "udid=[$UDID]"
if [ -z "$UDID" ]; then echo "NO-IOS-SIMULATOR-DEVICE"; exit 0; fi
xcrun simctl boot "$UDID" || true
xcrun simctl bootstatus "$UDID" -b 2>&1 | tail -3 || true
if xcrun simctl spawn "$UDID" "$ART"; then
echo "SPAWN-OK"
else
echo "SPAWN-FAILED exit=$?"
fi
# D4: `mcpp run` THROUGH THE RUNNER, WHICH IS THE SUPPORTED PATH.
#
# The step above spawned the artefact by hand, which proves the platform
# can run it and not that mcpp can. This repository has a standing note
# that publishing and verifying an asset is not the same as being able to
# use it through a supported path, and the difference here is a `runner`
# a manifest declares and a program a package provides.
- name: "D4: mcpp run --target aarch64-ios-sim prints 1-2-3"
continue-on-error: true
run: |
set -x
cd /tmp/iostest
out=$("$MCPP_DEV" run --target aarch64-ios-sim 2>&1) || true
set +x
printf '%s\n' "$out" | tail -20
if printf '%s\n' "$out" | grep -q '1-2-3'; then
echo "RUN-THROUGH-RUNNER-OK"
else
echo "RUN-THROUGH-RUNNER-FAILED"
fi
# x86_64-ios-sim IS THE THIRD ROW AND THIS RUNNER CANNOT RUN IT.
#
# The simulator runs the HOST's architecture, so an Apple-silicon machine
# has no x86_64 iOS runtime to spawn into. What can be measured here is
# the ARTEFACT, which is the claim the row's tier will rest on; a machine
# with an Intel host is what would move it further.
- name: "x86_64-ios-sim: the artefact, on a host that cannot run it"
continue-on-error: true
run: |
set -x
cd /tmp/iostest
"$MCPP_DEV" build --target x86_64-ios-sim
set +x
ART=$(find /tmp/iostest/target/x86_64-ios-sim -name iostest -type f | head -1)
file "$ART"
otool -l "$ART" | grep -A5 LC_BUILD_VERSION
# THE HOST SURFACE IS BOUNDED, AND ITS ABSENCE NAMES THE SDK. The
# recorded rule is that a host dependency must be minimal, named, and
# never a fallthrough -- so the refusal is a claim like any other, and
# `SDKROOT` pointing at a macOS SDK is the cheapest way to make the iOS
# SDK unlocatable without breaking the rest of the machine.
#
# AND NOT BY POINTING `DEVELOPER_DIR` AT NOTHING, WHICH MEASURED NOTHING.
#
# The first version of this step set `DEVELOPER_DIR=/nonexistent` and
# expected the refusal. The build SUCCEEDED: `xcrun` ignores an invalid
# developer directory and falls back to the recorded one, so the
# environment change did not make the SDK unlocatable. The predicate was
# right and the object was wrong -- the claim was never tested.
#
# The claim now lives where the SDK is genuinely absent, which is every
# non-Apple host: `tests/e2e/641` asserts it on Linux and Windows, with
# an explicit toolchain override so the tier gate does not answer first
# and `MCPP_NO_AUTO_INSTALL=1` so no payload is needed. What is left for
# this runner is the other half of the same claim -- that a machine which
# DOES have the SDK is not refused -- and that is what the steps above
# measure by building.
- name: "D: the SDK is located here, which is the other half of the claim"
continue-on-error: true
run: |
set -x
xcrun --sdk iphoneos --show-sdk-path
xcrun --sdk iphonesimulator --show-sdk-path
set +x
echo "both SDKs located; the refusal for a machine without them is"
echo "asserted by tests/e2e/641 on every non-Apple host"
cd /tmp/iostest && "$MCPP_DEV" build --target aarch64-ios 2>&1 | tail -3