From d970c51adeb45c4b7febc2dcda0dd642aeeb1bcd Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 9 Sep 2026 23:56:58 +0800 Subject: [PATCH 1/3] Allow code_assets 2.x in the Dart package code_assets 2.0.0 shipped after the constraint was written. Its only breaking change, equality on OS and Architecture, does not affect the hook, and capping below it makes the package unresolvable next to any dependency that already requires 2.x. Verified against code_assets 2.0.0 and hooks 2.2.0 in both local and download mode. --- .changeset/dart_hook_code_assets_2.md | 15 +++++++++++++++ livekit-uniffi/support/dart/pubspec.yaml.tera | 16 ++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 .changeset/dart_hook_code_assets_2.md diff --git a/.changeset/dart_hook_code_assets_2.md b/.changeset/dart_hook_code_assets_2.md new file mode 100644 index 000000000..7edc77d36 --- /dev/null +++ b/.changeset/dart_hook_code_assets_2.md @@ -0,0 +1,15 @@ +--- +livekit-uniffi: patch +--- + +# Allow code_assets 2.x in the Dart package and stop re-running its build hook + +The `livekit_uniffi` Dart package capped `code_assets` below 2.0.0, which has since +shipped. Its only breaking change (equality on `OS` and `Architecture`) does not +affect the hook, and the cap would make the package unresolvable next to any +dependency that already requires 2.x. The constraint now allows it, and the hook +was verified against `code_assets 2.0.0` / `hooks 2.2.0`. + +The hook also registered the downloaded library as a dependency. Dependencies are +inputs, so the hooks runner saw a file modified during the build and re-ran the +hook, and the download, once on every fresh build. The registration is removed. diff --git a/livekit-uniffi/support/dart/pubspec.yaml.tera b/livekit-uniffi/support/dart/pubspec.yaml.tera index 443d7fea3..a7bfbb08d 100644 --- a/livekit-uniffi/support/dart/pubspec.yaml.tera +++ b/livekit-uniffi/support/dart/pubspec.yaml.tera @@ -18,12 +18,16 @@ environment: dependencies: ffi: ^2.1.0 # 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" + # (`any` fails publish validation), but these must stay wide. The resolvable + # version is coupled to the consumer's Dart SDK: older hooks releases pin + # `meta`, which Flutter's flutter_test pins exactly, so Flutter 3.44 + # back-solves to code_assets 1.0.0 / hooks 1.0.2 while a bare Dart 3.12 + # resolves newer ones. Carets here break `flutter pub get` for consumers on + # older stable channels, and a tight upper bound makes this package + # unresolvable next to any package that already requires the next major. + # The hook uses only the stable core of both APIs (target config, CodeAsset, + # DynamicLoadingBundled), which is unchanged across these majors. + code_assets: ">=1.0.0 <3.0.0" hooks: ">=1.0.2 <3.0.0" # Used by hook/build.dart to download and verify prebuilt libraries. archive: ^4.0.0 From b54ed6271aac995054160f9f9217b291ca6086da Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 9 Sep 2026 23:56:58 +0800 Subject: [PATCH 2/3] Stop registering the downloaded library as a hook dependency Dependencies are inputs. Registering the downloaded output made the hooks runner see a file modified during the build and rerun the hook, and the download, once on every fresh build. Local mode keeps its registration, since there the library really is an input that a fresh cargo make dart-package overwrites. --- livekit-uniffi/support/dart/hook/build.dart.tera | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/livekit-uniffi/support/dart/hook/build.dart.tera b/livekit-uniffi/support/dart/hook/build.dart.tera index 295825c56..fea639af3 100644 --- a/livekit-uniffi/support/dart/hook/build.dart.tera +++ b/livekit-uniffi/support/dart/hook/build.dart.tera @@ -71,8 +71,11 @@ Future _resolveLibrary( return local.uri; } - // Download mode: fetch the prebuilt library for the target. The hooks-runner - // already caches hook results across builds, so there's no manual cache check. + // Download mode: fetch the prebuilt library for the target. The hooks runner + // caches hook results across builds, so there is no manual cache check. The + // downloaded file is an output, not an input, so it is deliberately not + // registered as a dependency: doing so made the runner see a file modified + // during the build and rerun the hook (and the download) once per fresh build. final outDir = Directory.fromUri( input.outputDirectoryShared.resolve('$_cdylibName/'), ); @@ -89,7 +92,7 @@ Future _resolveLibrary( for (final entry in ZipDecoder().decodeBytes(bytes)) { if (!entry.isFile) continue; // Guard against zip-slip: `resolve` normalizes `../` and absolute paths, so - // an entry that lands outside `base` is a malicious name — reject it. The + // an entry that lands outside `base` is a malicious name, so reject it. The // SHA-256 check proves integrity against tampering in transit, not that a // compromised host can't serve a matching-checksum malicious archive. final entryUri = base.resolve(entry.name); @@ -104,7 +107,6 @@ Future _resolveLibrary( if (!await libFile.exists()) { throw Exception('$libName not found in $zipUrl'); } - output.dependencies.add(libFile.uri); return libFile.uri; } From f537f27da7dd5e37d1693e31af07eb78e7acee06 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:37:26 +0800 Subject: [PATCH 3/3] Keep each target's downloaded library in its own directory The hook wrote every target's library to the same path under the shared output directory. A universal macOS release build runs the hook once per architecture and then merges the results with lipo, so the second download overwrote the first and lipo was handed the same x86_64 file twice. Key the directory by target triple, which also matches how the hooks package documents the shared directory should be used. --- .changeset/dart_hook_code_assets_2.md | 5 +++++ livekit-uniffi/support/dart/hook/build.dart.tera | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/dart_hook_code_assets_2.md b/.changeset/dart_hook_code_assets_2.md index 7edc77d36..3888b4ced 100644 --- a/.changeset/dart_hook_code_assets_2.md +++ b/.changeset/dart_hook_code_assets_2.md @@ -13,3 +13,8 @@ was verified against `code_assets 2.0.0` / `hooks 2.2.0`. The hook also registered the downloaded library as a dependency. Dependencies are inputs, so the hooks runner saw a file modified during the build and re-ran the hook, and the download, once on every fresh build. The registration is removed. + +The hook also wrote every target's library to the same shared path. A universal +macOS build runs the hook once per architecture and then merges the results with +`lipo`, which failed because the second download had overwritten the first. Each +target now gets its own subdirectory. diff --git a/livekit-uniffi/support/dart/hook/build.dart.tera b/livekit-uniffi/support/dart/hook/build.dart.tera index fea639af3..b8c04cedb 100644 --- a/livekit-uniffi/support/dart/hook/build.dart.tera +++ b/livekit-uniffi/support/dart/hook/build.dart.tera @@ -76,12 +76,17 @@ Future _resolveLibrary( // downloaded file is an output, not an input, so it is deliberately not // registered as a dependency: doing so made the runner see a file modified // during the build and rerun the hook (and the download) once per fresh build. + // + // The shared directory is one per package, not per target, and a universal + // macOS build runs this hook once per architecture before lipo merges the + // results. Keep each target in its own subdirectory, or the second download + // overwrites the first and lipo is handed the same architecture twice. + final triple = _targetTriple(os, arch, iosSdk); final outDir = Directory.fromUri( - input.outputDirectoryShared.resolve('$_cdylibName/'), + input.outputDirectoryShared.resolve('$_cdylibName/$triple/'), ); final libFile = File.fromUri(outDir.uri.resolve(libName)); - final triple = _targetTriple(os, arch, iosSdk); // Asset naming matches the existing node downloader convention so one set of // release assets serves both: `/v/build-.zip`. final zipUrl = Uri.parse('$_downloadBase/v$_version/build-$triple.zip');