fix: support multi-crate (multi-component) frameworks#101
Open
pblazej wants to merge 1 commit into
Open
Conversation
When a built cdylib comprises several UniFFI components (multiple crates that each call setup_scaffolding!), uniffi-bindgen --library emits one FFI header, modulemap, and Swift file per component. Packaged as a dynamic .framework, a bundle only exposes the Clang module whose name matches the bundle, so multiple `framework module` blocks in one bundle leave all but one unimportable — resolved by `swift build` (which loads the modulemap directly) but not by Xcode's framework-by-name resolution (antoniusnaumann#97, antoniusnaumann#99). The framework name was also derived from the first dependent crate by directory order rather than the target crate (antoniusnaumann#100). generate_bindings now collects every component's FFI module, selects the primary one (the module imported by the target crate's own Swift file), emits a single module exposing all component headers (UNIFFI_SHARED_H / UNIFFI_FFIDEF_* guards deduplicate the shared runtime types), and repoints each binding's import at it. The package-dynamic end-to-end test is extended to a multi-crate fixture and type-checks the generated sources with `swiftc -F` (Xcode-style framework resolution), which `swift build` cannot catch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8d5c7d9 to
439269c
Compare
pblazej
added a commit
to livekit/rust-sdks
that referenced
this pull request
Jun 26, 2026
The fork's fix/multi-crate-framework branch was rebased onto cargo-swift main as a single commit (open upstream as antoniusnaumann/cargo-swift#101) and no longer carries the uniffi_bindgen 0.31.2 bump — that went to its own PR (antoniusnaumann/cargo-swift#102). Re-pin the swift-xcframework task to the new revision. Without that bindgen bump the generated callback-interface vtables are plain `static let`, which the Swift 6 language mode rejects, so restore `swiftLanguageModes: [.v5]` in the package templates until cargo-swift ships uniffi 0.31.2. Give livekit-datatrack a uniffi.toml setting ffi_module_name = "RustLiveKitDataTrack" so its generated FFI header matches the RustLiveKitUniFFI naming instead of the default livekit_datatrackFFI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
cargo swift packagebreaks when the built library comprises more than one UniFFIcomponent (multiple crates that each call
setup_scaffolding!()).uniffi-bindgen --libraryemits one FFI header,module.modulemap, and Swift file per component, but thedynamic-framework packaging only handles the single-component case, producing a framework
that won't compile or import.
This PR makes the dynamic
.frameworkpath multi-component aware:.frameworkonly exposesthe module whose name matches the bundle, so several
framework moduleblocks in onebundle leave all but one unimportable. Instead a single module lists all component headers
(UniFFI's
UNIFFI_SHARED_H/UNIFFI_FFIDEF_*include guards deduplicate the sharedruntime types), and each generated Swift file's FFI import is repointed at it.
own generated Swift file rather than from directory iteration order, so the
framework/xcframework no longer take the name of an arbitrary dependency crate.
Resolves
Unable to resolve module dependency: '<crate>FFI'ffi_module_nameis not set in uniffi.toml #100 — invalid framework name whenffi_module_nameis unset (taken from the firstdependent crate instead of the target crate)
Testing
Extended
testing/end-to-end/package-dynamic.swiftwith a second crate so the fixtureexercises the multi-component path, plus regression checks: exactly one
framework module,at least two component Swift files, and a
swiftc -typecheck -F <slice>compile against thebuilt framework. The
-Fcheck matters because Xcode resolves framework modules by bundlename and catches the breakage that
swift build— which loads the modulemap directly — doesnot.