Skip to content

Build the iOS project in Debug and Release on CI - #604

Merged
simonhamp merged 1 commit into
mainfrom
ci/ios-release-compile
Oct 24, 2026
Merged

simonhamp merged 1 commit into
mainfrom
ci/ios-release-compile

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Release builds on main didn't compile from #372 until #602, and nothing caught it. Debug built fine, and nothing in CI compiles the iOS project at all (tests.yml is PHP only and test-package.yml is Android and manual). This adds a job that does.

What the job does

  1. Makes a fresh Laravel app outside the workspace that requires this checkout and mobile-ui main through path repositories.
  2. Runs native:install ios. That downloads the PHP binaries, 34 MB in a couple of seconds, so I haven't cached them. --skip-php doesn't work for this: without the PHP headers Swift can't find php_embed_init and the rest, so nothing compiles.
  3. Runs native:build --simulated. That's the only command that compiles plugin renderers into the project, and it builds Debug for the simulator while it's there.
  4. Builds Release for devices with xcodebuild, unsigned. native:build --release archives with -allowProvisioningUpdates, which needs an Apple account, so this step calls xcodebuild itself. It sets NATIVEPHP_CLI_BUILD=1 so the project's build phase doesn't run native:build a second time.

If the Release build fails, the step only prints the error lines and the compiler's "While ..." lines. The abort from #602 dumps about 170,000 lines of substitution map, so the full logs go up as an artifact instead.

Runs

The first two runs used an earlier version of this workflow that ran both Xcodes side by side. The steps were otherwise the same.

Code Xcode Result Job time
main with #602 (ae17977) 27.0 passes 2m 11s
main with #602 (ae17977) 26.6 passes 4m 03s
2189e72, before #602 27.0 Release aborts, Debug passes 2m 55s
2189e72, before #602 26.6 Release aborts, Debug passes 3m 42s
this PR (c7f99b2), final workflow 27.0 passes 2m 33s

On 2189e72 the Release step prints this, which is enough to see what broke:

Abort: function substOpaqueTypesWithUnderlyingTypes at SubstitutionMap.cpp:651
Possible non-terminating type substitution detected
3.	While evaluating request ASTLoweringRequest(Lowering AST to SIL for module NativePHP)
4.	While silgen visitDecl 'ResolvedNodeView' (at /Users/runner/work/_temp/app/nativephp/ios/NativePHP/NativeRender/SwiftUINodeRenderer.swift:456:1)
5.	While silgen emitFunction SIL function "@$s9NativePHP16ResolvedNodeViewV4bodyQrvg".
** BUILD FAILED **

This also answers the open question on #602: Xcode 26.6 hits the same limit.

Which runner

It runs on xcode-27, GitHub's preview image for Xcode 27, and pins Xcode 27.0 (27A266a, the same build #602 was bisected on). Xcode 26.6 on macos-26 catches the abort too but takes about twice as long. The preview label will need changing to the macOS 27 image's label when that's released. GitHub says preview images can queue and their software can be unstable. None of these runs waited for a runner.

Cost

mobile-air is public, so standard GitHub-hosted runners, macOS included, cost nothing here. What it does use up is the org's macOS concurrency. The Free plan allows 5 macOS jobs at once across the whole NativePHP org, and each run holds one for 2 to 3 minutes.

If it ever were billed: GitHub rounds each job up to the minute, so a run is 3 minutes. At $0.062 a minute for a standard macOS runner that's about $0.19 a run. The Xcode 26.6 job would be 4 to 5 minutes, about $0.31.

Things to know

  • mobile-ui main is part of the build. Its 95 renderer files compile into the same Swift module as core, so this catches a mobile-air change that breaks them. The downside is that a broken mobile-ui main turns mobile-air PRs red as well. Locally it adds about 15 seconds to the Release build.
  • PRs only run it when they touch resources/xcode, src, composer.json or this workflow. Pushes to main always run it. Because of that filter, it can't become a required check as it is, or PRs that skip it will wait forever.
  • A new push to a PR cancels the run already going for it.
  • ci/ios-release-compile-before-602 is 2189e72 plus this workflow. It only exists for the failing runs above and can be deleted.

🤖 Generated with Claude Code

Release builds on main aborted in swift-frontend from #372 until #602.
Debug built fine and nothing in CI compiles the iOS project, so nobody
noticed until someone built for release.

The job makes a Laravel app that requires this checkout and mobile-ui
main, runs native:install ios and native:build --simulated (Debug), then
builds Release for devices with xcodebuild, unsigned. It runs on GitHub's
xcode-27 image with Xcode 27.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review October 24, 2026 03:00
@simonhamp
simonhamp merged commit b107ed9 into main Oct 24, 2026
6 checks passed
@simonhamp
simonhamp deleted the ci/ios-release-compile branch October 24, 2026 03:31
gwleuverink added a commit that referenced this pull request Oct 25, 2026
Covers mobile PRs #241, #305, #344, #348, #370, #372, #401, #436, #439, #441, #447, #451, #461, #499, #500, #501, #502, #509, #511, #517, #522, #524, #529, #530, #537, #539, #541, #543, #545, #549, #550, #553, #557, #558, #561, #564, #565, #566, #571, #572, #575, #576, #577, #578, #579, #580, #581, #582, #583, #584, #585, #586, #587, #589, #591, #592, #593, #594, #595, #596, #597, #598, #599, #600, #601, #602, #603 and #604 (main d5dd874..b107ed9). #539 was already on this branch, merged as if accepted on 10 Oct. Its later pushes only brought main in and fixed OnWhenTest, and both were already here.

Ported to core in NativePHP/core#17 (branch sync/mobile-main-2026-10-25), which this merge needs: #572 and #589 (the ImageFit enum, which moves to core), #578 (route() and external URLs in navigate() and replace(), with an openedExternally() seam), #502 (unknown element tags; UnknownElementException moves to core with a hint seam), #529, #499, #372, #586, #241, #549, #601, #603, #344 and #593, the core halves of #511, #370, #348 and #401, and --json on core's native:run for #305. --fresh on core's native:run too, for #513, which came before the last sync and was missed then.

Kept on this branch: #578's exitToWeb() and the openedExternally() that opens the browser, #558's browser handoff, #370's system back and #596's root back, all in InteractsWithAppShell. #439's stack depth in WrapsScreensInNativeChrome, read through router(). #511's PERF gating in runLoop(). ScreenGuard (#348, #595) and UnknownElementHint (#502) stay in mobile, and the provider hands core the hints at boot. Platform reads go through System::platform(), as #596 has it.

Removed again in the merge: the moved files main changed, ImageFit and UnknownElementException (now core's), and src/Events/GlobalEventDispatcher.php, which main added back (core has it, and the old name is an alias). Main's import of the old dispatcher name came out of DispatchEventFromAppController and NativeServiceProvider, where it clashed with core's.

Adapted for the extraction: RunloopIdleTest reflects on core's NativeComponent, where the runloop state it reads is private. NativeComponentAnalyzer counts a render() declared on core's class as the default one, which #501's new test caught.

Needs NativePHP/core#17.

Tests: mobile on this merge against the core sync branch (52db12f), 2,343 passed, 4 risky, 5 skipped (Devkeepr run 5371). PHPStan clean (run 5372). Mobile main (b107ed9), 2,288 passed, 4 risky, 5 skipped (run 5359). Desktop main (24f3003) against the core sync branch, test:php 1,883 passed (run 5373), as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant