perf(build): speed up UniFFI binding generation - #590
Merged
Conversation
…ot use The codegen profile builds a dylib that is never shipped: uniffi-bindgen only reads its exported metadata symbols. It inherited opt-level z, fat LTO and a single codegen unit from release, which buys nothing for metadata extraction and is paid on every core edit. Measured on an M4 Max, both figures repeated and stable: make uniffi, cold 67s -> 26s make uniffi, incremental 24s -> 10s The generated bindings are byte-identical before and after, and the dylib still carries its metadata: 3,034 uniffi symbols, 179 UNIFFI_META entries. Only profile.codegen changes, so every shipped artifact is unaffected.
The provider's publish script has always rejected a simulator-only xcframework because it cannot ship; the host's did not, so building the simulator slice alone and publishing produced a release asset that no consumer could link for a device. Reachable before this change through XCFRAMEWORK_TARGETS, and about to become easier to reach. Same check, message and exit code as the provider. Verified both directions: exit 66 on a simulator-only framework, and execution continues past the guard once a device slice is present.
Iterating against a simulator does not need the device slice, and building it doubles the target builds for no benefit. SIM_ONLY=1 drops it, matching the convention provider-ios already uses. Safe because publish.sh now refuses a simulator-only framework, so this cannot reach a release asset. XCFRAMEWORK_TARGETS still overrides both. Verified that SIM_ONLY=1 resolves to one target and the default to two.
Imod7
approved these changes
Sep 3, 2026
Imod7
left a comment
Contributor
There was a problem hiding this comment.
This guard has no test, and the file for it already exists: scripts/lib/xcframework-publish-guard.test.mjs, run in CI by npm run test:scripts, already driving both publish scripts and already carrying a slices field. Please add a case, it is about ten lines.
Please document the new flag in ios/truapi-host/README.md. Its "Build outputs in detail" xcframework bullet still says both targets are built, and nothing on the host side mentions the new publish guard. ios/truapi-provider/README.md has the line to mirror.
Most core edits touch one crate, so incremental compilation is the largest remaining win here. Measured on an M4 Max, three runs: make uniffi, cold 26s -> 18s make uniffi, incremental 10s -> 3s Against main that is 67s -> 18s cold and 24s -> 3s incremental. Bindings stay byte-identical on every run.
The earlier check only looked for the device slice, so a device-only framework passed and shipped, and no simulator build could link it. Selecting one target through XCFRAMEWORK_TARGETS reaches either state, so both need rejecting. Both publish scripts now loop over both slices. The message no longer names a flag, because someone who selected targets directly never set one. Verified in both scripts: exit 66 for a simulator-only framework, 66 for a device-only one, and past the guard when both slices are present.
Make reads any non-empty value as true, so SIM_ONLY=0 and SIM_ONLY=false dropped the device slice, which is the opposite of what typing them means. 0, false, no and off are now off, at both call sites including provider-ios. Verified across empty, 1, true, 0, false, no and off.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
profile.codegenbuilds a dylib that is never shipped.uniffi-bindgenonly reads its exported metadata symbols. The profile inheritedopt-level = "z",lto = "fat"andcodegen-units = 1fromrelease. None of that helps metadata extraction, and the cost is paid on every core edit.Change
profile.codegennow optimises for build speed.make uniffi, coldmake uniffi, incremental after a core editGenerated bindings are byte identical before and after. The dylib still carries its metadata: 3,034 uniffi symbols and 179
UNIFFI_METAentries. Onlyprofile.codegenchanges, so shipped artifacts are unaffected.Two smaller changes come with it.
SIM_ONLY=1on thexcframeworktarget drops the device slice while iterating, which halves the target builds.provider-iosalready uses this convention.The host
publish.shnow refuses a framework with no device slice. The provider script has always rejected one, the host script did not, so building the simulator slice alone and publishing produced a release asset that cannot link for a device. That was already reachable throughXCFRAMEWORK_TARGETS.Timings measured on an Apple M4 Max, 16 cores, macOS 26, rustc 1.97.1.