Skip to content

fix: support multi-crate (multi-component) frameworks#101

Open
pblazej wants to merge 1 commit into
antoniusnaumann:mainfrom
livekit:fix/multi-crate-framework
Open

fix: support multi-crate (multi-component) frameworks#101
pblazej wants to merge 1 commit into
antoniusnaumann:mainfrom
livekit:fix/multi-crate-framework

Conversation

@pblazej

@pblazej pblazej commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

cargo swift package breaks when the built library comprises more than one UniFFI
component
(multiple crates that each call setup_scaffolding!()). uniffi-bindgen --library emits one FFI header, module.modulemap, and Swift file per component, but the
dynamic-framework packaging only handles the single-component case, producing a framework
that won't compile or import.

This PR makes the dynamic .framework path multi-component aware:

  • One Clang module exposing every component header. A dynamic .framework only exposes
    the module whose name matches the bundle, so several framework module blocks in one
    bundle leave all but one unimportable. Instead a single module lists all component headers
    (UniFFI's UNIFFI_SHARED_H / UNIFFI_FFIDEF_* include guards deduplicate the shared
    runtime types), and each generated Swift file's FFI import is repointed at it.
  • Deterministic framework name. The primary component is derived from the target crate's
    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

Testing

Extended testing/end-to-end/package-dynamic.swift with a second crate so the fixture
exercises 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 the
built framework. The -F check matters because Xcode resolves framework modules by bundle
name and catches the breakage that swift build — which loads the modulemap directly — does
not.

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>
@pblazej pblazej force-pushed the fix/multi-crate-framework branch from 8d5c7d9 to 439269c Compare June 26, 2026 08:47
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>
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