From dc26f4e422e002f5b724344891bde941e10db49a Mon Sep 17 00:00:00 2001 From: Sakiko Date: Mon, 3 Aug 2026 23:50:18 +0800 Subject: [PATCH 1/3] chore(release): prepare v0.1.5 --- .github/workflows/release.yml | 69 +++++++++++++- CHANGELOG.md | 32 +++++++ Cargo.lock | 54 +++++------ Cargo.toml | 2 +- docs/releasing.ja.md | 10 +- docs/releasing.md | 11 ++- docs/releasing.zh.md | 10 +- packages/erika_flutter/README.ja.md | 2 +- packages/erika_flutter/README.md | 4 +- packages/erika_flutter/README.zh.md | 2 +- .../erika_flutter/android/erika-native.gradle | 2 +- .../erika_flutter/ios/erika_flutter.podspec | 11 ++- .../erika_flutter/macos/erika_flutter.podspec | 4 +- packages/erika_flutter/ohos/oh-package.json5 | 2 +- packages/erika_flutter/pubspec.yaml | 2 +- .../test/apple_memory_font_contract_test.dart | 36 ++++++++ .../tvos/Classes/ErikaFlutterPlugin.swift | 92 +++++++++++++++++++ .../erika_flutter/tvos/erika_flutter.podspec | 11 ++- .../windows/build_erika_runtime.cmake | 2 +- 19 files changed, 297 insertions(+), 61 deletions(-) create mode 100644 packages/erika_flutter/test/apple_memory_font_contract_test.dart diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4c93af..ecf8edf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,12 +2,13 @@ name: Release # Build prebuilt erika_capi bundles and attach them to a GitHub Release. # -# Trigger: push a tag like `v0.1.4` to publish. Use the manual "Run workflow" +# Trigger: push a tag like `v0.1.5` to publish. Use the manual "Run workflow" # button (workflow_dispatch) to dry-run the builds without publishing (the # publish job is gated on a tag ref). # # Bundles (per platform): liberika_capi.{dylib,a} / erika_capi.dll + libs / -# erika_capi.xcframework / Android ABI archives / OpenHarmony shared libraries, +# iOS/tvOS erika_capi.xcframework bundles / Android ABI archives / +# OpenHarmony shared libraries, # plus erika.h, LICENSE (MPL-2.0), and THIRD_PARTY_NOTICES.md. Native deps # (FFmpeg/libass/...) are statically linked via the `lgpl` profile. # @@ -27,6 +28,7 @@ permissions: env: PROFILE: lgpl IPHONEOS_DEPLOYMENT_TARGET: "13.0" + TVOS_DEPLOYMENT_TARGET: "13.0" ANDROID_API_LEVEL: "26" ANDROID_NDK_VERSION: "29.0.14206865" CARGO_TERM_COLOR: always @@ -165,6 +167,63 @@ jobs: name: erika-capi-ios path: dist/*.zip + tvos: + name: tvOS (XCFramework) + runs-on: macos-14 + timeout-minutes: 240 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - name: Install native build tools + run: brew install nasm meson ninja pkg-config + - name: Cache native dependencies + uses: actions/cache@v4 + with: + path: third_party + key: native-v2-tvos-release-all-min${{ env.TVOS_DEPLOYMENT_TARGET }}-${{ hashFiles('xtask/src/main.rs', 'third_party/patches/**') }} + - name: Build native deps + erika_capi (device + simulators) + run: | + set -euo pipefail + for ENTRY in \ + "aarch64-apple-tvos|appletvos|arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}" \ + "aarch64-apple-tvos-sim|appletvsimulator|arm64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator" \ + "x86_64-apple-tvos|appletvsimulator|x86_64-apple-tvos${TVOS_DEPLOYMENT_TARGET}-simulator" + do + IFS='|' read -r T SDK CLANG_TARGET <<< "$ENTRY" + SDKROOT="$(xcrun --sdk "$SDK" --show-sdk-path)" + BINDGEN_KEY="${T//-/_}" + export SDKROOT + export "BINDGEN_EXTRA_CLANG_ARGS_${BINDGEN_KEY}=--target=${CLANG_TARGET} -isysroot ${SDKROOT}" + cargo run --locked -p xtask -- deps build --all \ + --profile "$PROFILE" --target "$T" + ERIKA_NATIVE_PROFILE="$PROFILE" ERIKA_NATIVE_TARGET="$T" \ + cargo +nightly rustc -Z build-std=std,panic_abort --locked \ + -p erika_capi --lib --release --target "$T" \ + --no-default-features --features libass --crate-type staticlib + done + - name: Assemble XCFramework + run: | + mkdir -p out hdr + cp crates/erika_capi/include/erika.h hdr/erika.h + lipo -create \ + target/aarch64-apple-tvos-sim/release/liberika_capi.a \ + target/x86_64-apple-tvos/release/liberika_capi.a \ + -output out/liberika_capi-sim.a + xcodebuild -create-xcframework \ + -library target/aarch64-apple-tvos/release/liberika_capi.a -headers hdr \ + -library out/liberika_capi-sim.a -headers hdr \ + -output out/erika_capi.xcframework + - name: Package + run: | + bash packaging/bundle.sh erika-capi-tvos \ + dist/erika-capi-tvos.zip out/erika_capi.xcframework + - uses: actions/upload-artifact@v4 + with: + name: erika-capi-tvos + path: dist/*.zip + windows: name: Windows ${{ matrix.arch }} runs-on: ${{ matrix.runner }} @@ -449,7 +508,7 @@ jobs: release: name: Publish Release - needs: [macos, macos-universal, ios, windows, android, openharmony] + needs: [macos, macos-universal, ios, tvos, windows, android, openharmony] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest permissions: @@ -466,8 +525,8 @@ jobs: body: | Prebuilt `erika_capi` C ABI bundles (static `lgpl` profile), for macOS arm64, macOS x64, macOS universal, Windows x64, - Windows ARM64, iOS, and Android arm64-v8a, armeabi-v7a, x86_64, - and x86, plus OpenHarmony arm64. + Windows ARM64, iOS, tvOS, and Android arm64-v8a, armeabi-v7a, + x86_64, and x86, plus OpenHarmony arm64. Each archive contains the library, `include/erika.h`, `LICENSE` (MPL-2.0), and `THIRD_PARTY_NOTICES.md`. See diff --git a/CHANGELOG.md b/CHANGELOG.md index d95e1db..21d3dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ ## Unreleased +## 0.1.5 - 2026-08-03 + +### Platform support and release artifacts + +- Added the tvOS Flutter plugin and native target support for Apple TV devices + and arm64/x86_64 simulators, including Metal presentation, Apple audio and + VideoToolbox integration, subtitles, danmaku, screenshots, and HTTP sources. +- Added `erika-capi-tvos.zip`, containing a tvOS device and universal simulator + `erika_capi.xcframework`, to the automated GitHub Release workflow. +- Fixed tvOS platform detection during native dependency builds and accepted + valid zero-length HTTP resources without misclassifying them as truncated. + +### Subtitle memory fonts + +- Added an in-memory subtitle font registry across Rust, the C API, Dart, and + every native Flutter embedding. Hosts can inspect font faces, select ordered + fallback sets, replace font data, and clear the registry without writing font + files to disk. +- Preserved ordered fallback in the bundled libass build, shared TTC/OTC data + across faces, and invalidated renderer caches when a same-path font is + replaced so active subtitle tracks immediately use the new data. +- Kept registry and selected-font generations separate, replayed libass codec + private data after renderer recreation, and added OpenHarmony and tvOS C API + coverage for the complete memory-font surface. + +### Playback recovery + +- Added explicit buffering transitions when audio and video input starves. The + playback clock now freezes during a stall and reanchors to recovered media + time instead of running ahead and causing a prolonged frame drop or black + screen after data resumes. + ## 0.1.4 - 2026-07-30 ### Compatibility notes diff --git a/Cargo.lock b/Cargo.lock index b910739..6c1bb09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "capi_smoke" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika_capi", ] @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "coreaudio_smoke" -version = "0.1.4" +version = "0.1.5" dependencies = [ "crossbeam-channel", "erika", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "danmaku_perf_lab" -version = "0.1.4" +version = "0.1.5" dependencies = [ "cc", "erika", @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "danmaku_sync_smoke" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] @@ -443,7 +443,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erika" -version = "0.1.4" +version = "0.1.5" dependencies = [ "ab_glyph", "aho-corasick", @@ -480,7 +480,7 @@ dependencies = [ [[package]] name = "erika_capi" -version = "0.1.4" +version = "0.1.5" dependencies = [ "crossbeam-channel", "erika", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "erika_ffmpeg_sys" -version = "0.1.4" +version = "0.1.5" dependencies = [ "bindgen 0.72.1", "libc", @@ -508,42 +508,42 @@ dependencies = [ [[package]] name = "ffmpeg_audio_decode" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "ffmpeg_decode" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "ffmpeg_demux" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "ffmpeg_probe" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "ffmpeg_probe_source" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "ffmpeg_videotoolbox" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] @@ -941,7 +941,7 @@ checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "macos_native_demo" -version = "0.1.4" +version = "0.1.5" dependencies = [ "cc", "crossbeam-channel", @@ -965,14 +965,14 @@ dependencies = [ [[package]] name = "metal_import_videotoolbox" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "metal_overlay_check" -version = "0.1.4" +version = "0.1.5" dependencies = [ "cc", "erika", @@ -980,7 +980,7 @@ dependencies = [ [[package]] name = "metal_presenter_check" -version = "0.1.4" +version = "0.1.5" dependencies = [ "cc", "erika", @@ -1311,21 +1311,21 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "playback_audio_tick" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "playback_tick" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] [[package]] name = "player_tick" -version = "0.1.4" +version = "0.1.5" dependencies = [ "crossbeam-channel", "erika", @@ -1484,7 +1484,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "render_pipeline_plan" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", ] @@ -2173,7 +2173,7 @@ dependencies = [ [[package]] name = "wgpu_decode_png" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", "png", @@ -2181,7 +2181,7 @@ dependencies = [ [[package]] name = "wgpu_overlay_png" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", "png", @@ -2189,7 +2189,7 @@ dependencies = [ [[package]] name = "wgpu_video_png" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", "png", @@ -2197,7 +2197,7 @@ dependencies = [ [[package]] name = "wgpu_window_check" -version = "0.1.4" +version = "0.1.5" dependencies = [ "cc", "erika", @@ -2436,7 +2436,7 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_native_demo" -version = "0.1.4" +version = "0.1.5" dependencies = [ "erika", "windows-sys 0.61.2", @@ -2501,7 +2501,7 @@ checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "xtask" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 3cb26a0..bb4d82d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ edition = "2024" license = "MPL-2.0" repository = "https://github.com/AimesSoft/Erika" rust-version = "1.92" -version = "0.1.4" +version = "0.1.5" [workspace.dependencies] ab_glyph = "0.2.32" diff --git a/docs/releasing.ja.md b/docs/releasing.ja.md index ebd6a2b..48a2704 100644 --- a/docs/releasing.ja.md +++ b/docs/releasing.ja.md @@ -14,6 +14,7 @@ | Windows x64 | `erika-capi-windows-x64.zip` | | Windows ARM64 | `erika-capi-windows-arm64.zip` | | iOS | `erika-capi-ios.zip`、device と simulator の XCFramework slice | +| tvOS | `erika-capi-tvos.zip`、device と arm64/x86_64 simulator の XCFramework slice | | Android | `erika-capi-android.zip`、`arm64-v8a`、`armeabi-v7a`、`x86_64`、`x86` | | OpenHarmony arm64 | `erika-capi-openharmony-arm64.zip`、`liberika_capi.so` と `liberika_flutter.so` | @@ -28,8 +29,8 @@ build され、C API runtime と Flutter N-API bridge を含みます。Hvigor Release は [release.yml](../.github/workflows/release.yml) で自動化されています。GitHub Release を作成するには `v*` tag を push します: ```sh -git tag v0.1.4 -git push origin v0.1.4 +git tag v0.1.5 +git push origin v0.1.5 ``` `workflow_dispatch` の手動実行は Actions Artifact のみを生成し、`ERIKA_PREBUILT_TAG` から取得できる GitHub Release は公開しません。 @@ -40,7 +41,7 @@ macOS arm64 は `macos-15`、x64 は `macos-15-intel` で native build し、そ ```sh export ERIKA_PREBUILT=1 -export ERIKA_PREBUILT_TAG=v0.1.4 +export ERIKA_PREBUILT_TAG=v0.1.5 ``` plugin source と C ABI の version を一致させるため、`ERIKA_PREBUILT_TAG` を明示的に固定することを推奨します。download または展開に失敗した場合は source build に fallback します。local debug では次を設定します: @@ -60,11 +61,12 @@ Platform architecture の選択: | Windows | `ERIKA_WINDOWS_ARCH=arm64` | `windows-arm64` | | Android | `ERIKA_ANDROID_ABIS=` | 共通 Android package から ABI を選択 | | iOS | Xcode platform/arch に従う | 共通 iOS XCFramework から slice を選択 | +| tvOS | Xcode platform/arch に従う | 共通 tvOS XCFramework から slice を選択 | Android の例: ```sh -ERIKA_PREBUILT=1 ERIKA_PREBUILT_TAG=v0.1.4 \ +ERIKA_PREBUILT=1 ERIKA_PREBUILT_TAG=v0.1.5 \ ERIKA_ANDROID_ABIS=arm64-v8a,x86_64 flutter build apk ``` diff --git a/docs/releasing.md b/docs/releasing.md index e7aeee8..57925bb 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -18,6 +18,7 @@ license files: | Windows x64 | `erika-capi-windows-x64.zip` | `erika_capi.dll`, `erika_capi.dll.lib` (import), `erika_capi.lib` (static) | | Windows ARM64 | `erika-capi-windows-arm64.zip` | `erika_capi.dll`, `erika_capi.dll.lib` (import), `erika_capi.lib` (static) | | iOS | `erika-capi-ios.zip` | `erika_capi.xcframework` (device + simulator) | +| tvOS | `erika-capi-tvos.zip` | `erika_capi.xcframework` (device + arm64/x86_64 simulator) | | Android | `erika-capi-android.zip` | `liberika_capi.so`, `liberika_capi.a`, and `libc++_shared.so` for `arm64-v8a`, `armeabi-v7a`, `x86_64`, and `x86` | | OpenHarmony arm64 | `erika-capi-openharmony-arm64.zip` | `liberika_capi.so`, `liberika_flutter.so` | @@ -56,13 +57,13 @@ The release is fully automated by section, and bump `version` in the root `Cargo.toml` if appropriate. 2. Tag and push: ```sh - git tag v0.1.4 - git push origin v0.1.4 + git tag v0.1.5 + git push origin v0.1.5 ``` 3. The workflow builds macOS arm64 on `macos-15` and macOS x64 on `macos-15-intel`, then combines those native-host outputs into the universal bundle. Windows x64 runs on `windows-latest` and Windows ARM64 runs natively - on `windows-11-arm`. It also builds the iOS / Android bundles and attaches + on `windows-11-arm`. It also builds the iOS / tvOS / Android bundles and attaches all archives to a new GitHub Release for that tag. To dry-run the builds without publishing, trigger the workflow manually @@ -93,7 +94,7 @@ Enable it by setting environment variables in the host app's build: | Variable | Effect | |----------|--------| | `ERIKA_PREBUILT=1` | Download the prebuilt `erika_capi` instead of building from source. | -| `ERIKA_PREBUILT_TAG=v0.1.4` | Release tag to download (default `v0.1.4`). | +| `ERIKA_PREBUILT_TAG=v0.1.5` | Release tag to download (default `v0.1.5`). | | `ERIKA_FORCE_SOURCE_BUILD=1` | Bypass the prebuilt path and build the local source, useful when debugging Erika changes through the Flutter plugin. | | `ERIKA_MACOS_ARCHS=universal|arm64|x86_64|arm64,x86_64` | Select the macOS source and prebuilt artifact architecture. | @@ -105,6 +106,8 @@ Enable it by setting environment variables in the host app's build: must be built `--no-default-features --features libass` to match the plugin's link flags (the release workflow does this); verify against a release built that way before relying on it. +- **tvOS** (podspec): downloads `erika-capi-tvos.zip` and selects the device or + universal simulator slice from its XCFramework. - **macOS** (podspec `script_phase`): downloads the arm64, x64, or universal archive selected by `ERIKA_MACOS_ARCHS` and bundles its dylib into the app's `Contents/Frameworks` (`install_name @rpath`, codesigned). Without diff --git a/docs/releasing.zh.md b/docs/releasing.zh.md index cd17fd3..6f13bb7 100644 --- a/docs/releasing.zh.md +++ b/docs/releasing.zh.md @@ -14,6 +14,7 @@ | Windows x64 | `erika-capi-windows-x64.zip` | | Windows ARM64 | `erika-capi-windows-arm64.zip` | | iOS | `erika-capi-ios.zip`,包含 device 和 simulator XCFramework slice | +| tvOS | `erika-capi-tvos.zip`,包含 device 和 arm64/x86_64 simulator XCFramework slice | | Android | `erika-capi-android.zip`,包含 `arm64-v8a`、`armeabi-v7a`、`x86_64`、`x86` | | OpenHarmony arm64 | `erika-capi-openharmony-arm64.zip`,包含 `liberika_capi.so` 和 `liberika_flutter.so` | @@ -28,8 +29,8 @@ OpenHarmony 归档使用 OpenHarmony 5.1.0 Native SDK、compatible SDK 18 构建 Release 由 [release.yml](../.github/workflows/release.yml) 自动执行。推送 `v*` tag 才会创建 GitHub Release: ```sh -git tag v0.1.4 -git push origin v0.1.4 +git tag v0.1.5 +git push origin v0.1.5 ``` 手动运行 `workflow_dispatch` 只生成 Actions Artifact,不发布可供 `ERIKA_PREBUILT_TAG` 下载的 GitHub Release。 @@ -40,7 +41,7 @@ macOS arm64 在 `macos-15` 原生构建,x64 在 `macos-15-intel` 原生构建 ```sh export ERIKA_PREBUILT=1 -export ERIKA_PREBUILT_TAG=v0.1.4 +export ERIKA_PREBUILT_TAG=v0.1.5 ``` 建议始终显式固定 `ERIKA_PREBUILT_TAG`,保证插件源码和 C ABI 版本一致。下载或解压失败会回退源码构建;本地调试时设置: @@ -60,11 +61,12 @@ export ERIKA_FORCE_SOURCE_BUILD=1 | Windows | `ERIKA_WINDOWS_ARCH=arm64` | `windows-arm64` | | Android | `ERIKA_ANDROID_ABIS=<列表>` | 从统一 Android 包抽取所选 ABI | | iOS | 由 Xcode platform/arch 决定 | 从统一 iOS XCFramework 选择 slice | +| tvOS | 由 Xcode platform/arch 决定 | 从统一 tvOS XCFramework 选择 slice | Android 示例: ```sh -ERIKA_PREBUILT=1 ERIKA_PREBUILT_TAG=v0.1.4 \ +ERIKA_PREBUILT=1 ERIKA_PREBUILT_TAG=v0.1.5 \ ERIKA_ANDROID_ABIS=arm64-v8a,x86_64 flutter build apk ``` diff --git a/packages/erika_flutter/README.ja.md b/packages/erika_flutter/README.ja.md index db9a5e7..0cd865f 100644 --- a/packages/erika_flutter/README.ja.md +++ b/packages/erika_flutter/README.ja.md @@ -37,7 +37,7 @@ cargo build -p erika_capi ## Prebuilt package と source build -`ERIKA_PREBUILT=1` を設定すると GitHub Release から prebuilt native library を取得します。`ERIKA_PREBUILT_TAG=v0.1.4` で plugin source と一致する Release tag を固定してください。download または展開に失敗した場合は source build に fallback します。local source を debug するときは `ERIKA_FORCE_SOURCE_BUILD=1` で prebuilt を無効化します。package 名と release 手順は [releasing.ja.md](../../docs/releasing.ja.md) を参照してください。 +`ERIKA_PREBUILT=1` を設定すると GitHub Release から prebuilt native library を取得します。`ERIKA_PREBUILT_TAG=v0.1.5` で plugin source と一致する Release tag を固定してください。download または展開に失敗した場合は source build に fallback します。local source を debug するときは `ERIKA_FORCE_SOURCE_BUILD=1` で prebuilt を無効化します。package 名と release 手順は [releasing.ja.md](../../docs/releasing.ja.md) を参照してください。 source build の architecture は macOS では `ERIKA_MACOS_ARCHS=arm64|x86_64|universal`、Windows では `ERIKA_WINDOWS_ARCH=x64|arm64`、Android では `ERIKA_ANDROID_ABIS=arm64-v8a,armeabi-v7a,x86_64,x86` で選択します。native library を直接 build する場合、`xtask --target`、`ERIKA_NATIVE_TARGET`、`cargo build --target` は同じ target にしてください。詳細は [building.ja.md](../../docs/building.ja.md) を参照してください。 diff --git a/packages/erika_flutter/README.md b/packages/erika_flutter/README.md index 814d3dd..0fcc404 100644 --- a/packages/erika_flutter/README.md +++ b/packages/erika_flutter/README.md @@ -58,8 +58,8 @@ points the build phase at an explicit dylib to bundle instead of building. To skip building Erika (and FFmpeg) from source, set `ERIKA_PREBUILT=1` in the app build to download the prebuilt `erika_capi` from a GitHub Release -(`ERIKA_PREBUILT_TAG` selects the tag, default `v0.1.4`). Supported on macOS, -Windows, iOS, and Android; any failure falls back to the source build. See +(`ERIKA_PREBUILT_TAG` selects the tag, default `v0.1.5`). Supported on macOS, +Windows, iOS, tvOS, and Android; any failure falls back to the source build. See [`docs/releasing.md`](../../docs/releasing.md). When debugging local Erika source changes, set diff --git a/packages/erika_flutter/README.zh.md b/packages/erika_flutter/README.zh.md index 8a25e6b..0848a38 100644 --- a/packages/erika_flutter/README.zh.md +++ b/packages/erika_flutter/README.zh.md @@ -37,7 +37,7 @@ cargo build -p erika_capi ## 预构建包与源码构建 -设置 `ERIKA_PREBUILT=1` 可从 GitHub Release 下载预构建原生库,`ERIKA_PREBUILT_TAG=v0.1.4` 用于固定与当前插件源码匹配的 Release tag。下载或解压失败时会回退源码构建。调试本地源码时设置 `ERIKA_FORCE_SOURCE_BUILD=1` 强制绕过预构建包。完整包名和发布方式见 [releasing.zh.md](../../docs/releasing.zh.md)。 +设置 `ERIKA_PREBUILT=1` 可从 GitHub Release 下载预构建原生库,`ERIKA_PREBUILT_TAG=v0.1.5` 用于固定与当前插件源码匹配的 Release tag。下载或解压失败时会回退源码构建。调试本地源码时设置 `ERIKA_FORCE_SOURCE_BUILD=1` 强制绕过预构建包。完整包名和发布方式见 [releasing.zh.md](../../docs/releasing.zh.md)。 源码构建时,macOS 使用 `ERIKA_MACOS_ARCHS=arm64|x86_64|universal`,Windows 使用 `ERIKA_WINDOWS_ARCH=x64|arm64`,Android 使用 `ERIKA_ANDROID_ABIS=arm64-v8a,armeabi-v7a,x86_64,x86`。直接构建原生库时,`xtask --target`、`ERIKA_NATIVE_TARGET` 和 `cargo build --target` 必须使用同一个 target。详细示例见 [building.zh.md](../../docs/building.zh.md)。 diff --git a/packages/erika_flutter/android/erika-native.gradle b/packages/erika_flutter/android/erika-native.gradle index 5e40ca1..e2f4d53 100644 --- a/packages/erika_flutter/android/erika-native.gradle +++ b/packages/erika_flutter/android/erika-native.gradle @@ -89,7 +89,7 @@ def skipNativeBuild = (project.findProperty("erikaSkipNativeBuild") ?: "false").toString().toBoolean() def forceSourceBuild = (System.getenv("ERIKA_FORCE_SOURCE_BUILD") ?: "0") == "1" def usePrebuilt = (System.getenv("ERIKA_PREBUILT") ?: "0") == "1" && !forceSourceBuild -def prebuiltTag = System.getenv("ERIKA_PREBUILT_TAG") ?: "v0.1.4" +def prebuiltTag = System.getenv("ERIKA_PREBUILT_TAG") ?: "v0.1.5" def prebuiltCacheKey = prebuiltTag.replaceAll(/[^A-Za-z0-9._-]/, "_") def prebuiltCacheDirectory = new File( gradle.gradleUserHomeDir, diff --git a/packages/erika_flutter/ios/erika_flutter.podspec b/packages/erika_flutter/ios/erika_flutter.podspec index b89aa1a..b722576 100644 --- a/packages/erika_flutter/ios/erika_flutter.podspec +++ b/packages/erika_flutter/ios/erika_flutter.podspec @@ -23,11 +23,15 @@ Pod::Spec.new do |s| erika_presenter_poll_event erika_presenter_remove_danmaku_track erika_presenter_remove_subtitle_track + erika_presenter_register_subtitle_memory_font erika_presenter_render_tick erika_presenter_resize_surface erika_presenter_seek erika_presenter_select_audio_track erika_presenter_select_subtitle_track + erika_presenter_select_subtitle_memory_fonts + erika_presenter_clear_subtitle_memory_fonts + erika_presenter_get_subtitle_memory_font_status erika_presenter_set_danmaku_block_words_json erika_presenter_set_danmaku_config_ptr erika_presenter_set_danmaku_enabled @@ -43,13 +47,14 @@ Pod::Spec.new do |s| erika_presenter_track_selection erika_presenter_tracks erika_track_info_free + erika_subtitle_memory_font_status_free ] erika_cabi_undefined_flags = erika_cabi_symbols .map { |symbol| "-Wl,-u,_#{symbol}" } .join(' ') s.name = 'erika_flutter' - s.version = '0.1.4' + s.version = '0.1.5' s.summary = 'Flutter embedder glue for the Erika Rust media engine.' s.description = <<-DESC Flutter iOS plugin that hosts a CAMetalLayer and drives Erika through its C ABI. @@ -141,11 +146,11 @@ ERIKA_DAV1D_MARKER="$ERIKA_ROOT/third_party/build/$RUST_TARGET/$ERIKA_NATIVE_PRO # Optional: use a prebuilt static lib from a GitHub Release (opt-in). # Enable with ERIKA_PREBUILT=1; ERIKA_PREBUILT_TAG selects the tag (default -# v0.1.4). Any failure falls through to the source build below, so enabling it +# v0.1.5). Any failure falls through to the source build below, so enabling it # never breaks a build. ERIKA_IOS_CAPI_STATICLIB still takes precedence. PREBUILT_LIB="" if [ "${ERIKA_FORCE_SOURCE_BUILD:-0}" != "1" ] && [ "${ERIKA_PREBUILT:-0}" = "1" ] && [ -z "${ERIKA_IOS_CAPI_STATICLIB:-}" ]; then - PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.4}" + PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.5}" PREBUILT_WORK="$ERIKA_ROOT/target/erika-prebuilt-ios" PREBUILT_ZIP="$PREBUILT_WORK/erika-capi-ios.zip" PREBUILT_URL="https://github.com/AimesSoft/Erika/releases/download/$PREBUILT_TAG/erika-capi-ios.zip" diff --git a/packages/erika_flutter/macos/erika_flutter.podspec b/packages/erika_flutter/macos/erika_flutter.podspec index bd5dd6a..7517179 100644 --- a/packages/erika_flutter/macos/erika_flutter.podspec +++ b/packages/erika_flutter/macos/erika_flutter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'erika_flutter' - s.version = '0.1.4' + s.version = '0.1.5' s.summary = 'Flutter embedder glue for the Erika Rust media engine.' s.description = <<-DESC Flutter macOS plugin that hosts a CAMetalLayer and drives Erika through its C ABI. @@ -96,7 +96,7 @@ fi OUTPUT_DYLIB="" if [ "${ERIKA_FORCE_SOURCE_BUILD:-0}" != "1" ] && [ "${ERIKA_PREBUILT:-0}" = "1" ] && [ -z "${ERIKA_MACOS_CAPI_DYLIB:-}" ]; then - PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.4}" + PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.5}" PREBUILT_WORK="$ERIKA_ROOT/target/erika-prebuilt-macos-$PREBUILT_ARCH" PREBUILT_ZIP="$PREBUILT_WORK/erika-capi-macos-$PREBUILT_ARCH.zip" PREBUILT_URL="https://github.com/AimesSoft/Erika/releases/download/$PREBUILT_TAG/erika-capi-macos-$PREBUILT_ARCH.zip" diff --git a/packages/erika_flutter/ohos/oh-package.json5 b/packages/erika_flutter/ohos/oh-package.json5 index 74e1fec..a8f62a3 100644 --- a/packages/erika_flutter/ohos/oh-package.json5 +++ b/packages/erika_flutter/ohos/oh-package.json5 @@ -1,6 +1,6 @@ { "name": "erika_flutter", - "version": "0.1.4", + "version": "0.1.5", "description": "Erika media engine Flutter plugin for OpenHarmony", "main": "index.ets", "author": "Aimesoft", diff --git a/packages/erika_flutter/pubspec.yaml b/packages/erika_flutter/pubspec.yaml index 74c63a1..3a86fd4 100644 --- a/packages/erika_flutter/pubspec.yaml +++ b/packages/erika_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: erika_flutter description: Flutter embedder glue for the Erika Rust media engine. -version: 0.1.4 +version: 0.1.5 publish_to: none environment: diff --git a/packages/erika_flutter/test/apple_memory_font_contract_test.dart b/packages/erika_flutter/test/apple_memory_font_contract_test.dart new file mode 100644 index 0000000..f6b3b94 --- /dev/null +++ b/packages/erika_flutter/test/apple_memory_font_contract_test.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + for (final platform in ['ios', 'tvos']) { + test('$platform exposes and force-links subtitle memory fonts', () { + final plugin = File( + '$platform/Classes/ErikaFlutterPlugin.swift', + ).readAsStringSync(); + final podspec = File( + '$platform/erika_flutter.podspec', + ).readAsStringSync(); + + for (final method in [ + 'registerSubtitleMemoryFont', + 'selectSubtitleMemoryFonts', + 'clearSubtitleMemoryFonts', + 'getSubtitleMemoryFontStatus', + ]) { + expect(plugin, contains('case "$method"')); + } + + for (final symbol in [ + 'erika_presenter_register_subtitle_memory_font', + 'erika_presenter_select_subtitle_memory_fonts', + 'erika_presenter_clear_subtitle_memory_fonts', + 'erika_presenter_get_subtitle_memory_font_status', + 'erika_subtitle_memory_font_status_free', + ]) { + expect(plugin, contains('"$symbol"')); + expect(podspec, contains(symbol)); + } + }); + } +} diff --git a/packages/erika_flutter/tvos/Classes/ErikaFlutterPlugin.swift b/packages/erika_flutter/tvos/Classes/ErikaFlutterPlugin.swift index e6f2de3..926e0d7 100644 --- a/packages/erika_flutter/tvos/Classes/ErikaFlutterPlugin.swift +++ b/packages/erika_flutter/tvos/Classes/ErikaFlutterPlugin.swift @@ -262,6 +262,14 @@ private struct ErikaUpscalerStatusC { var lastGpuMicros: UInt64 = 0 } +private struct ErikaSubtitleMemoryFontStatusC { + var registeredCount: UInt = 0 + var registeredBytes: UInt = 0 + var selectedCount: UInt = 0 + var generation: UInt64 = 0 + var selectedIds: UnsafeMutablePointer? +} + // Keep field order and types aligned with `ErikaOutputStatus` in erika.h. private struct ErikaOutputStatusC { var requestedMode: Int32 = 0 @@ -377,6 +385,10 @@ private final class ErikaNativeLibrary { UnsafeMutableRawPointer?, UnsafeRawPointer? ) -> Int32 + typealias RegisterSubtitleMemoryFontFn = @convention(c) (UnsafeMutableRawPointer?, UnsafePointer?, UInt, UnsafeMutablePointer?) -> Int32 + typealias SelectSubtitleMemoryFontsFn = @convention(c) (UnsafeMutableRawPointer?, UnsafePointer?, UInt) -> Int32 + typealias GetSubtitleMemoryFontStatusFn = @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Int32 + typealias FreeSubtitleMemoryFontStatusFn = @convention(c) (UnsafeMutableRawPointer?) -> Void typealias GetUpscalerStatusFn = @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Int32 typealias GetOutputStatusFn = @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Int32 typealias SelectTrackFn = @convention(c) (UnsafeMutableRawPointer?, Int64) -> Int32 @@ -444,6 +456,11 @@ private final class ErikaNativeLibrary { let setSubtitleScale: SetSubtitleScaleFn? let setSubtitleFont: SetSubtitleFontFn? let setSubtitleStyle: SetSubtitleStyleFn? + let registerSubtitleMemoryFont: RegisterSubtitleMemoryFontFn? + let selectSubtitleMemoryFonts: SelectSubtitleMemoryFontsFn? + let clearSubtitleMemoryFonts: CommandFn? + let getSubtitleMemoryFontStatus: GetSubtitleMemoryFontStatusFn? + let freeSubtitleMemoryFontStatus: FreeSubtitleMemoryFontStatusFn? let getUpscalerStatus: GetUpscalerStatusFn? let getOutputStatus: GetOutputStatusFn? let selectAudioTrack: SelectTrackFn @@ -507,6 +524,11 @@ private final class ErikaNativeLibrary { setSubtitleScale = Self.loadOptional("erika_presenter_set_subtitle_scale", from: libraryHandle, as: SetSubtitleScaleFn.self) setSubtitleFont = Self.loadOptional("erika_presenter_set_subtitle_font", from: libraryHandle, as: SetSubtitleFontFn.self) setSubtitleStyle = Self.loadOptional("erika_presenter_set_subtitle_style", from: libraryHandle, as: SetSubtitleStyleFn.self) + registerSubtitleMemoryFont = Self.loadOptional("erika_presenter_register_subtitle_memory_font", from: libraryHandle, as: RegisterSubtitleMemoryFontFn.self) + selectSubtitleMemoryFonts = Self.loadOptional("erika_presenter_select_subtitle_memory_fonts", from: libraryHandle, as: SelectSubtitleMemoryFontsFn.self) + clearSubtitleMemoryFonts = Self.loadOptional("erika_presenter_clear_subtitle_memory_fonts", from: libraryHandle, as: CommandFn.self) + getSubtitleMemoryFontStatus = Self.loadOptional("erika_presenter_get_subtitle_memory_font_status", from: libraryHandle, as: GetSubtitleMemoryFontStatusFn.self) + freeSubtitleMemoryFontStatus = Self.loadOptional("erika_subtitle_memory_font_status_free", from: libraryHandle, as: FreeSubtitleMemoryFontStatusFn.self) getUpscalerStatus = Self.loadOptional("erika_presenter_get_upscaler_status", from: libraryHandle, as: GetUpscalerStatusFn.self) getOutputStatus = Self.loadOptional("erika_presenter_get_output_status", from: libraryHandle, as: GetOutputStatusFn.self) selectAudioTrack = try Self.load("erika_presenter_select_audio_track", from: libraryHandle, as: SelectTrackFn.self) @@ -799,6 +821,58 @@ private final class ErikaPlayerHost { return status.toFlutterMap() } + func registerSubtitleMemoryFont(_ data: Data) throws -> UInt64 { + guard let register = library.registerSubtitleMemoryFont else { + throw ErikaPluginError.symbolMissing("erika_presenter_register_subtitle_memory_font") + } + var fontId: UInt64 = 0 + let status = data.withUnsafeBytes { bytes in + register(handle, bytes.bindMemory(to: UInt8.self).baseAddress, UInt(data.count), &fontId) + } + try check(status, operation: "register_subtitle_memory_font") + return fontId + } + + func selectSubtitleMemoryFonts(_ fontIds: [UInt64]) throws { + guard let select = library.selectSubtitleMemoryFonts else { + throw ErikaPluginError.symbolMissing("erika_presenter_select_subtitle_memory_fonts") + } + try fontIds.withUnsafeBufferPointer { buffer in + try check(select(handle, buffer.baseAddress, UInt(buffer.count)), operation: "select_subtitle_memory_fonts") + } + } + + func clearSubtitleMemoryFonts() throws { + guard let clear = library.clearSubtitleMemoryFonts else { + throw ErikaPluginError.symbolMissing("erika_presenter_clear_subtitle_memory_fonts") + } + try check(clear(handle), operation: "clear_subtitle_memory_fonts") + } + + func subtitleMemoryFontStatus() throws -> [String: Any] { + guard let getStatus = library.getSubtitleMemoryFontStatus else { + throw ErikaPluginError.symbolMissing("erika_presenter_get_subtitle_memory_font_status") + } + var status = ErikaSubtitleMemoryFontStatusC() + defer { + if let freeStatus = library.freeSubtitleMemoryFontStatus { + withUnsafeMutablePointer(to: &status) { freeStatus(UnsafeMutableRawPointer($0)) } + } + } + try withUnsafeMutablePointer(to: &status) { pointer in + try check(getStatus(handle, UnsafeMutableRawPointer(pointer)), operation: "get_subtitle_memory_font_status") + } + return [ + "registeredCount": Int(status.registeredCount), + "registeredBytes": Int(status.registeredBytes), + "selectedCount": Int(status.selectedCount), + "generation": Int64(clamping: status.generation), + "selectedIds": status.selectedIds.map { pointer in + (0.. [String: Any] { guard let getStatus = library.getOutputStatus else { throw ErikaPluginError.symbolMissing("erika_presenter_get_output_status") @@ -1609,6 +1683,24 @@ public final class ErikaFlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHan } try playerHost(from: args).setSubtitleScale(scale) result(nil) + case "registerSubtitleMemoryFont": + let args = try dictionaryArgs(call.arguments) + guard let data = args["data"] as? FlutterStandardTypedData else { + throw ErikaPluginError.invalidArguments("data is required.") + } + result(Int64(clamping: try playerHost(from: args).registerSubtitleMemoryFont(data.data))) + case "selectSubtitleMemoryFonts": + let args = try dictionaryArgs(call.arguments) + let ids = (args["fontIds"] as? [NSNumber] ?? []).map { $0.uint64Value } + try playerHost(from: args).selectSubtitleMemoryFonts(ids) + result(nil) + case "clearSubtitleMemoryFonts": + let args = try dictionaryArgs(call.arguments) + try playerHost(from: args).clearSubtitleMemoryFonts() + result(nil) + case "getSubtitleMemoryFontStatus": + let args = try dictionaryArgs(call.arguments) + result(try playerHost(from: args).subtitleMemoryFontStatus()) case "setSubtitleStyle": let args = try dictionaryArgs(call.arguments) let host = try playerHost(from: args) diff --git a/packages/erika_flutter/tvos/erika_flutter.podspec b/packages/erika_flutter/tvos/erika_flutter.podspec index 06c8e62..f25b0f0 100644 --- a/packages/erika_flutter/tvos/erika_flutter.podspec +++ b/packages/erika_flutter/tvos/erika_flutter.podspec @@ -23,11 +23,15 @@ Pod::Spec.new do |s| erika_presenter_poll_event erika_presenter_remove_danmaku_track erika_presenter_remove_subtitle_track + erika_presenter_register_subtitle_memory_font erika_presenter_render_tick erika_presenter_resize_surface erika_presenter_seek erika_presenter_select_audio_track erika_presenter_select_subtitle_track + erika_presenter_select_subtitle_memory_fonts + erika_presenter_clear_subtitle_memory_fonts + erika_presenter_get_subtitle_memory_font_status erika_presenter_set_danmaku_block_words_json erika_presenter_set_danmaku_config_ptr erika_presenter_set_danmaku_enabled @@ -43,13 +47,14 @@ Pod::Spec.new do |s| erika_presenter_track_selection erika_presenter_tracks erika_track_info_free + erika_subtitle_memory_font_status_free ] erika_cabi_undefined_flags = erika_cabi_symbols .map { |symbol| "-Wl,-u,_#{symbol}" } .join(' ') s.name = 'erika_flutter' - s.version = '0.1.4' + s.version = '0.1.5' s.summary = 'Flutter embedder glue for the Erika Rust media engine.' s.description = <<-DESC Flutter tvOS plugin that hosts a CAMetalLayer and drives Erika through its C ABI. @@ -152,11 +157,11 @@ ERIKA_DAV1D_MARKER="$ERIKA_ROOT/third_party/build/$RUST_TARGET/$ERIKA_NATIVE_PRO # Optional: use a prebuilt static lib from a GitHub Release (opt-in). # Enable with ERIKA_PREBUILT=1; ERIKA_PREBUILT_TAG selects the tag (default -# v0.1.4). Any failure falls through to the source build below, so enabling it +# v0.1.5). Any failure falls through to the source build below, so enabling it # never breaks a build. ERIKA_TVOS_CAPI_STATICLIB still takes precedence. PREBUILT_LIB="" if [ "${ERIKA_FORCE_SOURCE_BUILD:-0}" != "1" ] && [ "${ERIKA_PREBUILT:-0}" = "1" ] && [ -z "${ERIKA_TVOS_CAPI_STATICLIB:-}" ]; then - PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.4}" + PREBUILT_TAG="${ERIKA_PREBUILT_TAG:-v0.1.5}" PREBUILT_WORK="$ERIKA_ROOT/target/erika-prebuilt-tvos" PREBUILT_ZIP="$PREBUILT_WORK/erika-capi-tvos.zip" PREBUILT_URL="https://github.com/AimesSoft/Erika/releases/download/$PREBUILT_TAG/erika-capi-tvos.zip" diff --git a/packages/erika_flutter/windows/build_erika_runtime.cmake b/packages/erika_flutter/windows/build_erika_runtime.cmake index ba2d945..8e2f0c8 100644 --- a/packages/erika_flutter/windows/build_erika_runtime.cmake +++ b/packages/erika_flutter/windows/build_erika_runtime.cmake @@ -63,7 +63,7 @@ if("$ENV{ERIKA_PREBUILT}" STREQUAL "1") else() set(_erika_cfg_dir "release") endif() - set(_erika_tag "v0.1.4") + set(_erika_tag "v0.1.5") if(NOT "$ENV{ERIKA_PREBUILT_TAG}" STREQUAL "") set(_erika_tag "$ENV{ERIKA_PREBUILT_TAG}") endif() From aff1389118571050e6a896a4eca4c869ac944fe2 Mon Sep 17 00:00:00 2001 From: Sakiko Date: Tue, 4 Aug 2026 00:02:31 +0800 Subject: [PATCH 2/3] build(release): align prebuilt consumers --- .github/workflows/ci.yml | 9 +- .github/workflows/release.yml | 13 +- CHANGELOG.md | 5 + docs/releasing.ja.md | 8 +- docs/releasing.md | 17 +- docs/releasing.zh.md | 7 +- packages/erika_flutter/README.ja.md | 9 +- packages/erika_flutter/README.md | 13 +- packages/erika_flutter/README.zh.md | 8 +- .../ohos/src/main/cpp/CMakeLists.txt | 159 +++++++++++++----- .../test/apple_memory_font_contract_test.dart | 17 ++ .../test/ohos_screenshot_contract_test.dart | 19 +++ 12 files changed, 206 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8795964..b510de4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,10 +61,10 @@ jobs: include: - arch: arm64 target: aarch64-apple-darwin - runner: macos-15 + runner: macos-26 - arch: x64 target: x86_64-apple-darwin - runner: macos-15-intel + runner: macos-26 runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 @@ -81,7 +81,6 @@ jobs: key: native-v2-macos-${{ matrix.target }}-${{ hashFiles('xtask/src/main.rs', 'third_party/patches/**') }} - name: Build native dependencies run: | - test "$(uname -m)" = "${{ matrix.arch == 'arm64' && 'arm64' || 'x86_64' }}" cargo run --locked -p xtask -- deps build --all \ --profile "$PROFILE" --target "${{ matrix.target }}" - name: Build C API library @@ -112,7 +111,7 @@ jobs: ios: name: iOS device staticlib - runs-on: macos-14 + runs-on: macos-26 timeout-minutes: 150 env: IPHONEOS_DEPLOYMENT_TARGET: "13.0" @@ -141,7 +140,7 @@ jobs: tvos: name: tvOS simulator staticlib - runs-on: macos-14 + runs-on: macos-26 timeout-minutes: 150 env: TVOS_DEPLOYMENT_TARGET: "13.0" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecf8edf..f403966 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,13 +44,11 @@ jobs: matrix: include: - arch: arm64 - host_arch: arm64 target: aarch64-apple-darwin - runner: macos-15 + runner: macos-26 - arch: x64 - host_arch: x86_64 target: x86_64-apple-darwin - runner: macos-15-intel + runner: macos-26 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -65,7 +63,6 @@ jobs: key: native-v2-macos-${{ matrix.target }}-${{ hashFiles('xtask/src/main.rs', 'third_party/patches/**') }} - name: Build native deps + erika_capi run: | - test "$(uname -m)" = "${{ matrix.host_arch }}" cargo run -p xtask -- deps build --all --profile "$PROFILE" --target "${{ matrix.target }}" ERIKA_NATIVE_PROFILE="$PROFILE" ERIKA_NATIVE_TARGET="${{ matrix.target }}" \ cargo build -p erika_capi --release --target "${{ matrix.target }}" @@ -91,7 +88,7 @@ jobs: macos-universal: name: macOS universal needs: macos - runs-on: macos-15 + runs-on: macos-26 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 @@ -120,7 +117,7 @@ jobs: ios: name: iOS (XCFramework) - runs-on: macos-14 + runs-on: macos-26 timeout-minutes: 180 steps: - uses: actions/checkout@v4 @@ -169,7 +166,7 @@ jobs: tvos: name: tvOS (XCFramework) - runs-on: macos-14 + runs-on: macos-26 timeout-minutes: 240 steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d3dcf..d254a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ VideoToolbox integration, subtitles, danmaku, screenshots, and HTTP sources. - Added `erika-capi-tvos.zip`, containing a tvOS device and universal simulator `erika_capi.xcframework`, to the automated GitHub Release workflow. +- Added opt-in OpenHarmony prebuilt consumption: the plugin CMake build can + download the tagged `liberika_capi.so`, stage it beside + `liberika_flutter.so`, and fall back to a source build on failure. +- Unified macOS, iOS, and tvOS CI/release builds on macOS 26 runners; macOS x64 + is cross-built alongside arm64 before the universal archive is assembled. - Fixed tvOS platform detection during native dependency builds and accepted valid zero-length HTTP resources without misclassifying them as truncated. diff --git a/docs/releasing.ja.md b/docs/releasing.ja.md index 48a2704..007c30d 100644 --- a/docs/releasing.ja.md +++ b/docs/releasing.ja.md @@ -19,8 +19,10 @@ | OpenHarmony arm64 | `erika-capi-openharmony-arm64.zip`、`liberika_capi.so` と `liberika_flutter.so` | OpenHarmony archive は OpenHarmony 5.1.0 Native SDK、compatible SDK 18 で -build され、C API runtime と Flutter N-API bridge を含みます。Hvigor の -`ERIKA_PREBUILT` 自動 download はまだ実装されていません。 +build され、C API runtime と Flutter N-API bridge を含みます。 +`ERIKA_PREBUILT=1` の場合、plugin CMake は対応する tag を download し、prebuilt +runtime を link して N-API bridge とともに HAR/HAP に package します。download +失敗時は source build に fallback します。 各 archive には `include/erika.h`、`LICENSE`、`THIRD_PARTY_NOTICES.md`、dependency license、tag/commit を記録する `MANIFEST.txt` も含まれます。native dependency は `lgpl` profile で static link され、Android は ABI に対応する `libc++_shared.so` も含みます。 @@ -35,7 +37,7 @@ git push origin v0.1.5 `workflow_dispatch` の手動実行は Actions Artifact のみを生成し、`ERIKA_PREBUILT_TAG` から取得できる GitHub Release は公開しません。 -macOS arm64 は `macos-15`、x64 は `macos-15-intel` で native build し、その後 universal package を合成します。Windows x64 は `windows-latest`、ARM64 は `windows-11-arm` で native build します。 +macOS arm64 と x64 はどちらも `macos-26` で cross build し、その後 universal package を合成します。iOS と tvOS の XCFramework も `macos-26` を使用します。Windows x64 は `windows-latest`、ARM64 は `windows-11-arm` で native build します。 ## Flutter で prebuilt を使用 diff --git a/docs/releasing.md b/docs/releasing.md index 57925bb..608fbbd 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -24,7 +24,9 @@ license files: The OpenHarmony archive is built against the OpenHarmony 5.1.0 native SDK with compatible SDK version 18. It contains the C API runtime and Flutter N-API -bridge; automatic `ERIKA_PREBUILT` consumption is not yet wired into Hvigor. +bridge. With `ERIKA_PREBUILT=1`, the plugin CMake build downloads the matching +tag, links the prebuilt runtime, and packages it beside the locally linked N-API +bridge; a failed download falls back to the source build. The Android archive stores each ABI at `lib/android//`. Flutter/Gradle consumers package `liberika_capi.so` together with the matching NDK @@ -60,11 +62,11 @@ The release is fully automated by git tag v0.1.5 git push origin v0.1.5 ``` -3. The workflow builds macOS arm64 on `macos-15` and macOS x64 on - `macos-15-intel`, then combines those native-host outputs into the universal - bundle. Windows x64 runs on `windows-latest` and Windows ARM64 runs natively - on `windows-11-arm`. It also builds the iOS / tvOS / Android bundles and attaches - all archives to a new GitHub Release for that tag. +3. The workflow cross-builds macOS arm64 and x64 on `macos-26`, then combines + those outputs into the universal bundle. iOS and tvOS XCFrameworks also use + `macos-26`. Windows x64 runs on `windows-latest` and Windows ARM64 runs + natively on `windows-11-arm`. It also builds the Android and OpenHarmony + bundles and attaches all archives to a new GitHub Release for that tag. To dry-run the builds without publishing, trigger the workflow manually ("Run workflow" / `workflow_dispatch`) — the build jobs run, but the publish job @@ -108,6 +110,9 @@ Enable it by setting environment variables in the host app's build: that way before relying on it. - **tvOS** (podspec): downloads `erika-capi-tvos.zip` and selects the device or universal simulator slice from its XCFramework. +- **OpenHarmony** (plugin CMake): downloads + `erika-capi-openharmony-arm64.zip`, links its `liberika_capi.so`, and stages + that runtime beside `liberika_flutter.so` for HAR/HAP packaging. - **macOS** (podspec `script_phase`): downloads the arm64, x64, or universal archive selected by `ERIKA_MACOS_ARCHS` and bundles its dylib into the app's `Contents/Frameworks` (`install_name @rpath`, codesigned). Without diff --git a/docs/releasing.zh.md b/docs/releasing.zh.md index 6f13bb7..562e766 100644 --- a/docs/releasing.zh.md +++ b/docs/releasing.zh.md @@ -19,8 +19,9 @@ | OpenHarmony arm64 | `erika-capi-openharmony-arm64.zip`,包含 `liberika_capi.so` 和 `liberika_flutter.so` | OpenHarmony 归档使用 OpenHarmony 5.1.0 Native SDK、compatible SDK 18 构建, -包含 C API runtime 和 Flutter N-API bridge;Hvigor 暂未接入自动 -`ERIKA_PREBUILT` 下载。 +包含 C API runtime 和 Flutter N-API bridge。设置 `ERIKA_PREBUILT=1` 后,插件 +CMake 会下载匹配 tag 的归档、链接预构建 runtime,并把它与本地链接的 N-API bridge +一起打入 HAR/HAP;下载失败时自动回退源码构建。 每个归档还包含 `include/erika.h`、`LICENSE`、`THIRD_PARTY_NOTICES.md`、依赖许可证和记录 tag/commit 的 `MANIFEST.txt`。原生依赖使用 `lgpl` profile 静态链接;Android 同时携带匹配 ABI 的 `libc++_shared.so`。 @@ -35,7 +36,7 @@ git push origin v0.1.5 手动运行 `workflow_dispatch` 只生成 Actions Artifact,不发布可供 `ERIKA_PREBUILT_TAG` 下载的 GitHub Release。 -macOS arm64 在 `macos-15` 原生构建,x64 在 `macos-15-intel` 原生构建,然后合并 universal 包。Windows x64 在 `windows-latest` 构建,ARM64 在 `windows-11-arm` 原生构建。 +macOS arm64 和 x64 都在 `macos-26` 交叉构建,然后合并 universal 包;iOS 和 tvOS XCFramework 同样使用 `macos-26`。Windows x64 在 `windows-latest` 构建,ARM64 在 `windows-11-arm` 原生构建。 ## Flutter 使用预构建包 diff --git a/packages/erika_flutter/README.ja.md b/packages/erika_flutter/README.ja.md index 0cd865f..c473d62 100644 --- a/packages/erika_flutter/README.ja.md +++ b/packages/erika_flutter/README.ja.md @@ -84,10 +84,11 @@ output status を更新します。API 35 では host の global Window を変 ## HarmonyOS Setup -HarmonyOS module には DevEco Studio の OpenHarmony Native SDK と Rust の -`aarch64-unknown-linux-ohos` target が必要です。Hvigor/CMake build が LGPL の -FFmpeg/zlib 依存と `liberika_capi.so` を compile し、その runtime を -`liberika_flutter.so` と一緒に package します。 +HarmonyOS module には DevEco Studio の OpenHarmony Native SDK が必要です。 +`ERIKA_PREBUILT=1` の場合、CMake は指定 Release から `liberika_capi.so` を +download して `liberika_flutter.so` と一緒に package します。それ以外では Rust +の `aarch64-unknown-linux-ohos` target が必要で、LGPL native dependency と runtime +を source build します。download 失敗時は source build に fallback します。 HarmonyOS では `ErikaVideoView` を使ってください。Flutter external texture を登録し、 その texture surface を `OHNativeWindow` として取得して、wgpu Vulkan で描画します。 diff --git a/packages/erika_flutter/README.md b/packages/erika_flutter/README.md index 0fcc404..dde6b85 100644 --- a/packages/erika_flutter/README.md +++ b/packages/erika_flutter/README.md @@ -59,8 +59,8 @@ points the build phase at an explicit dylib to bundle instead of building. To skip building Erika (and FFmpeg) from source, set `ERIKA_PREBUILT=1` in the app build to download the prebuilt `erika_capi` from a GitHub Release (`ERIKA_PREBUILT_TAG` selects the tag, default `v0.1.5`). Supported on macOS, -Windows, iOS, tvOS, and Android; any failure falls back to the source build. See -[`docs/releasing.md`](../../docs/releasing.md). +Windows, iOS, tvOS, Android, and OpenHarmony; any failure falls back to the +source build. See [`docs/releasing.md`](../../docs/releasing.md). When debugging local Erika source changes, set `ERIKA_FORCE_SOURCE_BUILD=1` to bypass the prebuilt download path even if the @@ -133,10 +133,11 @@ desired HDR headroom without changing the host window globally. ## HarmonyOS Setup -The HarmonyOS module requires DevEco Studio's OpenHarmony Native SDK and the -Rust `aarch64-unknown-linux-ohos` target. Its Hvigor/CMake build compiles the -LGPL FFmpeg/zlib dependencies and `liberika_capi.so`, then packages that runtime -alongside `liberika_flutter.so`. +The HarmonyOS module requires DevEco Studio's OpenHarmony Native SDK. With +`ERIKA_PREBUILT=1`, its CMake build downloads `liberika_capi.so` from the +selected release and packages it beside `liberika_flutter.so`; otherwise it +requires the Rust `aarch64-unknown-linux-ohos` target and builds the LGPL native +dependencies and runtime from source. Download failures fall back to source. Use `ErikaVideoView` on HarmonyOS. It registers a Flutter external texture, obtains the texture surface as an `OHNativeWindow`, and renders through wgpu diff --git a/packages/erika_flutter/README.zh.md b/packages/erika_flutter/README.zh.md index 0848a38..53bd18f 100644 --- a/packages/erika_flutter/README.zh.md +++ b/packages/erika_flutter/README.zh.md @@ -83,10 +83,10 @@ Android 最低版本仍为 API 26。Extended-linear 还要求 native-window data ## HarmonyOS Setup -HarmonyOS 模块需要 DevEco Studio 的 OpenHarmony Native SDK 和 Rust 的 -`aarch64-unknown-linux-ohos` target。它的 Hvigor/CMake 构建会编译 LGPL 的 -FFmpeg/zlib 依赖和 `liberika_capi.so`,然后把这套运行时和 `liberika_flutter.so` -一起打包。 +HarmonyOS 模块需要 DevEco Studio 的 OpenHarmony Native SDK。设置 +`ERIKA_PREBUILT=1` 后,CMake 会从指定 Release 下载 `liberika_capi.so`,并与 +`liberika_flutter.so` 一起打包;否则需要 Rust 的 `aarch64-unknown-linux-ohos` +target,并从源码构建 LGPL 原生依赖和 runtime。下载失败会自动回退源码构建。 HarmonyOS 上请使用 `ErikaVideoView`。它注册 Flutter 外部纹理,把纹理 surface 取为 `OHNativeWindow`,并通过 wgpu Vulkan 渲染。音频走 OHAudio,交错 f32 PCM。 diff --git a/packages/erika_flutter/ohos/src/main/cpp/CMakeLists.txt b/packages/erika_flutter/ohos/src/main/cpp/CMakeLists.txt index 100a80c..42c0647 100644 --- a/packages/erika_flutter/ohos/src/main/cpp/CMakeLists.txt +++ b/packages/erika_flutter/ohos/src/main/cpp/CMakeLists.txt @@ -15,11 +15,78 @@ set(ERIKA_RUNTIME "${ERIKA_ROOT}/target/${ERIKA_TARGET}/release/liberika_capi.so" ) +set(ERIKA_USE_PREBUILT OFF) +if( + "$ENV{ERIKA_PREBUILT}" STREQUAL "1" + AND NOT "$ENV{ERIKA_FORCE_SOURCE_BUILD}" STREQUAL "1" +) + if("$ENV{ERIKA_PREBUILT_TAG}" STREQUAL "") + set(ERIKA_PREBUILT_TAG "v0.1.5") + else() + set(ERIKA_PREBUILT_TAG "$ENV{ERIKA_PREBUILT_TAG}") + endif() + string(REGEX REPLACE "[^A-Za-z0-9._-]" "_" ERIKA_PREBUILT_CACHE_KEY + "${ERIKA_PREBUILT_TAG}" + ) + set(ERIKA_PREBUILT_DIR + "${CMAKE_CURRENT_BINARY_DIR}/erika-prebuilt-${ERIKA_PREBUILT_CACHE_KEY}" + ) + set(ERIKA_PREBUILT_ZIP + "${ERIKA_PREBUILT_DIR}/erika-capi-openharmony-arm64.zip" + ) + set(ERIKA_PREBUILT_RUNTIME + "${ERIKA_PREBUILT_DIR}/erika-capi-openharmony-arm64/lib/liberika_capi.so" + ) + set(ERIKA_PREBUILT_URL + "https://github.com/AimesSoft/Erika/releases/download/${ERIKA_PREBUILT_TAG}/erika-capi-openharmony-arm64.zip" + ) + + if(NOT EXISTS "${ERIKA_PREBUILT_RUNTIME}") + file(MAKE_DIRECTORY "${ERIKA_PREBUILT_DIR}") + message(STATUS "Erika: downloading prebuilt ${ERIKA_PREBUILT_URL}") + file( + DOWNLOAD "${ERIKA_PREBUILT_URL}" "${ERIKA_PREBUILT_ZIP}" + STATUS ERIKA_PREBUILT_DOWNLOAD_STATUS + SHOW_PROGRESS + TLS_VERIFY ON + TIMEOUT 120 + INACTIVITY_TIMEOUT 30 + ) + list(GET ERIKA_PREBUILT_DOWNLOAD_STATUS 0 ERIKA_PREBUILT_DOWNLOAD_CODE) + if(ERIKA_PREBUILT_DOWNLOAD_CODE EQUAL 0) + execute_process( + COMMAND "${CMAKE_COMMAND}" -E tar xf "${ERIKA_PREBUILT_ZIP}" + WORKING_DIRECTORY "${ERIKA_PREBUILT_DIR}" + RESULT_VARIABLE ERIKA_PREBUILT_EXTRACT_STATUS + ) + else() + list(GET ERIKA_PREBUILT_DOWNLOAD_STATUS 1 ERIKA_PREBUILT_DOWNLOAD_MESSAGE) + message(WARNING + "Erika: prebuilt download failed (${ERIKA_PREBUILT_DOWNLOAD_MESSAGE}); building from source" + ) + endif() + endif() + + set(ERIKA_PREBUILT_RUNTIME_VALID OFF) + if(EXISTS "${ERIKA_PREBUILT_RUNTIME}") + file(SIZE "${ERIKA_PREBUILT_RUNTIME}" ERIKA_PREBUILT_RUNTIME_SIZE) + if(ERIKA_PREBUILT_RUNTIME_SIZE GREATER 0) + set(ERIKA_PREBUILT_RUNTIME_VALID ON) + endif() + endif() + if(ERIKA_PREBUILT_RUNTIME_VALID) + set(ERIKA_RUNTIME "${ERIKA_PREBUILT_RUNTIME}") + set(ERIKA_USE_PREBUILT ON) + message(STATUS "Erika: using prebuilt ${ERIKA_PREBUILT_TAG}") + else() + message(STATUS "Erika: prebuilt unavailable; building from source") + endif() +endif() + if(NOT DEFINED OHOS_SDK_NATIVE) message(FATAL_ERROR "OHOS_SDK_NATIVE is required to build Erika for OpenHarmony") endif() -find_program(CARGO_EXECUTABLE cargo REQUIRED) set(OHOS_LLVM_BIN "${OHOS_SDK_NATIVE}/llvm/bin") set(OHOS_CXX_INCLUDE "${OHOS_SDK_NATIVE}/llvm/include/libcxx-ohos/include/c++/v1" @@ -30,50 +97,57 @@ endif() set(OHOS_BINDGEN_ARGS "--target=${ERIKA_TARGET} --sysroot=${OHOS_SDK_NATIVE}/sysroot -isystem ${OHOS_CXX_INCLUDE}" ) -file(GLOB_RECURSE ERIKA_RUST_SOURCES CONFIGURE_DEPENDS - "${ERIKA_ROOT}/crates/*.rs" - "${ERIKA_ROOT}/third_party/wgpu-hal/*.rs" - "${ERIKA_ROOT}/xtask/*.rs" -) -list(APPEND ERIKA_RUST_SOURCES - "${ERIKA_ROOT}/Cargo.toml" - "${ERIKA_ROOT}/Cargo.lock" -) +if(NOT ERIKA_USE_PREBUILT) + find_program(CARGO_EXECUTABLE cargo REQUIRED) + file(GLOB_RECURSE ERIKA_RUST_SOURCES CONFIGURE_DEPENDS + "${ERIKA_ROOT}/crates/*.rs" + "${ERIKA_ROOT}/third_party/wgpu-hal/*.rs" + "${ERIKA_ROOT}/xtask/*.rs" + ) + list(APPEND ERIKA_RUST_SOURCES + "${ERIKA_ROOT}/Cargo.toml" + "${ERIKA_ROOT}/Cargo.lock" + ) -add_custom_command( - OUTPUT "${ERIKA_RUNTIME}" - COMMAND - "${CMAKE_COMMAND}" -E env - "OHOS_NDK_HOME=${OHOS_SDK_NATIVE}" - "${CARGO_EXECUTABLE}" +1.93.0 run -q -p xtask -- - deps build --profile lgpl --target "${ERIKA_TARGET}" - COMMAND - "${CMAKE_COMMAND}" -E env - "ERIKA_NATIVE_PROFILE=lgpl" - "ERIKA_NATIVE_TARGET=${ERIKA_TARGET}" - "OHOS_NDK_HOME=${OHOS_SDK_NATIVE}" - "LIBCLANG_PATH=${OHOS_SDK_NATIVE}/llvm/lib" - "BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_ohos=${OHOS_BINDGEN_ARGS}" - "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_OHOS_LINKER=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang" - "CC_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang" - "CXX_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang++" - "AR_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/llvm-ar" - "${CARGO_EXECUTABLE}" +1.93.0 build -p erika_capi - --target "${ERIKA_TARGET}" --release - --no-default-features --features wgpu - WORKING_DIRECTORY "${ERIKA_ROOT}" - DEPENDS ${ERIKA_RUST_SOURCES} - COMMENT "Building Erika OpenHarmony runtime" - VERBATIM -) -add_custom_target(erika_ohos_runtime DEPENDS "${ERIKA_RUNTIME}") + add_custom_command( + OUTPUT "${ERIKA_RUNTIME}" + COMMAND + "${CMAKE_COMMAND}" -E env + "OHOS_NDK_HOME=${OHOS_SDK_NATIVE}" + "${CARGO_EXECUTABLE}" +1.93.0 run -q -p xtask -- + deps build --profile lgpl --target "${ERIKA_TARGET}" + COMMAND + "${CMAKE_COMMAND}" -E env + "ERIKA_NATIVE_PROFILE=lgpl" + "ERIKA_NATIVE_TARGET=${ERIKA_TARGET}" + "OHOS_NDK_HOME=${OHOS_SDK_NATIVE}" + "LIBCLANG_PATH=${OHOS_SDK_NATIVE}/llvm/lib" + "BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_ohos=${OHOS_BINDGEN_ARGS}" + "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_OHOS_LINKER=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang" + "CC_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang" + "CXX_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/aarch64-unknown-linux-ohos-clang++" + "AR_aarch64_unknown_linux_ohos=${OHOS_LLVM_BIN}/llvm-ar" + "${CARGO_EXECUTABLE}" +1.93.0 build -p erika_capi + --target "${ERIKA_TARGET}" --release + --no-default-features --features wgpu + WORKING_DIRECTORY "${ERIKA_ROOT}" + DEPENDS ${ERIKA_RUST_SOURCES} + COMMENT "Building Erika OpenHarmony runtime" + VERBATIM + ) + add_custom_target(erika_ohos_runtime DEPENDS "${ERIKA_RUNTIME}") +endif() add_library(erika_capi SHARED IMPORTED GLOBAL) set_target_properties(erika_capi PROPERTIES IMPORTED_LOCATION "${ERIKA_RUNTIME}") -add_dependencies(erika_capi erika_ohos_runtime) +if(NOT ERIKA_USE_PREBUILT) + add_dependencies(erika_capi erika_ohos_runtime) +endif() add_library(${PLUGIN_NAME} SHARED erika_flutter_plugin.cpp) -add_dependencies(${PLUGIN_NAME} erika_ohos_runtime) +if(NOT ERIKA_USE_PREBUILT) + add_dependencies(${PLUGIN_NAME} erika_ohos_runtime) +endif() set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden OUTPUT_NAME "erika_flutter" @@ -94,3 +168,10 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE libnative_window.so libhilog_ndk.z.so ) +add_custom_command( + TARGET ${PLUGIN_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${ERIKA_RUNTIME}" "$/liberika_capi.so" + COMMENT "Staging Erika OpenHarmony runtime beside the Flutter plugin" + VERBATIM +) diff --git a/packages/erika_flutter/test/apple_memory_font_contract_test.dart b/packages/erika_flutter/test/apple_memory_font_contract_test.dart index f6b3b94..f8e1032 100644 --- a/packages/erika_flutter/test/apple_memory_font_contract_test.dart +++ b/packages/erika_flutter/test/apple_memory_font_contract_test.dart @@ -33,4 +33,21 @@ void main() { } }); } + + test('release builds complete Apple XCFrameworks on macOS 26', () { + final workflow = File( + '../../.github/workflows/release.yml', + ).readAsStringSync(); + + for (final target in [ + 'aarch64-apple-tvos', + 'aarch64-apple-tvos-sim', + 'x86_64-apple-tvos', + ]) { + expect(workflow, contains(target)); + } + expect(workflow, contains('dist/erika-capi-tvos.zip')); + expect(workflow, contains('runs-on: macos-26')); + expect(workflow, isNot(contains('macos-15-intel'))); + }); } diff --git a/packages/erika_flutter/test/ohos_screenshot_contract_test.dart b/packages/erika_flutter/test/ohos_screenshot_contract_test.dart index bb5529b..80cd197 100644 --- a/packages/erika_flutter/test/ohos_screenshot_contract_test.dart +++ b/packages/erika_flutter/test/ohos_screenshot_contract_test.dart @@ -65,4 +65,23 @@ void main() { ), ); }); + + test('OpenHarmony can consume and package the prebuilt runtime', () { + final cmake = File( + 'ohos/src/main/cpp/CMakeLists.txt', + ).readAsStringSync(); + + expect(cmake, contains(r'$ENV{ERIKA_PREBUILT}')); + expect(cmake, contains(r'$ENV{ERIKA_PREBUILT_TAG}')); + expect( + cmake, + contains('erika-capi-openharmony-arm64.zip'), + ); + expect(cmake, contains('ERIKA_USE_PREBUILT')); + expect(cmake, contains('copy_if_different')); + expect( + cmake, + contains(r'$/liberika_capi.so'), + ); + }); } From fd27f130d3ba0ac06ffceb72b1368eb2dc32fe68 Mon Sep 17 00:00:00 2001 From: Sakiko Date: Tue, 4 Aug 2026 00:08:31 +0800 Subject: [PATCH 3/3] fix(release): scope Apple deployment targets --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f403966..0a95ec5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,8 +27,6 @@ permissions: env: PROFILE: lgpl - IPHONEOS_DEPLOYMENT_TARGET: "13.0" - TVOS_DEPLOYMENT_TARGET: "13.0" ANDROID_API_LEVEL: "26" ANDROID_NDK_VERSION: "29.0.14206865" CARGO_TERM_COLOR: always @@ -39,6 +37,8 @@ jobs: name: macOS ${{ matrix.arch }} runs-on: ${{ matrix.runner }} timeout-minutes: 150 + env: + MACOSX_DEPLOYMENT_TARGET: "11.0" strategy: fail-fast: false matrix: @@ -119,6 +119,8 @@ jobs: name: iOS (XCFramework) runs-on: macos-26 timeout-minutes: 180 + env: + IPHONEOS_DEPLOYMENT_TARGET: "13.0" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -168,6 +170,8 @@ jobs: name: tvOS (XCFramework) runs-on: macos-26 timeout-minutes: 240 + env: + TVOS_DEPLOYMENT_TARGET: "13.0" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly