Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/uniffi_dart_release_automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
livekit-uniffi: patch
---

Attach Dart/Flutter cdylib assets to releases and prepare livekit_uniffi for pub.dev publishing
1 change: 1 addition & 0 deletions .github/actions/uniffi-deps/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Install Common Deps for UniFFI Tasks
description: Installs the tooling shared by the UniFFI packaging workflows (cargo-make)
runs:
using: composite
steps:
Expand Down
70 changes: 50 additions & 20 deletions .github/workflows/uniffi-cdylib.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ name: UniFFI cdylib builds
# `.sha256` sidecar). The Dart build hook (support/dart/hook/build.dart.tera)
# downloads these at consumer build time.
#
# Reusable workflow, invoked from uniffi-packages.yml once the release tag has
# been resolved. Mirrors the structure of ffi-builds.yml. Not triggered directly.
# Reusable workflow, invoked from uniffi-dart-publish.yml once the release tag
# has been resolved, so the Dart publish job can depend on the assets being
# attached. Mirrors the structure of ffi-builds.yml. Not triggered directly.

on:
workflow_call:
Expand Down Expand Up @@ -53,9 +54,11 @@ jobs:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux
- os: ubuntu-latest
build_image: quay.io/pypa/manylinux_2_28_x86_64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
platform: linux
build_image: quay.io/pypa/manylinux_2_28_aarch64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows
Expand All @@ -75,9 +78,15 @@ jobs:
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# Build the tagged source, not the ref the workflow was dispatched
# from. A manual re-run for an older release would otherwise upload
# libraries built from main under that release's version.
ref: ${{ inputs.tag_name }}
submodules: true

- name: Setup Rust toolchain
# Linux builds run inside a container with their own toolchain.
if: ${{ matrix.platform != 'linux' }}
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
with:
target: ${{ matrix.target }}
Expand All @@ -92,28 +101,45 @@ jobs:
shell: bash
run: cargo build --release --target ${{ matrix.target }} -p livekit-uniffi

# Linux: native build for x64; cross toolchain for aarch64.
# Linux: build inside a manylinux_2_28 container so the shared library
# links against glibc 2.28 and loads on the distributions the Dart package
# is used on (Debian 12, Ubuntu 22.04, Amazon Linux 2023 and newer). A
# native build on the runner would inherit its glibc 2.39 floor. Same
# approach as ffi-builds.yml. The arm64 entry runs on a native arm64
# runner, so no cross toolchain is needed, and livekit-uniffi has no C
# dependencies, so the image's stock gcc is enough. The toolchain version
# comes from rust-toolchain.toml.
- name: Build (Linux)
if: ${{ matrix.platform == 'linux' }}
env:
TARGET: ${{ matrix.target }}
BUILD_IMAGE: ${{ matrix.build_image }}
run: |
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
# .cargo/config.toml forces -fuse-ld=lld for this target to link
# libwebrtc; livekit-uniffi doesn't use libwebrtc. A non-empty global
# RUSTFLAGS overrides that per-target config, selecting the GNU bfd
# linker the cross binutils provides.
export RUSTFLAGS="-C link-arg=-fuse-ld=bfd"
fi
cargo build --release --target ${{ matrix.target }} -p livekit-uniffi
docker run --rm -e TARGET -v "$PWD:/workspace" -w /workspace "$BUILD_IMAGE" bash -euo pipefail -c '
curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none
export PATH="/root/.cargo/bin:$PATH"
if [ "$TARGET" = "aarch64-unknown-linux-gnu" ]; then
# .cargo/config.toml forces -fuse-ld=lld for this target to link
# libwebrtc, which livekit-uniffi does not use. A non-empty global
# RUSTFLAGS overrides that per-target config, selecting the GNU
# bfd linker the image provides.
export RUSTFLAGS="-C link-arg=-fuse-ld=bfd"
fi
cargo build --release --target "$TARGET" -p livekit-uniffi
'
# The container ran as root, so hand the outputs back to the runner user.
sudo chown -R "$USER:$USER" target

# Android: build with cargo-ndk. Installed as a prebuilt binary; a
# source build costs 3-5 minutes per matrix job on the release path.
- name: Install cargo-ndk
if: ${{ matrix.platform == 'android' }}
uses: taiki-e/install-action@682e7d9e49c5e653d371fc6adbda67653461378a # v2.82.4
with: { tool: cargo-ndk }

# Android: build with cargo-ndk.
- name: Build (Android)
if: ${{ matrix.platform == 'android' }}
run: |
cargo install cargo-ndk
cargo ndk --target ${{ matrix.target }} build --release -p livekit-uniffi
run: cargo ndk --target ${{ matrix.target }} build --release -p livekit-uniffi

# macOS/Linux: zip + shasum are available.
- name: Package artifact (Unix)
Expand Down Expand Up @@ -167,6 +193,10 @@ jobs:
path: cdylibs

- name: Upload to release
# TAG_NAME is bound through env rather than template-expanded into the
# script: tag names may contain shell metacharacters (Actions script
# injection). GITHUB_REPOSITORY is provided by the runner already.
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ inputs.tag_name }}" cdylibs/* --repo "${{ github.repository }}" --clobber
TAG_NAME: ${{ inputs.tag_name }}
run: gh release upload "$TAG_NAME" cdylibs/* --repo "$GITHUB_REPOSITORY" --clobber
201 changes: 201 additions & 0 deletions .github/workflows/uniffi-dart-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
name: UniFFI Dart package

# Publishes the generated livekit_uniffi Dart package to pub.dev on a
# livekit-uniffi release, together with the native libraries it loads.
#
# This is a separate workflow from uniffi-packages.yml because pub.dev's
# automated publishing only accepts workflows triggered by a tag push matching
# the tag pattern configured on the package (livekit-uniffi/v{{version}});
# a release-event-triggered workflow is rejected. knope-bot creates the tag via
# the API, and App-created events do trigger workflows here (see the note in
# uniffi-packages.yml / #1256).
#
# The package's hook/build.dart downloads build-<triple>.zip assets from the
# GitHub release at consumer build time, so a pub.dev version whose assets are
# missing would be broken on arrival, and pub.dev versions cannot be
# unpublished. The cdylib job below builds and attaches those assets in this
# same run, and the publish job depends on it, so the package can only be
# published once every asset is on the release.
#
# PUBLISHING IS NOT ENABLED YET. Until the steps below are done, every run
# stops after `dart pub publish --dry-run`. The first live tag confirms that
# knope-bot's API-created tag fires this push trigger at all (expected for App
# events, never exercised here; workflow_dispatch is the fallback).
# Enablement steps:
# 1. A first manual `dart pub publish` by a livekit.io publisher admin has
# created the package on pub.dev (pub.dev only automates existing packages).
# 2. Automated publishing from GitHub Actions is enabled in the package's
# pub.dev admin settings for livekit/rust-sdks with tag pattern
# `livekit-uniffi/v{{version}}`, and PUBLISH_ENABLED below is flipped.
#
# Recovery: if a run fails, fix the cause and re-run the failed jobs, or
# dispatch this workflow with the tag. A dispatch rebuilds and re-attaches the
# assets (with --clobber) and re-validates the package, but never publishes,
# since pub.dev rejects OIDC tokens from dispatch runs. Set dry_run to skip the
# asset upload when only exercising the workflow.

on:
push:
tags: ["livekit-uniffi/v*"]
workflow_dispatch:
inputs:
tag_name:
description: "Release tag (e.g. livekit-uniffi/v0.1.9)"
required: true
type: string
dry_run:
description: "Build everything but skip the release asset upload."
type: boolean
default: false

env:
# Flip to "true" once the enablement steps in the header are done. Real
# publishing additionally requires a tag-push trigger; workflow_dispatch
# runs always stop at the dry run (pub.dev rejects their OIDC tokens).
PUBLISH_ENABLED: "false"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Might it make sense to turn this into a workflow input like some of the other workflows do?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An input would be inert here. pub.dev only accepts OIDC tokens from tag-push runs, which have no inputs, and dispatch runs can never publish regardless. Kept the env constant as the gate.


permissions:
contents: read

jobs:
resolve-tag:
runs-on: ubuntu-latest
outputs:
tag_name: ${{ steps.tag.outputs.tag_name }}
version: ${{ steps.tag.outputs.version }}
steps:
- name: Resolve tag
id: tag
# Bound through env, never template-expanded into the script: tag names
# and dispatch inputs may contain shell metacharacters (Actions script
# injection), and the outputs feed jobs that write release assets and
# can mint the pub.dev publish token.
env:
PUSHED_TAG: ${{ github.ref_name }}
INPUT_TAG: ${{ inputs.tag_name }}
run: |
TAG="${INPUT_TAG:-$PUSHED_TAG}"
case "$TAG" in
livekit-uniffi/v*) ;;
*) echo "Unexpected tag: $TAG"; exit 1 ;;
esac
echo "tag_name=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${TAG#livekit-uniffi/v}" >> "$GITHUB_OUTPUT"

# Fast fail on a mis-pointed tag before spending runner time on the
# cdylib matrix: the tag version must match the crate version the tag
# carries (it is also what the pubspec, and pub.dev's tag-pattern check,
# will see). Only the manifest is needed here.
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ steps.tag.outputs.tag_name }}
sparse-checkout: livekit-uniffi/Cargo.toml
sparse-checkout-cone-mode: false

- name: Check tag matches crate version
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
crate_version=$(grep -m1 '^version = ' livekit-uniffi/Cargo.toml | cut -d'"' -f2)
if [ "$crate_version" != "$VERSION" ]; then
echo "Tag version $VERSION does not match livekit-uniffi crate version $crate_version." >&2
exit 1
fi

# Builds build-<triple>.zip (+ .sha256) for every target and attaches them to
# the release. Runs here rather than in uniffi-packages.yml so the publish
# job can depend on it directly instead of polling the release for assets.
cdylib:
needs: resolve-tag
permissions:
# The upload step attaches assets to the release with the workflow token.
contents: write
uses: ./.github/workflows/uniffi-cdylib.yml
with:
version: ${{ needs.resolve-tag.outputs.version }}
tag_name: ${{ needs.resolve-tag.outputs.tag_name }}
dry_run: ${{ inputs.dry_run || false }}

build-and-publish:
name: Build & publish Dart package
needs: [resolve-tag, cdylib]
runs-on: ubuntu-latest
permissions:
contents: read
# Exchanged for a temporary pub.dev token by dart-lang/setup-dart.
id-token: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
# Build the tagged source, not the dispatch ref (which defaults to main).
ref: ${{ needs.resolve-tag.outputs.tag_name }}
submodules: true

- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
with:
cache: false
rustflags: ""

# Same keys as uniffi-dart-test.yml, so pushes to main keep them warm.
- name: Cache cargo registry
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-

- name: Cache cargo target
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: target/
key: ${{ runner.os }}-cargo-target-dart-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-target-dart-

- name: Install Common Deps for UniFFI Tasks
uses: ./.github/actions/uniffi-deps

- name: Setup Dart
uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
with:
# Pinned, and kept identical to uniffi-dart-test.yml, so PR CI
# validates the SDK the publish runs with. A floating channel could
# introduce a new lint that fails the dry run only on release day.
# Bump both together.
sdk: 3.13.1

- name: Build Dart package (release)
working-directory: livekit-uniffi
run: cargo make --profile release dart-package

# pub builds the publish archive from git's file listing, and the
# generated packages/ tree is gitignored, so publishing from inside the
# work tree produces an empty archive. Stage a copy outside it.
#
# The guard is a backstop for the dart-clean task: a package that ships
# a local native library would shadow the hook's download mode for every
# consumer, and pub.dev versions cannot be unpublished.
- name: Stage package outside the work tree
run: |
rm -rf "$RUNNER_TEMP/livekit_uniffi"
cp -R livekit-uniffi/packages/dart "$RUNNER_TEMP/livekit_uniffi"
if find "$RUNNER_TEMP/livekit_uniffi" \( -name 'liblivekit_uniffi.*' -o -name 'livekit_uniffi.dll' \) | grep -q .; then
echo "Staged package contains a local native library; refusing to publish." >&2
exit 1
fi

- name: Validate package
working-directory: ${{ runner.temp }}/livekit_uniffi
run: |
dart pub get
dart pub publish --dry-run

- name: Publish to pub.dev
if: ${{ env.PUBLISH_ENABLED == 'true' && github.event_name == 'push' }}
working-directory: ${{ runner.temp }}/livekit_uniffi
run: dart pub publish --force
8 changes: 5 additions & 3 deletions .github/workflows/uniffi-dart-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ permissions:

jobs:
dart-test:
# TODO: Fix tests then re-enable.
if: false
name: Dart package tests
runs-on: ubuntu-latest
steps:
Expand All @@ -44,7 +42,11 @@ jobs:
- name: Setup Dart
uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
with:
sdk: stable
# Pinned, and kept identical to uniffi-dart-publish.yml, so this job
# validates the SDK the publish will run with. A floating channel
# could introduce a new lint that fails the publish dry run only on
# release day. Bump both together.
sdk: 3.13.1

- name: Cache cargo registry
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/uniffi-packages.yml
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ name: UniFFI packages
# release event, which fires only for genuine livekit-uniffi releases (the gate
# is free). workflow_dispatch allows a manual re-run with an explicit tag.
#
# The Dart/Flutter cdylib target is experimental and not published for now; add a
# `cdylib` job (uniffi-cdylib.yml) back when it ships.
# The Dart package, and the cdylib release assets its build hook downloads, are
# handled by uniffi-dart-publish.yml. That workflow is tag-push triggered, as
# pub.dev requires, and builds the assets itself so publishing can depend on
# them directly.

on:
release:
Expand Down Expand Up @@ -40,8 +42,12 @@ jobs:
version: ${{ steps.get-tag.outputs.version }}
steps:
- id: get-tag
# TAG is bound through env rather than template-expanded into the
# script: tag names may contain shell metacharacters (Actions script
# injection).
env:
TAG: ${{ github.event.release.tag_name || inputs.tag_name }}
run: |
TAG="${{ github.event.release.tag_name || inputs.tag_name }}"
echo "tag_name=${TAG}" >> "$GITHUB_OUTPUT"
# Strip the "livekit-uniffi/v" prefix to get the package version (e.g. 0.1.6).
echo "version=${TAG#livekit-uniffi/v}" >> "$GITHUB_OUTPUT"
Expand Down
5 changes: 3 additions & 2 deletions knope.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ versioned_files = [
changelog = "livekit-uniffi/CHANGELOG.md"
# No `assets` marker: knope publishes the release + tag directly, and
# uniffi-packages.yml reacts to the published release to build/publish the Swift
# and Android wrapper packages. (Re-add the marker if the release itself needs
# to host assets, e.g. the Dart/Flutter cdylibs.)
# and Android wrapper packages. The Dart/Flutter cdylibs are attached to the
# already-published release by its cdylib job (gh release upload --clobber), so
# the marker stays off; a draft-based flow stranded releases before (#1256).
scopes = ["uniffi", "livekit-uniffi"]

[packages.livekit-wakeword]
Expand Down
Loading
Loading