Repository navigation
fix(workbench): hide every symbol but the UniFFI exports in the iOS static libraries - #2968
Merged
Merged
Conversation
…tatic libraries The Swift wire-contract test links both iOS static libraries into one test bundle, and the linker prints 441 duplicate-symbol warnings. Each library carries its own copy of Rust's std, core and alloc, because the wallet crate and the proxy crate build in separate workspaces: nym-sdk and the zcash crates hold conflicting dependencies on crypto-common, so one static library cannot hold both. Every consumer that links both libraries sees the same warnings. The iOS plan now hides every symbol of each library except its UniFFI functions before lipo and the XCFramework steps. For each target the plan writes an exported-symbols list naming the library's uniffi_ and ffi_ functions, links the cargo archive into one relocatable object with ld -r and -exported_symbols_list, which marks every other global symbol private extern, and packs that object into an archive with libtool. The bindgen step still reads the cargo archive, because UniFFI's library mode finds its metadata through the symbol table. The universal simulator library and both XCFrameworks take the hidden archives. A unit test pins the exported-symbols text of each library, and a plan test asserts the link and archive steps of every target, their order after the bindgen step, and that lipo and the XCFramework steps read only hidden archives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…OS link The relocatable link that hides every symbol but the UniFFI exports ran `ld -r` without naming an architecture. Xcode 16.4's linker does not infer one from the inputs in that mode: it warns that no `-arch` was given, discards the archive as an "iOS-unknown" versus "iOS-arm64" mismatch, and then asserts "unknown objc arch" in its Objective-C pass. That assertion failed the per-PR iOS job on run 38651288550. Each iOS target now carries its linker architecture and platform in an IosTarget value, and the link passes `-arch` and `-platform_version` derived from it. The device target links as arm64 on ios, and both simulator targets link on ios-simulator as arm64 and x86_64. The deployment target serves as both the minimum and the SDK version, as rustc does for its own Apple links. The existing triple constants and the target iterator derive from the same values, so no other step changes. The plan test pins the literal link command for every triple from a three-row table of triple, architecture, and platform, and checks that the table covers every planned target. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o side files The hide step now passes its two UniFFI patterns to ld as -exported_symbol flags. The exports file, the Write step and the test that pinned the file's text are gone. The plan test pins the whole link command instead. The libtool step writes the hidden archive over cargo's archive. The bindgen step already runs before the hide, so it reads the raw symbol table, and the lipo and XCFramework steps return to their unchanged paths. The hidden directory and its closures are gone. The plan test asserts that every later reader of the archive follows the pack step. A Generation now names its own library, so the hide step takes one Generation and derives the target directory, the archive and the patterns from it. The LIB_NAMES array is gone. The IosTarget table is the only description of the three targets. The two triple constants, the iter_targets helper and the test's parallel table of triples, architectures and platforms are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…into fix/ios-hide-static-symbols
juanky201271
left a comment
Contributor
There was a problem hiding this comment.
CI confirms the fix: dev's iOS job (after #2937) prints 441 duplicate symbol warnings, this branch prints 0.
- The description predates a686a40: there's no exported-symbols list file (the flags are inline
-exported_symbol), nohidden/dir (libtool overwrites cargo's archive in place), and no unit test forexported_symbol_patterns. #2937 is already merged, so the Verification section is outdated too. - Overwriting cargo's
.ain place:fresh_planonly cleansout_host, so on a local rerun cargo sees the archive as fresh and bindgen reads the already-hidden one. Probably harmless, but writing to a separate path avoids relying on it. - The new consts,
IosTarget,exported_symbol_patternsandlib_namehave no///docs, unlike their neighbours. - Re
IosTarget: I'd keep the explicit fields.x86_64-apple-iosis a simulator without-sim, so the platform can't be cleanly parsed from the triple.
juanky201271
previously approved these changes
Oct 15, 2026
…rm from one enum A review of PR #2968 asked why IosTarget stored a triple beside two facts that the triple already implies. The struct is now an enum with a Device variant and a Simulator variant that carries its architecture. Three const getters derive the rustc triple, the linker architecture and the linker platform, so each fact about a target lives in one match arm. The device constant and the four string constants are gone, and the plan test compares a target to IosTarget::Device by equality. The simulator architecture rides as data rather than as two flat variants because the duplication gate treats a three-arm match from an enum to string literals as a structural twin of Profile::directory. The payload also states the real structure: one device target, and one simulator platform with two architectures. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
juanky201271
approved these changes
Oct 15, 2026
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
The Swift wire-contract test of PR #2937 links both iOS static libraries into one test bundle. The linker prints 441
duplicate symbolwarnings. Each library carries its own copy of Rust'sstd,coreandalloc. Every consumer that links both libraries sees the same warnings.One static library cannot hold both crates. The wallet crate and the proxy crate build in separate workspaces, because nym-sdk and the zcash crates hold conflicting dependencies on
crypto-common.Change
The iOS plan hides every symbol of each library except its UniFFI functions. It does this per target, after the bindgen step and before
lipoand the XCFramework steps.ld -r -force_loadlinks the cargo archive into one relocatable object. Two-exported_symbolpatterns on the command,_uniffi_<lib>_*and_ffi_<lib>_*, keep the UniFFI functions global. The linker marks every other global symbol private extern.libtool -staticpacks that object over the cargo archive.The link names its architecture and platform. Xcode 16.4's linker refuses an object with no
-arch, so the plan passes-archand-platform_versionper target. OneIosTargettable holds the triple, the architecture and the platform of each target. The plan, the toolchain pin and the tests all read that table.The bindgen step runs before the hide, so it reads the raw symbol table. Because the hidden archive takes the cargo archive's path,
lipoand the XCFramework steps do not change.A
Generationnow knows its library name. The hide step takes oneGenerationand derives the target directory, the archive and the export patterns from it.Tests
A plan test asserts the link and pack commands of every target, with their architecture, platform and export patterns. It asserts that the bindgen step precedes the link, that the link precedes the pack, and that every later step that reads the archive follows the pack.
Verification
cargo fmt,cargo check --all-targets,cargo clippy --all-targets -- -D warningsandcargo nextest runpass on the workbench crate.The per-PR iOS job on 991bcb5 built both XCFrameworks and linked the Swift contract test bundle. Its log holds no
duplicate symbolwarning, and the test reportsExecuted 3 tests, with 0 failures. That link proves the-exported_symbolwildcards and the in-place overwrite on Xcode 16.4.🤖 Generated with Claude Code