From 119e1ebb8f198b546d882a4e6be99276719c783d Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:10:54 +0900 Subject: [PATCH 1/4] Pin uniffi-dart to the multi-crate codegen fixes and re-enable Dart tests The upstream rev pinned by #1183 predates data tracks, so the bindgen emitted analysis errors (Bytes vs NativeType) for the multi-crate surface and the Dart tests have been disabled since #1034. Pin the fork rev that fixes multi-crate, custom-type and name-collision codegen; the pin is temporary until upstream merges the fixes or a livekit fork exists, see the Cargo.toml comment. Verified locally: cargo make dart-package passes all 5 FFI tests across the FFI boundary. --- .github/workflows/uniffi-dart-test.yml | 2 -- Cargo.lock | 8 ++++---- livekit-uniffi/Cargo.toml | 6 +++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/uniffi-dart-test.yml b/.github/workflows/uniffi-dart-test.yml index 1e694562b..d5b1aefc5 100644 --- a/.github/workflows/uniffi-dart-test.yml +++ b/.github/workflows/uniffi-dart-test.yml @@ -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: diff --git a/Cargo.lock b/Cargo.lock index 5da102a03..7bc464ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8093,8 +8093,8 @@ dependencies = [ [[package]] name = "uniffi-dart" -version = "0.1.0+v0.30.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart?rev=90f2c6f29cbf88c8bc2cf515e6a0c2314a48844c#90f2c6f29cbf88c8bc2cf515e6a0c2314a48844c" +version = "0.2.1+v0.31.2" +source = "git+https://github.com/1egoman/uniffi-dart?rev=ee04fd038659a9bd1438cbd297707861a55fd2f7#ee04fd038659a9bd1438cbd297707861a55fd2f7" dependencies = [ "anyhow", "camino", @@ -8164,8 +8164,8 @@ dependencies = [ [[package]] name = "uniffi_dart_macro" -version = "0.1.0+v0.30.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart?rev=90f2c6f29cbf88c8bc2cf515e6a0c2314a48844c#90f2c6f29cbf88c8bc2cf515e6a0c2314a48844c" +version = "0.2.1+v0.31.2" +source = "git+https://github.com/1egoman/uniffi-dart?rev=ee04fd038659a9bd1438cbd297707861a55fd2f7#ee04fd038659a9bd1438cbd297707861a55fd2f7" dependencies = [ "futures", "proc-macro2", diff --git a/livekit-uniffi/Cargo.toml b/livekit-uniffi/Cargo.toml index 665f2ca55..d4c42b4eb 100644 --- a/livekit-uniffi/Cargo.toml +++ b/livekit-uniffi/Cargo.toml @@ -27,7 +27,11 @@ thiserror = { workspace = true } # Dart binding generator. Not published to crates.io, so pinned by git rev. The # rev must target the same uniffi-rs release (0.31) as the `uniffi` dependency # above, or it cannot read this crate's compiled metadata. -uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart", rev = "90f2c6f29cbf88c8bc2cf515e6a0c2314a48844c", optional = true } +# TEMPORARY: pinned to the multi-crate codegen fixes on ryangaus's fork +# (upstream is dormant; PR Uniffi-Dart/uniffi-dart#150 is one of them). Move +# the pin back to upstream once merged, or to a livekit-maintained fork if a +# release needs cutting before that (CLT-2872). +uniffi-dart = { git = "https://github.com/1egoman/uniffi-dart", rev = "ee04fd038659a9bd1438cbd297707861a55fd2f7", optional = true } camino = { version = "1", optional = true } [features] From 2c299132af4865a32c3bafed71de0cdbcd8a79a9 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:11:04 +0900 Subject: [PATCH 2/4] Make the generated livekit_uniffi Dart package publishable Split the dart-package flow by cargo-make profile: dev builds keep the embedded host dylib and publish_to: none, release builds omit the dylib (consumers use the hook's download mode) and clear publish_to so pub.dev accepts the package. The publish_to guard fails closed, an unknown profile still renders none. A dart-clean task starts every build from scratch so a release build over a previous dev tree cannot ship the stale host dylib, which the hook would prefer over download on every platform. Also add the metadata pub.dev requires or scores: LICENSE, README, CHANGELOG, repository/homepage/issue_tracker pubspec fields, real version constraints for code_assets/hooks (wide ranges, flutter_test pins meta exactly so carets break older stable channels), and an analysis_options.yaml so unused-import lints in generated code do not fail publish validation. Verified: both profiles build, all 5 FFI tests pass on the dev package, dart pub publish --dry-run exits 0 on the release package, and a release build over a dev tree contains no dylib. --- livekit-uniffi/Makefile.toml | 54 +++++++++++++++++-- livekit-uniffi/support/dart/README.md | 40 ++++++++++++++ .../support/dart/analysis_options.yaml | 8 +++ livekit-uniffi/support/dart/pubspec.yaml.tera | 24 +++++++-- 4 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 livekit-uniffi/support/dart/README.md create mode 100644 livekit-uniffi/support/dart/analysis_options.yaml diff --git a/livekit-uniffi/Makefile.toml b/livekit-uniffi/Makefile.toml index ec3e57746..5516b2a8e 100644 --- a/livekit-uniffi/Makefile.toml +++ b/livekit-uniffi/Makefile.toml @@ -20,12 +20,20 @@ TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}" SPM_NAME = "LiveKitUniFFI" NPM_NAME = "@livekit/uniffi" DART_PACKAGE_NAME = "livekit_uniffi" +# Rendered into pubspec.yaml by dart-generate-manifest. Fail closed: every +# profile gets `publish_to: none` (dev packages embed a host dylib and must +# never reach pub.dev), and only an explicit release build clears it so +# `dart pub publish` accepts the package. +PUBSPEC_PUBLISH_TO = "none" # Per-release directory holding the cdylib archives published by # .github/workflows/uniffi-cdylib.yml. Downloaders append # `/v/build-.zip` (and a `.sha256` sidecar). CDYLIB_DOWNLOAD_BASE_URL = "https://github.com/livekit/rust-sdks/releases/download/livekit-uniffi" +[env.release] +PUBSPEC_PUBLISH_TO = "" + [config] skip_core_tasks = true @@ -697,14 +705,33 @@ exec cargo run --features dart-bindgen --bin uniffi-bindgen-dart -- \ """ # Assemble a consumable Dart package around the generated bindings: idiomatic -# lib/ layout, a pubspec, the Native Assets build hook, and (dev) the locally -# built dynamic library so a consumer can load it without a download step. +# lib/ layout, a pubspec, the Native Assets build hook, package metadata, and +# (dev only) the locally built dynamic library so a consumer can load it +# without a download step. Release builds omit the dylib, so consumers use the +# hook's download mode against the release assets. # # packages/dart/ # pubspec.yaml (from support/dart/pubspec.yaml.tera) # lib/livekit_uniffi.dart # hook/build.dart (from support/dart/hook/build.dart) -# liblivekit_uniffi. (dev: copied local build) +# LICENSE, README.md, CHANGELOG.md +# liblivekit_uniffi. (dev only: copied local build) +# +# Publishing note: pub builds its archive from git's file listing and this +# whole tree is gitignored, so `dart pub publish` must run from a copy of +# packages/dart placed outside the repository (uniffi-dart-publish.yml does +# this; do the same for a manual publish). + +# Start from a clean slate: leftovers from a previous build with a different +# profile would otherwise survive into this one. The dev-profile dylib is the +# dangerous case, a release package must never carry it (the build hook +# prefers a package-root library over download, on every platform). +[tasks.dart-clean] +private = true +script_runner = "@shell" +script = """ +rm -rf ${PACKAGES_DIR}/${LANG} +""" [tasks.dart-move-code-into-lib] private = true @@ -733,14 +760,31 @@ extend = "tera" args = ["--env-only", "-t", "${SUPPORT_DIR}/${LANG}/hook/build.dart.tera", "-o", "${PACKAGES_DIR}/${LANG}/hook/build.dart"] # Dev delivery: copy the locally built dylib into the package root, where the -# build hook resolves it. A release flow would replace this with a download. +# build hook resolves it. Development profile only. A published package must +# not contain the host dylib, its consumers get the right library per target +# from the hook's download mode instead. [tasks.dart-copy-lib] private = true +condition = { profiles = ["development"] } script_runner = "@shell" script = """ cp ${LIB_PATH} ${PACKAGES_DIR}/${LANG}/ """ +# Package metadata: pub.dev requires a LICENSE and scores README/CHANGELOG. +# The LICENSE comes from the repo root (same source as the Swift package), +# the changelog from the crate, and the README and analysis options from +# support/dart. +[tasks.dart-copy-meta] +private = true +script_runner = "@shell" +script = """ +cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/LICENSE ${PACKAGES_DIR}/${LANG}/ +cp ${SUPPORT_DIR}/${LANG}/README.md ${PACKAGES_DIR}/${LANG}/ +cp ${SUPPORT_DIR}/${LANG}/analysis_options.yaml ${PACKAGES_DIR}/${LANG}/ +cp CHANGELOG.md ${PACKAGES_DIR}/${LANG}/ +""" + [tasks.dart-copy-tests] private = true script_runner = "@shell" @@ -752,11 +796,13 @@ cp ${SUPPORT_DIR}/${LANG}/test/*.dart ${PACKAGES_DIR}/${LANG}/test/ [tasks.dart-package-flow] private = true dependencies = [ + "dart-clean", "bindgen-dart", "dart-move-code-into-lib", "dart-generate-manifest", "dart-copy-hook", "dart-copy-lib", + "dart-copy-meta", "dart-copy-tests" ] diff --git a/livekit-uniffi/support/dart/README.md b/livekit-uniffi/support/dart/README.md new file mode 100644 index 000000000..90fd7cb32 --- /dev/null +++ b/livekit-uniffi/support/dart/README.md @@ -0,0 +1,40 @@ +# livekit_uniffi + +Dart bindings for the LiveKit Rust SDK core, generated from the +[`livekit-uniffi`](https://github.com/livekit/rust-sdks/tree/main/livekit-uniffi) +crate with [UniFFI](https://mozilla.github.io/uniffi-rs/) and +[uniffi-dart](https://github.com/Uniffi-Dart/uniffi-dart). + +This is a low-level package. It is consumed by +[`livekit_client`](https://pub.dev/packages/livekit_client) and is not +intended to be used directly in applications. + +## How it works + +The bindings call into a prebuilt Rust dynamic library through Dart's +[Native Assets](https://dart.dev/tools/hooks). The package's `hook/build.dart` +resolves the library for your target platform at build time, downloading it +from the matching [`livekit-uniffi` release](https://github.com/livekit/rust-sdks/releases) +and verifying its SHA-256 checksum. No Rust toolchain is required to consume +this package. + +Requires Dart >= 3.10 (Flutter >= 3.38), where build hooks and code assets are +stable. There is no web support: guard usage behind a conditional import. + +## Supported platforms + +macOS (arm64, x64), iOS (device and simulator), Android (arm64, armv7, x64), +Linux (arm64, x64), and Windows (arm64, x64). + +## Development + +This package is generated; do not edit it directly. To build it from source: + +```sh +cd rust-sdks/livekit-uniffi +cargo make dart-package # generates packages/dart with a locally built library +``` + +See the [crate README](https://github.com/livekit/rust-sdks/tree/main/livekit-uniffi) +for details. Issues and pull requests go to +[livekit/rust-sdks](https://github.com/livekit/rust-sdks). diff --git a/livekit-uniffi/support/dart/analysis_options.yaml b/livekit-uniffi/support/dart/analysis_options.yaml new file mode 100644 index 000000000..74aa836c5 --- /dev/null +++ b/livekit-uniffi/support/dart/analysis_options.yaml @@ -0,0 +1,8 @@ +# The package contents are generated by uniffi-dart, which emits imports and +# helpers that are not always used by the exported surface. Silence those +# lints so pub.dev validation stays clean; real analysis errors still fail. +analyzer: + errors: + unused_import: ignore + unnecessary_import: ignore + unused_element: ignore diff --git a/livekit-uniffi/support/dart/pubspec.yaml.tera b/livekit-uniffi/support/dart/pubspec.yaml.tera index bfd6a7ddb..443d7fea3 100644 --- a/livekit-uniffi/support/dart/pubspec.yaml.tera +++ b/livekit-uniffi/support/dart/pubspec.yaml.tera @@ -1,16 +1,30 @@ name: {{ DART_PACKAGE_NAME }} -description: Dart bindings for the LiveKit UniFFI interface. +description: Dart bindings for the LiveKit Rust SDK core, delivered as a prebuilt native library via Native Assets. version: {{ CARGO_MAKE_CRATE_VERSION }} -publish_to: none +repository: https://github.com/livekit/rust-sdks +issue_tracker: https://github.com/livekit/rust-sdks/issues +homepage: https://livekit.io/ +{#- Dev packages embed a host dylib and must never be published; release + packages omit publish_to so pub.dev accepts them. Fails closed: the base + [env] in Makefile.toml sets "none" for every profile and only + [env.release] clears it. #} +{%- if PUBSPEC_PUBLISH_TO %} +publish_to: {{ PUBSPEC_PUBLISH_TO }} +{%- endif %} environment: sdk: ">=3.10.0 <4.0.0" dependencies: ffi: ^2.1.0 - # Native Assets build-hook support. `any` tracks the SDK-bundled versions. - code_assets: any - hooks: any + # Native Assets build-hook support. pub.dev requires bounded constraints + # (`any` fails publish validation), but these must stay wide: the resolvable + # version is coupled to the consumer's Dart SDK via a shared `meta` pin + # (Flutter 3.44 back-solves to code_assets 1.0.0 / hooks 1.0.2, while a bare + # Dart 3.12 resolves 1.2.1 / 2.1.0). Carets here break `flutter pub get` + # for consumers on older stable channels. + code_assets: ">=1.0.0 <2.0.0" + hooks: ">=1.0.2 <3.0.0" # Used by hook/build.dart to download and verify prebuilt libraries. archive: ^4.0.0 crypto: ^3.0.0 From d131b1effa45a9ab6b57b1940850bc318419a7f3 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:28:53 +0900 Subject: [PATCH 3/4] Attach Dart/Flutter cdylib assets to livekit-uniffi releases Wire the existing uniffi-cdylib.yml into uniffi-packages.yml so every livekit-uniffi release carries the build-.zip archives (and sha256 sidecars) the Dart build hook downloads at consumer build time. The assets attach to the already-published release, so the knope assets marker stays off (a draft-based flow stranded releases before, #1256). Since this activates the previously dormant uniffi-cdylib.yml on every release, also bind its tag name through env instead of template expanding it into the upload script: tag names may contain shell metacharacters (Actions script injection). The changeset cuts the release that carries the first assets. --- .changeset/uniffi_dart_release_automation.md | 5 ++++ .github/workflows/uniffi-cdylib.yml | 6 ++++- .github/workflows/uniffi-packages.yml | 24 ++++++++++++++++---- knope.toml | 5 ++-- 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 .changeset/uniffi_dart_release_automation.md diff --git a/.changeset/uniffi_dart_release_automation.md b/.changeset/uniffi_dart_release_automation.md new file mode 100644 index 000000000..f1f9d1e65 --- /dev/null +++ b/.changeset/uniffi_dart_release_automation.md @@ -0,0 +1,5 @@ +--- +livekit-uniffi: patch +--- + +Attach Dart/Flutter cdylib assets to releases and prepare livekit_uniffi for pub.dev publishing diff --git a/.github/workflows/uniffi-cdylib.yml b/.github/workflows/uniffi-cdylib.yml index dc5c4ba08..eaffecc56 100644 --- a/.github/workflows/uniffi-cdylib.yml +++ b/.github/workflows/uniffi-cdylib.yml @@ -167,6 +167,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 diff --git a/.github/workflows/uniffi-packages.yml b/.github/workflows/uniffi-packages.yml index 0ebab424c..a03c76062 100644 --- a/.github/workflows/uniffi-packages.yml +++ b/.github/workflows/uniffi-packages.yml @@ -1,16 +1,18 @@ name: UniFFI packages # Publishes the per-language livekit-uniffi wrapper packages (Swift xcframework → -# livekit/livekit-uniffi-xcframework; Android AAR → Maven Central) when a -# livekit-uniffi release is published. +# livekit/livekit-uniffi-xcframework; Android AAR → Maven Central) and attaches +# the Dart/Flutter cdylib archives to the release when a livekit-uniffi release +# is published. # # knope publishes the `livekit-uniffi/v*` GitHub release (and its git tag) # directly on the release merge — no draft — so this reacts to the published # 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 itself is published from uniffi-dart-publish.yml (tag-push +# triggered, as pub.dev requires); it waits for the cdylib assets this workflow +# attaches, since the package's build hook downloads them at consumer build time. on: release: @@ -64,3 +66,17 @@ jobs: tag_name: ${{ needs.resolve-tag.outputs.tag_name }} dry_run: ${{ inputs.dry_run || false }} secrets: inherit + + # Attaches build-.zip (+ .sha256) for every Dart/Flutter target to the + # release. The livekit_uniffi package's hook/build.dart downloads these at + # consumer build time, so a release consumed from pub.dev must carry them. + 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 }} diff --git a/knope.toml b/knope.toml index 4e8783902..182f2f394 100644 --- a/knope.toml +++ b/knope.toml @@ -130,8 +130,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] From c3437557d994ceddeda8e6988d78955c978e4ca8 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:11:25 +0900 Subject: [PATCH 4/4] Add a tag-push workflow publishing livekit_uniffi to pub.dev pub.dev's automated publishing only accepts workflows triggered by a push of a tag matching the configured pattern, so this cannot be a job in the release-event-triggered uniffi-packages.yml. The workflow builds the release-profile package (cached, before the asset wait), stages it outside the work tree (pub archives from git's file listing and the generated packages/ tree is gitignored), refuses to publish a package containing a local native library, gates on every cdylib zip having its sha256 sidecar, and fast-fails on mis-pointed tags or a missing release. Tag names are env-bound, never template-expanded into scripts, since the job holds id-token: write for pub.dev token minting. PUBLISHING IS NOT ENABLED YET (PUBLISH_ENABLED=false): every run stops at dart pub publish --dry-run until the uniffi-dart pin moves off the personal fork, a livekit.io publisher admin has done the first manual publish, and automated publishing is configured on pub.dev. See the header for the full enablement steps. --- .github/workflows/uniffi-dart-publish.yml | 203 ++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 .github/workflows/uniffi-dart-publish.yml diff --git a/.github/workflows/uniffi-dart-publish.yml b/.github/workflows/uniffi-dart-publish.yml new file mode 100644 index 000000000..f5454ffaa --- /dev/null +++ b/.github/workflows/uniffi-dart-publish.yml @@ -0,0 +1,203 @@ +name: UniFFI Dart package + +# Publishes the generated livekit_uniffi Dart package to pub.dev on a +# livekit-uniffi release. +# +# 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-.zip assets from the +# GitHub release at consumer build time. Those assets are attached by the +# cdylib job in uniffi-packages.yml, which runs from the same release, so this +# workflow waits for them before publishing: a pub.dev version whose assets +# are missing would be broken on arrival, and pub.dev versions cannot be +# unpublished. +# +# PUBLISHING IS NOT ENABLED YET. Until the steps below are done, every run +# stops after `dart pub publish --dry-run`. Things 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), and that the asset wait sees the cdylib job's uploads. +# Enablement steps: +# 1. The temporary uniffi-dart pin on ryangaus's personal fork (see +# livekit-uniffi/Cargo.toml) has moved to upstream or a livekit fork, +# so releases don't depend on a personal repo. +# 2. 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). +# 3. 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. + +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 + +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" + +permissions: + contents: read + +jobs: + build-and-publish: + name: Build & publish Dart package + runs-on: ubuntu-latest + permissions: + contents: read + # Exchanged for a temporary pub.dev token by dart-lang/setup-dart. + id-token: write + 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 this job 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" + + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + # Build the tagged source, not the dispatch ref (which defaults to main). + ref: ${{ steps.tag.outputs.tag_name }} + submodules: true + + # Fast fail on a mis-pointed tag before spending runner time: the tag + # version must match the crate version the checkout carries (it is also + # what the pubspec, and pub.dev's tag-pattern check, will see). + - 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 + + - 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: + sdk: stable + + - 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 + + # The cdylib job in uniffi-packages.yml attaches the per-target archives + # to the release after knope publishes it, so they can land up to an + # hour after this workflow starts; the build above does not need them, + # only the validate/publish steps do. The hook needs a + # build-.zip AND its .zip.sha256 sidecar per target, so gate on + # both. 12 targets; keep the count in sync with the matrix in + # uniffi-cdylib.yml. + # + # Fails fast when the release itself is missing (mis-tag, or knope never + # published). Recovery from a failed cdylib build: fix it, dispatch + # uniffi-packages.yml with this tag to attach the assets, then re-run + # this workflow's failed run (or dispatch it with the tag). + - name: Wait for cdylib release assets + timeout-minutes: 90 + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.tag.outputs.tag_name }} + run: | + expected=12 + missing=0 + while true; do + if ! assets=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '[.assets[].name]'); then + missing=$((missing + 1)) + echo "release $TAG not found ($missing/10)" + if [ "$missing" -ge 10 ]; then + echo "Release $TAG does not exist; is the tag mis-pointed or the release unpublished?" >&2 + exit 1 + fi + sleep 60 + continue + fi + missing=0 + zips=$(echo "$assets" | jq '[.[] | select(test("^build-.*\\.zip$"))] | length') + sums=$(echo "$assets" | jq '[.[] | select(test("^build-.*\\.zip\\.sha256$"))] | length') + echo "cdylib assets on $TAG: $zips/$expected zips, $sums/$expected sha256 sidecars" + if [ "$zips" -ge "$expected" ] && [ "$sums" -ge "$zips" ]; then + break + fi + sleep 120 + done + + - 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