Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/reuse_datatrack_bytes_converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
livekit-uniffi: patch
livekit-datatrack: patch
livekit-common: patch
---

Register the `Bytes` UniFFI custom type once in `livekit-common` and borrow it from each component with `uniffi::use_remote_type!`, so the converter is emitted once and the post-generation Swift workaround is no longer needed
7 changes: 4 additions & 3 deletions .github/workflows/uniffi-swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:

- name: Publish to hosting repo
if: ${{ !inputs.dry_run }}
uses: livekit/publish-xcframework-action@200ee4984ef1b68068c0b3c4e87e798988730c90 # main @ fix: abort on missing source files (#3)
uses: livekit/publish-xcframework-action@a1bc3de909f9c0a2b7f92172a95d6dd35cb20b1a # main @ feat: allow path patterns as file sources (#5)
with:
xcframework-zip: ${{ env.OUTPUT_DIR }}/Rust${{ env.SPM_NAME }}.xcframework.zip
version: ${{ inputs.version }}
Expand All @@ -96,8 +96,9 @@ jobs:
${{ env.OUTPUT_DIR }}/${{ env.SPM_NAME }}.podspec:${{ env.SPM_NAME }}.podspec
${{ env.OUTPUT_DIR }}/LICENSE:LICENSE
${{ env.OUTPUT_DIR }}/PrivacyInfo.xcprivacy:PrivacyInfo.xcprivacy
${{ env.OUTPUT_DIR }}/Sources/${{ env.SPM_NAME }}/livekit_uniffi.swift:Sources/${{ env.SPM_NAME }}/livekit_uniffi.swift
${{ env.OUTPUT_DIR }}/Sources/${{ env.SPM_NAME }}/livekit_datatrack.swift:Sources/${{ env.SPM_NAME }}/livekit_datatrack.swift
# UniFFI emits one source per component, so match rather than list them:
# adding a component must not require a workflow change.
${{ env.OUTPUT_DIR }}/Sources/${{ env.SPM_NAME }}/*.swift:Sources/${{ env.SPM_NAME }}/
token: ${{ secrets.UNIFFI_XCFRAMEWORK_PAT }}
pr-body: |
Source tag: `${{ inputs.tag_name }}`
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions livekit-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ repository.workspace = true

[dependencies]
livekit-protocol = { workspace = true }
bytes = { workspace = true, optional = true }
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"], optional = true }

[features]
# Exposes this crate's shared FFI type registrations. Enabled transitively by
# livekit-uniffi so every component borrows one converter per type.
uniffi = ["dep:uniffi", "dep:bytes"]
29 changes: 29 additions & 0 deletions livekit-common/src/ffi_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Shared UniFFI type registrations.
//!
//! A remote type such as [`bytes::Bytes`] can only be registered once per UniFFI component:
//! `custom_type!` emits a `public` converter per component, and the generated Swift files are
//! compiled into a single module, so a second registration fails to build. Registering it here
//! and having each component borrow it with `uniffi::use_remote_type!` keeps exactly one
//! declaration no matter how many crates need the type.
//!
//! Owning a registration requires being a UniFFI component: `custom_type!` needs
//! `crate::UniFfiTag`, and bindgen rejects type metadata that belongs to no namespace
//! (`Unknown namespace for CustomType`). Hence the `setup_scaffolding!` in `lib.rs`.

use bytes::Bytes;

uniffi::custom_type!(Bytes, Vec<u8>, { remote });
8 changes: 8 additions & 0 deletions livekit-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ use livekit_protocol as proto;

mod enum_dispatch;

// Must sit at the crate root: it defines `crate::UniFfiTag`, which the registrations in
// `ffi_types` resolve against.
#[cfg(feature = "uniffi")]
uniffi::setup_scaffolding!();

#[cfg(feature = "uniffi")]
mod ffi_types;

// -------------------------------------------------------------------------------------------------
// Client protocol
// -------------------------------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions livekit-common/uniffi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[bindings.swift]
# Matches the Rust<Name> convention used by the other components; the cargo-swift
# fork folds every component's header into the single RustLiveKitUniFFI framework.
ffi_module_name = "RustLiveKitCommon"
3 changes: 2 additions & 1 deletion livekit-datatrack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository.workspace = true

[dependencies]
livekit-protocol = { workspace = true }
livekit-common = { workspace = true, optional = true }
livekit-runtime = { workspace = true, features = ["tokio"] }
log = { workspace = true }
thiserror = { workspace = true }
Expand All @@ -25,7 +26,7 @@ uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"], optional
indexmap = "2"

[features]
uniffi = ["dep:uniffi"]
uniffi = ["dep:uniffi", "dep:livekit-common", "livekit-common/uniffi"]
__fuzz = ["dep:fake"]

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion livekit-datatrack/src/e2ee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub trait DecryptionProvider: Send + Sync + Debug {
}

#[cfg(feature = "uniffi")]
uniffi::custom_type!(Bytes, Vec<u8>, { remote });
uniffi::use_remote_type!(livekit_common::Bytes);

#[cfg(feature = "uniffi")]
uniffi::custom_type!(InitializationVector, Vec<u8>, {
Expand Down
1 change: 1 addition & 0 deletions livekit-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ publish = false

[dependencies]
livekit-protocol = { workspace = true }
livekit-common = { workspace = true, features = ["uniffi"] }
livekit-api = { workspace = true, default-features = false, features = ["access-token"] }
livekit-datatrack = { workspace = true, features = ["uniffi"] }
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns", "tokio"] }
Expand Down
21 changes: 1 addition & 20 deletions livekit-uniffi/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -475,24 +475,6 @@ mkdir -p "${out_dir}"
mv "${SPM_NAME}" "${out_dir}"
"""

# Post-generation Swift workarounds for issues cargo-swift / the templates don't handle.
# Currently: the `Bytes` custom type is registered in both crates, so uniffi emits its
# converter in both component Swift files, which collide in one module ("invalid
# redeclaration of 'Bytes'"). Drop the duplicate from livekit_datatrack.swift.
# Upstream: https://github.com/mozilla/uniffi-rs/issues/2933. Idempotent.
[tasks.swift-workarounds]
private = true
script_runner = "@shell"
script = '''
file="${PACKAGES_DIR}/swift/${SPM_NAME}/Sources/${SPM_NAME}/livekit_datatrack.swift"
if [ ! -f "$file" ]; then
echo "swift-workarounds: $file not found, skipping"
exit 0
fi
perl -0pi -e 's{public typealias Bytes = Data.*?public func FfiConverterTypeBytes_lower\(_ value: Bytes\) -> RustBuffer \{\n return FfiConverterTypeBytes\.lower\(value\)\n\}}{// cargo-make swift-workarounds: duplicate Bytes converter removed (kept in livekit_uniffi.swift)}s' "$file"
echo "swift-workarounds: ensured single Bytes converter in $file"
'''

[tasks.swift-package-flow]
private = true
dependencies = [
Expand All @@ -501,8 +483,7 @@ dependencies = [
"swift-check-size",
"swift-zip-xcframework",
"swift-generate-manifest",
"swift-move-to-packages",
"swift-workarounds"
"swift-move-to-packages"
]

[tasks.swift-package]
Expand Down
6 changes: 5 additions & 1 deletion livekit-uniffi/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@

use bytes::Bytes;

uniffi::custom_type!(Bytes, Vec<u8>, { remote });
// `Bytes` is registered once in livekit-common so every component that needs it borrows the
// same converter; registering it here as well would emit a second `public typealias Bytes`
// into this component's Swift file, and both files compile into one module.
// Upstream: https://github.com/mozilla/uniffi-rs/issues/2933
uniffi::use_remote_type!(livekit_common::Bytes);
Loading