diff --git a/.changeset/dart_hook_code_assets_2.md b/.changeset/dart_hook_code_assets_2.md new file mode 100644 index 000000000..3888b4ced --- /dev/null +++ b/.changeset/dart_hook_code_assets_2.md @@ -0,0 +1,20 @@ +--- +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. + +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 295825c56..b8c04cedb 100644 --- a/livekit-uniffi/support/dart/hook/build.dart.tera +++ b/livekit-uniffi/support/dart/hook/build.dart.tera @@ -71,14 +71,22 @@ 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. + // + // 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'); @@ -89,7 +97,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 +112,6 @@ Future _resolveLibrary( if (!await libFile.exists()) { throw Exception('$libName not found in $zipUrl'); } - output.dependencies.add(libFile.uri); return libFile.uri; } 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