diff --git a/.cspell-wordlist.txt b/.cspell-wordlist.txt index 84d006eefe..dc093ea5a7 100644 --- a/.cspell-wordlist.txt +++ b/.cspell-wordlist.txt @@ -203,3 +203,49 @@ fishjam Fishjam deinitialize Deinitialize +podspec +libexecutorch +libxnnpack +libvulkan +libbackend +libcpuinfo +flatcc +Werror +dlopen +msluszniak +DRNE +libopencv +Kleidi +libphonemis +libreact +CPLUSPLUSFLAGS +LDFLAGS +iphoneos +iphonesimulator +xcframework +xcframeworks +EEXIST +RNET +Unigram +SIGSEGV +memcmp +nullptr +DANDROID +venv +pyyaml +libkernels +libthreadpool +libkleidiai +kleidiai +xcodeproj +prebuilts +pthreadpool +libpthreadpool +DFLATCC +flatccrt +Wimplicit +Wunterminated +NSURL +certifi +zstd +lintrunner diff --git a/.github/workflows/build-android-llm-example.yml b/.github/workflows/build-android-llm-example.yml index 3b4c95bf58..ad8bb29960 100644 --- a/.github/workflows/build-android-llm-example.yml +++ b/.github/workflows/build-android-llm-example.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest env: WORKING_DIRECTORY: apps/llm + # TODO: drop once v0.9.0 is cut and tarballs are attached to the matching Release. + RNET_BASE_URL: https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test concurrency: group: android-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/build-ios-llm-example.yml b/.github/workflows/build-ios-llm-example.yml index 4a2b2b9b99..8fa02fc6fa 100644 --- a/.github/workflows/build-ios-llm-example.yml +++ b/.github/workflows/build-ios-llm-example.yml @@ -22,6 +22,9 @@ jobs: concurrency: group: ios-${{ github.ref }} cancel-in-progress: true + env: + # TODO: drop once v0.9.0 is cut and tarballs are attached to the matching Release. + RNET_BASE_URL: https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test steps: - uses: maxim-lobanov/setup-xcode@v1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96fd27ad65..58a3884652 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,9 @@ on: types: - checks_requested workflow_dispatch: +env: + # TODO: drop once v0.9.0 is cut and tarballs are attached to the matching Release. + RNET_BASE_URL: https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test jobs: lint: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 86ce3a9042..a52b810e9e 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,17 @@ docs/docs/06-api-reference/ # integration test model assets packages/react-native-executorch/common/rnexecutorch/tests/integration/assets/models/ +# release artifact staging dir (produced by scripts/package-release-artifacts.sh) +packages/react-native-executorch/dist-artifacts/ + +# on-demand native libs (downloaded at postinstall time, not committed) +packages/react-native-executorch/third-party/android/libs/ +packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ +packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework/ +packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework/ +packages/react-native-executorch/third-party/ios/libs/ +packages/react-native-executorch/rne-build-config.json + # custom *.tgz Makefile diff --git a/docs/docs/01-fundamentals/01-getting-started.md b/docs/docs/01-fundamentals/01-getting-started.md index 790aa8ac33..daae38cfe8 100644 --- a/docs/docs/01-fundamentals/01-getting-started.md +++ b/docs/docs/01-fundamentals/01-getting-started.md @@ -76,6 +76,42 @@ Installation is pretty straightforward, use your package manager of choice to in +### Configuring backends and extras + +On install, `react-native-executorch` runs a `postinstall` script that downloads prebuilt native libraries from the matching GitHub Release and unpacks them under `third-party/`. By default every optional feature is included — which keeps the app binary large. You can opt out of anything you don't need by adding an `extras` array to your app's `package.json`: + +```json +{ + "react-native-executorch": { + "extras": ["xnnpack", "coreml", "vulkan", "opencv", "phonemizer"] + } +} +``` + +If the `extras` key is omitted, all five features are enabled. To disable a feature, drop its name from the array. + +| Extra | iOS | Android | What it enables | +| ------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| `opencv` | ✅ (via the `opencv-rne` CocoaPod) | ✅ | Computer-vision models (classification, detection, OCR, etc.) | +| `phonemizer` | ✅ | ✅ | Text-to-speech models | +| `xnnpack` | ✅ — `XnnpackBackend.xcframework` force-loaded into the app | ✅ — separately-loaded `libxnnpack_executorch_backend.so` | XNNPACK CPU backend (required for most quantized models) | +| `coreml` | ✅ — `CoreMLBackend.xcframework` force-loaded into the app | n/a (CoreML is iOS-only) | Core ML backend (Apple Neural Engine / GPU acceleration) | +| `vulkan` | n/a (Vulkan is Android-only) | ✅ — separately-loaded `libvulkan_executorch_backend.so` | Vulkan GPU backend | + +Source files and native libraries are excluded from compilation when an extra is disabled, so builds that only need LLMs can skip OpenCV and cut tens of megabytes off the final binary. + +The postinstall step honors a few environment variables: + +| Variable | Purpose | +| ---------------------- | ------------------------------------------------------------------------- | +| `RNET_SKIP_DOWNLOAD=1` | Skip the download entirely (for CI with pre-cached libraries). | +| `RNET_LIBS_CACHE_DIR` | Custom cache directory (default: `~/.cache/react-native-executorch/`). | +| `RNET_TARGET` | Force a specific target, e.g. `android-arm64-v8a` or `ios`. | +| `RNET_NO_X86_64=1` | Skip the Android x86_64 tarball (handy when only building for a device). | +| `GITHUB_TOKEN` | Required to access draft releases while iterating on a new version. | + +After changing `extras`, re-run `yarn install` (or the equivalent) so the postinstall script regenerates `rne-build-config.json` and re-extracts the right tarballs, then rebuild the native project. + :::warning Before using any other API, you must call `initExecutorch` with a resource fetcher adapter at the entry point of your app: diff --git a/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md b/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md new file mode 100644 index 0000000000..3bc694ab61 --- /dev/null +++ b/packages/react-native-executorch/NATIVE_LIBS_PIPELINE.md @@ -0,0 +1,239 @@ +# Native libraries pipeline + +This document describes how native dependencies (ExecuTorch runtime, backends, OpenCV, phonemizer) are produced, shipped, and stitched into an app build. It is intended for maintainers — the user-facing summary lives in `docs/docs/01-fundamentals/01-getting-started.md`. + +## High-level flow + +``` + ┌──────────────────────┐ ┌────────────────────────┐ ┌───────────────────────┐ + │ ExecuTorch fork │ ───▶ │ GitHub Release v │ ───▶ │ postinstall script │ + │ + our patches │ │ .tar.gz │ │ download-libs.js │ + │ (separate repo) │ │ .tar.gz.256 │ │ │ + └──────────────────────┘ └────────────────────────┘ └───────────┬───────────┘ + │ + ▼ + ┌───────────────────────┐ + │ third-party/android │ + │ third-party/ios │ + │ rne-build-config.json│ + └───────────┬───────────┘ + │ + ┌───────────────────────────┴────────────────────────────┐ + ▼ ▼ + ┌───────────────────────┐ ┌─────────────────────────┐ + │ android/build.gradle │ │ react-native-executorch │ + │ + CMakeLists.txt │ │ .podspec │ + │ -DRNE_ENABLE_* │ │ -DRNE_ENABLE_* │ + └───────────────────────┘ │ force_load xcframeworks │ + └─────────────────────────┘ +``` + +## Install-time: `scripts/download-libs.js` + +Runs at `postinstall`. Responsibilities: + +1. Read `react-native-executorch.extras` from the app's `package.json` (uses `INIT_CWD`). Defaults to `["opencv", "phonemizer", "xnnpack", "coreml", "vulkan"]`. +2. Write `rne-build-config.json` at the package root with boolean flags — this file is the single source of truth consumed by both the Gradle build and the podspec. +3. Detect targets (`ios` on macOS; always `android-arm64-v8a` and, unless `RNET_NO_X86_64` is set, `android-x86_64`). +4. For each target × enabled extra, fetch the corresponding `.tar.gz` from the GitHub Release tagged `v${PACKAGE_VERSION}`, verify the `.sha256`, and extract into `third-party/android/libs/` or `third-party/ios/`. +5. Cache validated tarballs under `~/.cache/react-native-executorch//` so subsequent installs skip the network. + +Environment overrides: `RNET_SKIP_DOWNLOAD`, `RNET_LIBS_CACHE_DIR`, `RNET_TARGET`, `RNET_BASE_URL` (useful with `python3 -m http.server` against `dist-artifacts/` for local iteration), `GITHUB_TOKEN` (needed for draft releases). + +The set of artifacts per target is defined in `getArtifacts()`: + +| Artifact name | Target | Produced by | Contents | +| ---------------------------------------- | ------- | ---------------------------------------- | ------------------------------------------------------ | +| `core-android-arm64-v8a` | Android | ExecuTorch fork build | `libexecutorch.so` (no backends), headers | +| `core-android-x86_64` | Android | ExecuTorch fork build | x86_64 `libexecutorch.so` for the simulator | +| `xnnpack-android-*` | Android | ExecuTorch fork build | `libxnnpack_executorch_backend.so` (separately-loaded) | +| `vulkan-android-*` | Android | ExecuTorch fork build | `libvulkan_executorch_backend.so` (separately-loaded) | +| `core-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `ExecutorchLib.xcframework` | +| `xnnpack-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `XnnpackBackend.xcframework` | +| `coreml-ios` | iOS | `third-party/ios/ExecutorchLib/build.sh` | `CoreMLBackend.xcframework` | +| `opencv-android-*` | Android | OpenCV release process | Static OpenCV + KleidiCV HAL | +| `phonemizer-android-*`, `phonemizer-ios` | both | phonemizer build | `libphonemis.a` (iOS: physical + simulator) | + +(`opencv-ios` is not a tarball — iOS consumes OpenCV through the `opencv-rne` CocoaPod.) + +## Build-time: Android + +`android/build.gradle` reads `rne-build-config.json` once and forwards the booleans to CMake: + +```groovy +"-DRNE_ENABLE_OPENCV=${rneBuildConfig.enableOpencv ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_PHONEMIZER=${rneBuildConfig.enablePhonemizer ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_XNNPACK=${rneBuildConfig.enableXnnpack ? 'ON' : 'OFF'}", +"-DRNE_ENABLE_VULKAN=${rneBuildConfig.enableVulkan ? 'ON' : 'OFF'}" +``` + +`android/CMakeLists.txt` and `android/src/main/cpp/CMakeLists.txt` respond by: + +- Adding `-DRNE_ENABLE_OPENCV` / `-DRNE_ENABLE_PHONEMIZER` compile definitions so C++ code can `#ifdef` around optional dependencies. +- Conditionally linking `libopencv_*.a`, KleidiCV HAL (arm64 only), and `libphonemis.a`. +- Always linking against the prebuilt `libexecutorch.so` downloaded into `third-party/android/libs/executorch//`. +- When `RNE_ENABLE_XNNPACK=ON` / `RNE_ENABLE_VULKAN=ON`, importing the matching `libxnnpack_executorch_backend.so` / `libvulkan_executorch_backend.so` and linking `react-native-executorch.so` against it. Linking (rather than dynamic `dlopen`) lets Gradle bundle the `.so` into the APK and triggers the dynamic linker to load it whenever `libreact-native-executorch.so` is loaded — each `.so`'s load-time constructor then registers its backend with the runtime in `libexecutorch.so`. + +## Build-time: iOS + +`react-native-executorch.podspec` reads the same `rne-build-config.json` and: + +- Excludes opencv/phonemizer C++ sources from compilation when those extras are off. +- Appends `-DRNE_ENABLE_*` to `OTHER_CPLUSPLUSFLAGS`. +- Assembles `OTHER_LDFLAGS[sdk=iphoneos*]` and `OTHER_LDFLAGS[sdk=iphonesimulator*]` with `-force_load` entries for each enabled backend xcframework. +- Declares `ExecutorchLib.xcframework` in `vendored_frameworks` but _not_ the backend xcframeworks — backend xcframeworks only live on the linker command line, never in the CocoaPods vendoring list (see next section for why). +- Adds `sqlite3` and the `CoreML` system framework to linkage only when Core ML is enabled. + +## Why backends differ between platforms + +ExecuTorch registers kernels statically via `__attribute__((constructor))` functions inside each backend's `.a`/`.so`. Two design points fall out of this: + +1. **Force-load is required.** Linkers drop unreferenced object files. The registrar symbols have no external users (they run as global constructors at load time), so a plain link keeps the backend library on disk but strips the registration symbols — and the app then fails with `Missing operator: ...` at inference. Every backend library must be force-loaded (`-force_load` on iOS, `--whole-archive` on Android, or `executorch_target_link_options_shared_lib(...)` in ExecuTorch's own CMake helpers). + +2. **A single copy of each CPU-kernel registration must exist.** Multiple backend libraries that each whole-archive-link `optimized_native_cpu_ops_lib` cause duplicate kernel-registration aborts (`error 22 EEXIST`) when both get force-loaded into the same process. + +On **iOS**, each backend ships as its own static xcframework (`XnnpackBackend.xcframework`, `CoreMLBackend.xcframework`). The podspec force-loads only the ones the user opted into, and `ExecutorchLib.xcframework` itself does not whole-archive the CPU ops — so there is no duplicate registration. + +On **Android**, the first iteration of the split hit a duplicate-registration abort (`Error::RegistrationAlreadyRegistered`, 0x16) because the ExecuTorch Android build whole-archive-linked the CPU ops (`optimized_native_cpu_ops_lib`, `custom_ops`, `quantized_ops_lib`, `register_prim_ops`) into each backend shared library, AND `extension/llm/custom_ops` PUBLIC-linked `xnnpack_backend` so XNNCompiler/XNNExecutor were getting WHOLE_ARCHIVE-pulled into `libexecutorch_jni.so` via `custom_ops`. Two fixes in the ExecuTorch fork resolve this: + +1. New `EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED` and `EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED` switches build `libxnnpack_executorch_backend.so` / `libvulkan_executorch_backend.so` linking only the backend's own static archive (--whole-archive) + the backend's schema/third-party deps + `executorch_core` — no kernel-registration archives. Loading either on top of `libexecutorch.so` does not duplicate any registration. +2. When `EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED=ON`, `extension/llm/custom_ops` drops the `xnnpack_backend` link (custom_ops doesn't actually call into XNNPACK anyway) so the WHOLE_ARCHIVE on `custom_ops` no longer drags XNNPACK code into `libexecutorch_jni.so`. + +Result: each Android backend ships as its own opt-in tarball, mirroring the iOS xcframework setup. + +## Building artifacts from the ExecuTorch fork + +Patched sources live in a separate repo (see `executorch/` in the maintainer's machine, typically checked out next to `react-native-executorch/`). The fork branch is [`msluszniak/executorch@ms/separate-backends`](https://github.com/msluszniak/executorch/tree/@ms/separate-backends), based on the upstream `release/1.2` tag, and the artifacts attached to the matching GitHub Release are produced from the pinned commit [`3ce953dbde73035e733442f99c082f5b6fedff5b`](https://github.com/msluszniak/executorch/commit/3ce953dbde73035e733442f99c082f5b6fedff5b). Bumping the `react-native-executorch` package version requires re-rolling the Release artifacts from a (possibly newer) pinned commit and updating this SHA. The branch carries (oldest → newest): + +- **chore: remove version script from `executorch_jni`** — reverts the Feb 2026 symbol-hiding change so RNE's C++ layer can resolve `Module`, `threadpool`, etc. directly. +- **feat: build `vulkan_backend` as a separate shared library on Android** — adds the `EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED` CMake option. When ON, `libvulkan_executorch_backend.so` is produced alongside `libexecutorch_jni.so` instead of vulkan_backend being whole-archive-linked into the latter. Mirrors the QNN backend pattern. +- **build: disable `-Werror` for `flatcc_ep` on host clang** — Apple clang 21+ flags warnings flatcc has not yet cleaned up; needed to build on Xcode 26.4+. Adds `-DFLATCC_ALLOW_WERROR=OFF` to the host-side `flatcc_ep` ExternalProject_Add in `third-party/CMakeLists.txt`. +- **feat: build `xnnpack_backend` as a separate shared library on Android** — same idea for XNNPACK via `EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED`. Also patches `extension/llm/custom_ops/CMakeLists.txt` to drop the (transitive) `xnnpack_backend` link when the switch is ON, so XNNPACK code does not leak into `libexecutorch_jni.so` via `custom_ops`. +- **chore: point tokenizers submodule at `software-mansion-labs/pytorch-tokenizers@build`** — replaces the old `meta-pytorch/tokenizers` pin with the SWM-internal build branch (`56a30afb…`). That branch carries support for new tokenizer types (Unigram, WordLevel) plus more normalizers / pre-tokenizers / decoders / post-processors. Headers in `third-party/include/executorch/extension/llm/tokenizers/` must match this commit; otherwise `HFTokenizer::setup_padding` / `setup_truncation` SIGSEGV when loading a real tokenizer.json. +- **build(android): forward `BACKEND_SHARED` env vars to cmake** — `build_android_library.sh` declared the `EXECUTORCH_BUILD_*_BACKEND_SHARED` env vars in surrounding docs but never passed them to cmake configure, so a fresh CMake configure produced backends baked into `libexecutorch_jni.so` even when the env vars were set. +- **fix(xnnpack): tolerate null ptr in `XNNWeightsCache::look_up_or_insert`** — `memcmp(ptr=NULL, saved_ptr, size)` crashed when XNNPACK re-checked the cache. Guard with `if (ptr == nullptr) ptr = saved_ptr;` before the compare. +- **build(android): pass `-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON`** — required for Android 16 KB-page devices (Pixel 9 / upcoming releases); without it the `.so` fails to map on those targets. +- **build(ios): keep merged intermediate `.a` files after `create_frameworks.sh`** — comments out the cleanup loop at the end of `create_frameworks.sh`. `build_apple_frameworks.sh` calls `create_frameworks.sh` to merge per-target archives via `libtool`, then deletes them by default. RNE's `third-party/ios/ExecutorchLib/build.sh` repackages those `.a` files into its own xcframeworks (with consistent slice library names that CocoaPods requires), so we need them to survive long enough to be repackaged. +- **build(ios): disable `XNNPACK_ENABLE_ARM_SME{,2}` on macOS / iOS presets** — the Apple SME backends in upstream XNNPACK fail to compile under Xcode 26's clang. Disable both for the `macos` / `ios` / `ios-simulator` presets in `CMakePresets.json` so the iOS build succeeds. +- **build(ios): disable `FLATCC_ALLOW_WERROR` on iOS / macOS presets** — the host-side `flatcc_ep` patch only affects the generator binary. Inside the iOS / macOS builds, flatcc's runtime (`flatccrt`) is rebuilt as a regular cmake target via `add_subdirectory(third-party/flatcc)`, which honors a separate cache variable. Pin `FLATCC_ALLOW_WERROR=OFF` for each Apple preset in `CMakePresets.json` so the runtime build also escapes Apple clang 21's stricter warnings (`-Wimplicit-int-conversion-on-negation`, `-Wunterminated-string-initialization`). + +### iOS + +Two-stage: the fork produces the merged per-slice `.a` files; RNE's `build.sh` repackages them into xcframeworks. Requires Xcode 26.x + Python 3.10. + +> **iOS 26.4 simulator caveat.** Apps linked against the iOS 26.4 SDK fail `URLSessionConfiguration.background` downloads in the 26.4 simulator with `NSURLErrorUnknown`. Older SDK builds (e.g. 26.2) are unaffected, and physical devices behave normally. This is an Apple regression — there's no fix on our end. Either use an iOS 26.2 sim for `ExpoResourceFetcher`-driven testing, fall back to `FOREGROUND` session type temporarily, or test on a real device. + +**1. Set up the Python env in the fork** + +```bash +# from the executorch fork (with @ms/separate-backends checked out) +python3 -m venv .venv && source .venv/bin/activate +pip install certifi zstd # for tools/cmake/resolve_buck.py +pip install torch==2.11.0 # cmake's find_package_torch_headers needs the torch wheel +pip install -r requirements-dev.txt # pyyaml, cmake, lintrunner, click — for codegen scripts +``` + +`install_executorch.sh` is **not** used: `torch_pin.py` pins a torch nightly (`2.11.0.dev20260215`) that's been pruned from the PyTorch nightly index, and the iOS build only needs the wheel installed for `find_package_torch_headers`. Pinning the matching stable `torch==2.11.0` is enough. + +**2. Build merged `.a` files** + +```bash +rm -rf cmake-out # always start clean — partial builds cache stale CMake options +./scripts/build_apple_frameworks.sh --Release +``` + +This drives Buck2 + cmake for the `ios`, `ios-simulator`, and `macos` presets, then calls the patched `create_frameworks.sh` with the right `--directory` / `--framework` flags to merge per-target archives via `libtool`. Outputs land in `cmake-out/`: + +- `cmake-out/libexecutorch_{ios,simulator,macos}.a`, `libexecutorch_llm_*`, `libkernels_*`, `libbackend_{xnnpack,coreml,mps}_*`, `libthreadpool_*` — the merged static archives the patched cleanup loop keeps around. +- `cmake-out/.xcframework` — produced by `xcodebuild -create-xcframework`, **not** what RNE consumes (RNE builds its own slimmer set via `ExecutorchLib/build.sh`). + +> The Swift Package generation step at the very end of the script prints +> `error: local binary target '_debug' at 'cmake-out/_debug.xcframework' does not contain a binary artifact` for each framework. That's harmless — it only fires when `--Debug` artifacts are missing — and `--Release` exits 0 anyway. The `.a` files are produced before this step runs. + +**3. Stage `.a` files into RNE** + +Copy only the `_ios` and `_simulator` slices into `packages/react-native-executorch/third-party/ios/libs/executorch/`. Skip the `_macos` files (RNE doesn't ship a macOS slice). Per-slice list to copy: + +``` +libbackend_coreml_{ios,simulator}.a +libbackend_mps_{ios,simulator}.a +libbackend_xnnpack_{ios,simulator}.a +libexecutorch_{ios,simulator}.a +libexecutorch_llm_{ios,simulator}.a +libkernels_llm_{ios,simulator}.a +libkernels_optimized_{ios,simulator}.a +libkernels_quantized_{ios,simulator}.a +libkernels_torchao_{ios,simulator}.a +libthreadpool_{ios,simulator}.a +``` + +Keep the existing `libkleidiai_{ios,simulator}.a` — kleidiai is merged into `libexecutorch_llm_*.a` by upstream's framework definitions, but RNE's `ExecutorchLib.xcodeproj` still references the standalone libs explicitly. They're stable and don't need rebuilding. + +The non-executorch prebuilts (`libs/cpuinfo/libcpuinfo.a`, `libs/pthreadpool/{physical-arm64-release,simulator-arm64-debug}/libpthreadpool.a`, `libs/phonemis/*/libphonemis.a`) live in their existing directories and are not produced by the executorch fork build — they ship as-is from prior tarballs. + +**4. Build xcframeworks** + +```bash +# from RNE +rm -rf packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework +rm -rf packages/react-native-executorch/third-party/ios/CoreMLBackend.xcframework +rm -rf packages/react-native-executorch/third-party/ios/XnnpackBackend.xcframework +cd packages/react-native-executorch/third-party/ios/ExecutorchLib +./build.sh +``` + +The script drives Xcode to archive the Obj-C++ wrapper for device and simulator, then uses `xcodebuild -create-xcframework` to produce: + +- `output/ExecutorchLib.xcframework` — the high-level wrapper + ExecuTorch core + kernels + threadpool + MPS backend. Does **not** contain XNNPACK or CoreML code (those live in their own xcframeworks). +- `output/XnnpackBackend.xcframework` — repackaged from `third-party/ios/libs/executorch/libbackend_xnnpack_{ios,simulator}.a`. +- `output/CoreMLBackend.xcframework` — repackaged from `libbackend_coreml_{ios,simulator}.a`. + +Move each `.xcframework` from `output/` up one level into `third-party/ios/`, then delete `output/` and `build/`. + +CocoaPods constraint: inside an xcframework, the library file name must be identical across slices, which is why `build.sh` copies each slice into a temp directory and renames before calling `-create-xcframework`. Do not skip this step. + +### Android + +Use `scripts/build_android_library.sh` from the fork (with the `@ms/separate-backends` branch checked out). It already passes the right preset and flags. Just enable the two extras we need: + +```bash +# from the executorch fork +export ANDROID_NDK=$HOME/Library/Android/sdk/ndk/27.1.12297006 +EXECUTORCH_BUILD_VULKAN=ON \ +EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED=ON \ +EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED=ON \ +ANDROID_ABI=arm64-v8a ./scripts/build_android_library.sh # repeat with x86_64 +``` + +Outputs land in `cmake-out-android-/extension/android/`: + +- `libexecutorch_jni.so` → copy to `third-party/android/libs/executorch//libexecutorch.so` (note the rename). +- `libxnnpack_executorch_backend.so` → copy to the same directory under its own name. +- `libvulkan_executorch_backend.so` → copy to the same directory under its own name. + +Strip all three with `$ANDROID_NDK/toolchains/llvm/prebuilt/*/bin/llvm-strip` before committing. The headers under `third-party/include/` must match the fork commit that produced the binary — a mismatch shows up as runtime `dlopen` / symbol errors. + +### Packaging for a release + +For each `` tarball: + +```bash +tar -czf .tar.gz -C . +sha256sum .tar.gz > .tar.gz.sha256 # or shasum -a 256 +``` + +Staging-dir layout must mirror the destination (`download-libs.js` extracts with `tar -xzf` into `third-party/android/libs/` or `third-party/ios/` without any path stripping). So `core-android-arm64-v8a.tar.gz` contains a top-level `executorch/arm64-v8a/libexecutorch.so`, `cpuinfo/arm64-v8a/libcpuinfo.a`, etc. + +Upload every `.tar.gz` **and** its `.tar.gz.sha256` as release assets under the `v` tag on GitHub. Publishing the release (out of draft) makes them fetchable anonymously; until then, consumers need `GITHUB_TOKEN` with `repo:read`. + +### Iterating locally + +Drop built artifacts (plus `.sha256` files) into `packages/react-native-executorch/dist-artifacts/`, then run a static server and point the script at it: + +```bash +cd packages/react-native-executorch/dist-artifacts +python3 -m http.server 8080 & +RNET_BASE_URL=http://localhost:8080 yarn install +``` + +This skips GitHub entirely and re-extracts from the local tarballs — the same checksum verification still runs, so stale caches still get rejected. diff --git a/packages/react-native-executorch/android/CMakeLists.txt b/packages/react-native-executorch/android/CMakeLists.txt index e7fae6e632..d2e61cb958 100644 --- a/packages/react-native-executorch/android/CMakeLists.txt +++ b/packages/react-native-executorch/android/CMakeLists.txt @@ -24,6 +24,12 @@ set(LIBS_DIR "${CMAKE_SOURCE_DIR}/../third-party/android/libs") set(TOKENIZERS_DIR "${CMAKE_SOURCE_DIR}/../third-party/include/executorch/extension/llm/tokenizers/include") set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/../third-party/include") +# Optional feature flags — driven by user config in package.json, passed via gradle cmake arguments +option(RNE_ENABLE_OPENCV "Enable OpenCV-dependent computer vision features" ON) +option(RNE_ENABLE_PHONEMIZER "Enable Phonemizer-dependent TTS features" ON) +option(RNE_ENABLE_XNNPACK "Load the XNNPACK backend shared library" ON) +option(RNE_ENABLE_VULKAN "Load the Vulkan backend shared library" ON) + # Treat third-party headers as system headers to suppress deprecation warnings include_directories(SYSTEM "${INCLUDE_DIR}") include_directories(SYSTEM "${INCLUDE_DIR}/cpuinfo") diff --git a/packages/react-native-executorch/android/build.gradle b/packages/react-native-executorch/android/build.gradle index 5b1cfd2973..3dbae2efdb 100644 --- a/packages/react-native-executorch/android/build.gradle +++ b/packages/react-native-executorch/android/build.gradle @@ -1,5 +1,22 @@ import org.apache.tools.ant.taskdefs.condition.Os +// Read the generated build config written by the postinstall script. +// Falls back to enabling everything if the file doesn't exist (e.g. during CI +// when libs are pre-cached and the postinstall script skipped writing config). +def getRneBuildConfig() { + def configFile = new File("${project.projectDir}/../rne-build-config.json") + if (configFile.exists()) { + try { + return new groovy.json.JsonSlurper().parse(configFile) + } catch (e) { + logger.warn("[RnExecutorch] Failed to parse rne-build-config.json: ${e.message}. Defaulting to all features enabled.") + } + } + return [enableOpencv: true, enablePhonemizer: true, enableXnnpack: true, enableCoreml: true, enableVulkan: true] +} + +def rneBuildConfig = getRneBuildConfig() + buildscript { ext { agp_version = '8.4.2' @@ -122,7 +139,11 @@ android { "-DREACT_NATIVE_DIR=${toPlatformFileString(reactNativeRootDir.path)}", "-DBUILD_DIR=${project.buildDir}", "-DANDROID_TOOLCHAIN=clang", - "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + "-DRNE_ENABLE_OPENCV=${rneBuildConfig.enableOpencv ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_PHONEMIZER=${rneBuildConfig.enablePhonemizer ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_XNNPACK=${rneBuildConfig.enableXnnpack ? 'ON' : 'OFF'}", + "-DRNE_ENABLE_VULKAN=${rneBuildConfig.enableVulkan ? 'ON' : 'OFF'}" } } } diff --git a/packages/react-native-executorch/android/libs/classes.jar b/packages/react-native-executorch/android/libs/classes.jar index be5ec2ee7f..6a50b05849 100644 Binary files a/packages/react-native-executorch/android/libs/classes.jar and b/packages/react-native-executorch/android/libs/classes.jar differ diff --git a/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt b/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt index d7bd1fa870..b638c2f1a2 100644 --- a/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt +++ b/packages/react-native-executorch/android/src/main/cpp/CMakeLists.txt @@ -1,12 +1,74 @@ cmake_minimum_required(VERSION 3.13) file(GLOB_RECURSE ANDROID_CPP_SOURCES CONFIGURE_DEPENDS "${ANDROID_CPP_DIR}/*.cpp") -file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.cpp") -file(GLOB_RECURSE COMMON_C_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.c") + +# --- Source separation --- +# Glob all common sources, then separate opencv-dependent and phonemizer-dependent +# files so they can be conditionally included based on feature flags. + +file(GLOB_RECURSE ALL_COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.cpp") +file(GLOB_RECURSE ALL_COMMON_C_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/*.c") + +# Exclude test sources unconditionally file(GLOB_RECURSE TEST_CPP_SOURCES "${COMMON_CPP_DIR}/rnexecutorch/tests/*.cpp") -list(REMOVE_ITEM COMMON_CPP_SOURCES ${TEST_CPP_SOURCES}) +list(REMOVE_ITEM ALL_COMMON_CPP_SOURCES ${TEST_CPP_SOURCES}) + +# OpenCV-dependent sources: CV models + frame utilities + image processing +file(GLOB_RECURSE OPENCV_CPP_SOURCES CONFIGURE_DEPENDS + "${COMMON_CPP_DIR}/rnexecutorch/models/classification/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/object_detection/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/semantic_segmentation/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/instance_segmentation/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/style_transfer/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/ocr/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/vertical_ocr/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/embeddings/image/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/text_to_image/*.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/models/VisionModel.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/data_processing/ImageProcessing.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameExtractor.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameProcessor.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/FrameTransform.cpp" + "${COMMON_CPP_DIR}/rnexecutorch/utils/computer_vision/*.cpp" + "${COMMON_CPP_DIR}/runner/encoders/vision_encoder.cpp" + "${COMMON_CPP_DIR}/runner/multimodal_prefiller.cpp" + "${COMMON_CPP_DIR}/runner/multimodal_runner.cpp" +) -add_library(react-native-executorch SHARED ${ANDROID_CPP_SOURCES} ${COMMON_CPP_SOURCES} ${COMMON_C_SOURCES}) +# Phonemizer-dependent sources: Kokoro TTS (only user of phonemis) +file(GLOB_RECURSE PHONEMIZER_CPP_SOURCES CONFIGURE_DEPENDS + "${COMMON_CPP_DIR}/rnexecutorch/models/text_to_speech/*.cpp" +) + +# Core = everything minus optional sources +set(CORE_COMMON_CPP_SOURCES ${ALL_COMMON_CPP_SOURCES}) +list(REMOVE_ITEM CORE_COMMON_CPP_SOURCES ${OPENCV_CPP_SOURCES} ${PHONEMIZER_CPP_SOURCES}) + +# Build final source list +set(ENABLED_COMMON_SOURCES ${CORE_COMMON_CPP_SOURCES}) + +if(RNE_ENABLE_OPENCV) + list(APPEND ENABLED_COMMON_SOURCES ${OPENCV_CPP_SOURCES}) +endif() + +if(RNE_ENABLE_PHONEMIZER) + list(APPEND ENABLED_COMMON_SOURCES ${PHONEMIZER_CPP_SOURCES}) +endif() + +add_library(react-native-executorch SHARED + ${ANDROID_CPP_SOURCES} + ${ENABLED_COMMON_SOURCES} + ${ALL_COMMON_C_SOURCES} +) + +# Propagate feature flags as preprocessor defines so C++ code can guard includes +if(RNE_ENABLE_OPENCV) + target_compile_definitions(react-native-executorch PRIVATE RNE_ENABLE_OPENCV) +endif() + +if(RNE_ENABLE_PHONEMIZER) + target_compile_definitions(react-native-executorch PRIVATE RNE_ENABLE_PHONEMIZER) +endif() find_package(ReactAndroid REQUIRED CONFIG) find_package(fbjni REQUIRED CONFIG) @@ -34,63 +96,73 @@ set(RN_VERSION_LINK_LIBRARIES ReactAndroid::reactnative ) -# Dependencies: - -# ------- Executorch ------- +# ------- Executorch (always required) ------- add_library(executorch SHARED IMPORTED) set_target_properties(executorch PROPERTIES IMPORTED_LOCATION "${LIBS_DIR}/executorch/${ANDROID_ABI}/libexecutorch.so") +# Backends ship as separate .so files (libxnnpack_executorch_backend.so, +# libvulkan_executorch_backend.so). When enabled, CMake imports each .so and +# links libreact-native-executorch.so against it; Gradle packages the .so into +# the APK and the dynamic linker loads it whenever libreact-native-executorch.so +# loads. Each .so's load-time constructor registers its backend with +# libexecutorch.so's registry. if(ANDROID_ABI STREQUAL "arm64-v8a") target_compile_definitions(react-native-executorch PRIVATE ARCH_ARM64) +endif() - # ------- pthreadpool ------- - add_library(pthreadpool SHARED IMPORTED) - - set_target_properties(pthreadpool PROPERTIES - IMPORTED_LOCATION "${LIBS_DIR}/pthreadpool/${ANDROID_ABI}/libpthreadpool.so") +# pthreadpool and cpuinfo are statically linked into libexecutorch.so, +# no separate shared libraries needed. - # ------- cpuinfo ------- - add_library(cpuinfo SHARED IMPORTED) +# ------- XNNPACK backend (optional) ------- - set_target_properties(cpuinfo PROPERTIES - IMPORTED_LOCATION "${LIBS_DIR}/cpuinfo/${ANDROID_ABI}/libcpuinfo.so") - set(EXECUTORCH_LIBS - "pthreadpool" - "cpuinfo" - ) +if(RNE_ENABLE_XNNPACK) + add_library(xnnpack_executorch_backend SHARED IMPORTED) + set_target_properties(xnnpack_executorch_backend PROPERTIES + IMPORTED_LOCATION + "${LIBS_DIR}/executorch/${ANDROID_ABI}/libxnnpack_executorch_backend.so") endif() -# ------- OpenCV ------- +# ------- Vulkan backend (optional) ------- -set(OPENCV_LIBS - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_core.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_features2d.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_highgui.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_imgproc.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_photo.a" - "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_video.a" -) +if(RNE_ENABLE_VULKAN) + add_library(vulkan_executorch_backend SHARED IMPORTED) + set_target_properties(vulkan_executorch_backend PROPERTIES + IMPORTED_LOCATION + "${LIBS_DIR}/executorch/${ANDROID_ABI}/libvulkan_executorch_backend.so") +endif() -if(ANDROID_ABI STREQUAL "arm64-v8a") - set(OPENCV_THIRD_PARTY_LIBS - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_hal.a" - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_thread.a" - "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv.a" +# ------- OpenCV (optional) ------- + +if(RNE_ENABLE_OPENCV) + set(OPENCV_LINK_LIBS + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_core.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_features2d.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_highgui.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_imgproc.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_photo.a" + "${LIBS_DIR}/opencv/${ANDROID_ABI}/libopencv_video.a" ) -elseif(ANDROID_ABI STREQUAL "x86_64") - set(OPENCV_THIRD_PARTY_LIBS "") -endif() + if(ANDROID_ABI STREQUAL "arm64-v8a") + list(APPEND OPENCV_LINK_LIBS + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_hal.a" + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv_thread.a" + "${LIBS_DIR}/opencv-third-party/${ANDROID_ABI}/libkleidicv.a" + ) + endif() +endif() -# ------- phonemis ------- +# ------- Phonemizer (optional) ------- -set(PHONEMIS_LIBS - "${LIBS_DIR}/phonemis/${ANDROID_ABI}/libphonemis.a" -) +if(RNE_ENABLE_PHONEMIZER) + set(PHONEMIZER_LINK_LIBS + "${LIBS_DIR}/phonemis/${ANDROID_ABI}/libphonemis.a" + ) +endif() # -------------- @@ -100,10 +172,19 @@ target_link_libraries( react-native-executorch ${LINK_LIBRARIES} ${RN_VERSION_LINK_LIBRARIES} - ${OPENCV_LIBS} - ${OPENCV_THIRD_PARTY_LIBS} - ${PHONEMIS_LIBS} + ${OPENCV_LINK_LIBS} + ${PHONEMIZER_LINK_LIBS} executorch - ${EXECUTORCH_LIBS} z ) + +# Linking against backend SOs (when enabled) makes Gradle bundle them into the +# APK and instructs the dynamic linker to load them whenever +# libreact-native-executorch.so is loaded. Each .so's load-time constructor +# registers its backend with libexecutorch.so's backend registry. +if(RNE_ENABLE_XNNPACK) + target_link_libraries(react-native-executorch xnnpack_executorch_backend) +endif() +if(RNE_ENABLE_VULKAN) + target_link_libraries(react-native-executorch vulkan_executorch_backend) +endif() diff --git a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt index acc43c0a9e..4dbb35676f 100644 --- a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt +++ b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstaller.kt @@ -49,6 +49,10 @@ class ETInstaller( init { try { + // Each backend (XNNPACK, Vulkan) ships as its own .so. When an extra is + // enabled, libreact-native-executorch.so links against the matching .so, + // so the dynamic linker loads it automatically here and its load-time + // constructor registers the backend with libexecutorch.so's registry. System.loadLibrary("executorch") System.loadLibrary("react-native-executorch") val jsCallInvokerHolder = reactContext.jsCallInvokerHolder as CallInvokerHolderImpl diff --git a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp index 53ee65a904..523b0bd8b3 100644 --- a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp @@ -2,24 +2,30 @@ #include #include +#include +#include +#include +#include +#include +#include +#include + +#ifdef RNE_ENABLE_OPENCV #include #include -#include #include -#include #include #include #include -#include #include -#include #include #include -#include #include -#include -#include -#include +#endif + +#ifdef RNE_ENABLE_PHONEMIZER +#include +#endif #if defined(__ANDROID__) && defined(__aarch64__) #include @@ -42,6 +48,7 @@ void RnExecutorchInstaller::injectJSIBindings( jsiRuntime->global().setProperty(*jsiRuntime, "__rne_isEmulator", jsi::Value(isEmulator)); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadStyleTransfer", RnExecutorchInstaller::loadModel( @@ -79,6 +86,7 @@ void RnExecutorchInstaller::injectJSIBindings( *jsiRuntime, "loadPoseEstimation", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadPoseEstimation")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadExecutorchModule", @@ -90,10 +98,12 @@ void RnExecutorchInstaller::injectJSIBindings( RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadTokenizerModule")); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadImageEmbeddings", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadImageEmbeddings")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadTextEmbeddings", @@ -110,6 +120,7 @@ void RnExecutorchInstaller::injectJSIBindings( RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadPrivacyFilter")); +#ifdef RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadOCR", RnExecutorchInstaller::loadModel( @@ -119,16 +130,19 @@ void RnExecutorchInstaller::injectJSIBindings( *jsiRuntime, "loadVerticalOCR", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadVerticalOCR")); +#endif // RNE_ENABLE_OPENCV jsiRuntime->global().setProperty( *jsiRuntime, "loadSpeechToText", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadSpeechToText")); +#ifdef RNE_ENABLE_PHONEMIZER jsiRuntime->global().setProperty( *jsiRuntime, "loadTextToSpeechKokoro", RnExecutorchInstaller::loadModel( jsiRuntime, jsCallInvoker, "loadTextToSpeechKokoro")); +#endif // RNE_ENABLE_PHONEMIZER jsiRuntime->global().setProperty( *jsiRuntime, "loadVAD", diff --git a/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp b/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp index 7e0fa4b26e..b46eb7cadc 100644 --- a/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/models/llm/LLM.cpp @@ -6,9 +6,11 @@ #include #include #include +#include +#ifdef RNE_ENABLE_OPENCV #include #include -#include +#endif namespace rnexecutorch::models::llm { namespace llm = ::executorch::extension::llm; @@ -22,10 +24,8 @@ LLM::LLM(const std::string &modelSource, const std::string &tokenizerSource, std::shared_ptr callInvoker) : BaseModel(modelSource, callInvoker, Module::LoadMode::Mmap) { - if (capabilities.empty()) { - runner_ = - std::make_unique(std::move(module_), tokenizerSource); - } else { +#ifdef RNE_ENABLE_OPENCV + if (!capabilities.empty()) { std::map> encoders; for (const auto &cap : capabilities) { if (cap == "vision") { @@ -35,7 +35,13 @@ LLM::LLM(const std::string &modelSource, const std::string &tokenizerSource, } runner_ = std::make_unique( std::move(module_), tokenizerSource, std::move(encoders)); + } else { +#endif + runner_ = + std::make_unique(std::move(module_), tokenizerSource); +#ifdef RNE_ENABLE_OPENCV } +#endif auto loadResult = runner_->load(); if (loadResult != Error::Ok) { diff --git a/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h b/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h index 50025eeeb7..ad955b29f4 100644 --- a/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h +++ b/packages/react-native-executorch/common/rnexecutorch/threads/GlobalThreadPool.h @@ -4,7 +4,9 @@ #include #include #include +#ifdef RNE_ENABLE_OPENCV #include +#endif #include #include #include @@ -41,7 +43,9 @@ class GlobalThreadPool { config); // Disable OpenCV's internal threading to prevent it from overriding our // thread pool configuration, which would cause degraded performance +#ifdef RNE_ENABLE_OPENCV cv::setNumThreads(0); +#endif }); } diff --git a/packages/react-native-executorch/package.json b/packages/react-native-executorch/package.json index ac32b09d5f..81c804b623 100644 --- a/packages/react-native-executorch/package.json +++ b/packages/react-native-executorch/package.json @@ -18,8 +18,10 @@ "*.podspec", "third-party/include", "third-party", + "!third-party/android/libs", "!third-party/ios/ExecutorchLib", - "!third-party/ios/libs/executorch", + "!third-party/ios/ExecutorchLib.xcframework", + "!third-party/ios/libs", "!ios/*.xcodeproj", "!ios/build", "!android/build", @@ -39,7 +41,8 @@ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "prepack": "cp ../../README.md ./README.md", - "postpack": "rm ./README.md" + "postpack": "rm ./README.md", + "postinstall": "node scripts/download-libs.js" }, "keywords": [ "text-to-speech", diff --git a/packages/react-native-executorch/react-native-executorch.podspec b/packages/react-native-executorch/react-native-executorch.podspec index 902210d01a..97ff93962a 100644 --- a/packages/react-native-executorch/react-native-executorch.podspec +++ b/packages/react-native-executorch/react-native-executorch.podspec @@ -2,6 +2,23 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) +# Read the build config written by the postinstall script. +# Falls back to all features enabled if the file doesn't exist. +rne_build_config_path = File.join(__dir__, "rne-build-config.json") +if File.exist?(rne_build_config_path) + require "json" + rne_build_config = JSON.parse(File.read(rne_build_config_path)) + enable_opencv = rne_build_config["enableOpencv"] != false + enable_phonemizer = rne_build_config["enablePhonemizer"] != false + enable_xnnpack = rne_build_config["enableXnnpack"] != false + enable_coreml = rne_build_config["enableCoreml"] != false +else + enable_opencv = true + enable_phonemizer = true + enable_xnnpack = true + enable_coreml = true +end + Pod::Spec.new do |s| s.name = "react-native-executorch" s.version = package["version"] @@ -13,32 +30,100 @@ Pod::Spec.new do |s| s.platforms = { :ios => '17.0' } s.source = { :git => "https://github.com/software-mansion/react-native-executorch.git", :tag => "#{s.version}" } - pthreadpool_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/pthreadpool', __dir__) - cpuinfo_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/cpuinfo', __dir__) - phonemis_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/phonemis', __dir__) + cpuinfo_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/cpuinfo', __dir__) + phonemis_binaries_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/libs/phonemis', __dir__) + + # --- Core sources (always compiled) --- + opencv_source_dirs = [ + "common/rnexecutorch/models/classification", + "common/rnexecutorch/models/object_detection", + "common/rnexecutorch/models/semantic_segmentation", + "common/rnexecutorch/models/instance_segmentation", + "common/rnexecutorch/models/style_transfer", + "common/rnexecutorch/models/ocr", + "common/rnexecutorch/models/vertical_ocr", + "common/rnexecutorch/models/embeddings/image", + "common/rnexecutorch/models/text_to_image", + "common/rnexecutorch/utils/computer_vision", + ] + opencv_source_files = opencv_source_dirs.map { |d| "#{d}/**/*.{cpp,c,h,hpp}" } + opencv_source_files += [ + "common/rnexecutorch/models/VisionModel.{cpp,h}", + "common/rnexecutorch/data_processing/ImageProcessing.{cpp,h}", + "common/rnexecutorch/utils/FrameExtractor.{cpp,h}", + "common/rnexecutorch/utils/FrameProcessor.{cpp,h}", + "common/rnexecutorch/utils/FrameTransform.{cpp,h}", + ] + + phonemizer_source_files = [ + "common/rnexecutorch/models/text_to_speech/**/*.{cpp,c,h,hpp}", + ] + + s.source_files = [ + "ios/**/*.{m,mm,h}", + "common/**/*.{cpp,c,h,hpp}", + ] + + # Exclude file with tests to not introduce gtest dependency. + # Do not include the headers from common/rnexecutorch/jsi/ as source files. + # Xcode/Cocoapods leaks them to other pods that an app also depends on, so if + # another pod includes a header with the same name without a path by + # #include "Header.h" we get a conflict. Here, headers in jsi/ collide with + # react-native-skia. The headers are preserved by preserve_paths and + # then made available by HEADER_SEARCH_PATHS. + exclude_files = [ + "common/rnexecutorch/tests/**/*", + "common/rnexecutorch/jsi/*.{h,hpp}", + ] + exclude_files += opencv_source_files unless enable_opencv + exclude_files += phonemizer_source_files unless enable_phonemizer + s.exclude_files = exclude_files + + # --- Preprocessor flags --- + extra_compiler_flags = [] + extra_compiler_flags << "-DRNE_ENABLE_OPENCV" if enable_opencv + extra_compiler_flags << "-DRNE_ENABLE_PHONEMIZER" if enable_phonemizer + extra_compiler_flags << "-DRNE_ENABLE_XNNPACK" if enable_xnnpack + extra_compiler_flags << "-DRNE_ENABLE_COREML" if enable_coreml + + # --- Link flags --- + physical_ldflags = [ + '$(inherited)', + "\"#{pthreadpool_binaries_path}/physical-arm64-release/libpthreadpool.a\"", + "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", + ] + simulator_ldflags = [ + '$(inherited)', + "\"#{pthreadpool_binaries_path}/simulator-arm64-debug/libpthreadpool.a\"", + "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", + ] + + if enable_phonemizer + physical_ldflags << "\"#{phonemis_binaries_path}/physical-arm64-release/libphonemis.a\"" + simulator_ldflags << "\"#{phonemis_binaries_path}/simulator-arm64-debug/libphonemis.a\"" + end + + xnnpack_xcframework_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/XnnpackBackend.xcframework', __dir__) + coreml_xcframework_path = File.expand_path('$(PODS_TARGET_SRCROOT)/third-party/ios/CoreMLBackend.xcframework', __dir__) + + if enable_xnnpack + physical_ldflags << "-force_load \"#{xnnpack_xcframework_path}/ios-arm64/libXnnpackBackend.a\"" + simulator_ldflags << "-force_load \"#{xnnpack_xcframework_path}/ios-arm64-simulator/libXnnpackBackend.a\"" + end + + if enable_coreml + physical_ldflags << "-force_load \"#{coreml_xcframework_path}/ios-arm64/libCoreMLBackend.a\"" + simulator_ldflags << "-force_load \"#{coreml_xcframework_path}/ios-arm64-simulator/libCoreMLBackend.a\"" + end s.user_target_xcconfig = { "HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/third-party/include" '+ '"$(PODS_TARGET_SRCROOT)/third-party/include/cpuinfo" '+ '"$(PODS_TARGET_SRCROOT)/third-party/include/pthreadpool"', - - "OTHER_LDFLAGS[sdk=iphoneos*]" => [ - '$(inherited)', - "\"#{pthreadpool_binaries_path}/physical-arm64-release/libpthreadpool.a\"", - "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", - "\"#{phonemis_binaries_path}/physical-arm64-release/libphonemis.a\"", - - ].join(' '), - - "OTHER_LDFLAGS[sdk=iphonesimulator*]" => [ - '$(inherited)', - "\"#{pthreadpool_binaries_path}/simulator-arm64-debug/libpthreadpool.a\"", - "\"#{cpuinfo_binaries_path}/libcpuinfo.a\"", - "\"#{phonemis_binaries_path}/simulator-arm64-debug/libphonemis.a\"", - ].join(' '), - + "OTHER_LDFLAGS[sdk=iphoneos*]" => physical_ldflags.join(' '), + "OTHER_LDFLAGS[sdk=iphonesimulator*]" => simulator_ldflags.join(' '), 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64', } @@ -52,32 +137,28 @@ Pod::Spec.new do |s| '"$(PODS_TARGET_SRCROOT)/third-party/include/pthreadpool" '+ '"$(PODS_TARGET_SRCROOT)/common" ', "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", + "OTHER_CPLUSPLUSFLAGS" => extra_compiler_flags.join(' '), 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64', } - s.source_files = [ - "ios/**/*.{m,mm,h}", - "common/**/*.{cpp,c,h,hpp}", - ] + libs = ["z"] + libs << "sqlite3" if enable_coreml + s.libraries = libs + + system_frameworks = ["Accelerate"] + system_frameworks << "CoreML" if enable_coreml + s.frameworks = system_frameworks + + # Backend xcframeworks are linked via force_load in OTHER_LDFLAGS (needed to + # preserve __attribute__((constructor)) registrations). Only ExecutorchLib goes + # in vendored_frameworks to avoid duplicate symbol errors. + s.ios.vendored_frameworks = ["third-party/ios/ExecutorchLib.xcframework"] - s.libraries = "z" - s.ios.vendored_frameworks = "third-party/ios/ExecutorchLib.xcframework" - # Exclude file with tests to not introduce gtest dependency. - # Do not include the headers from common/rnexecutorch/jsi/ as source files. - # Xcode/Cocoapods leaks them to other pods that an app also depends on, so if - # another pod includes a header with the same name without a path by - # #include "Header.h" we get a conflict. Here, headers in jsi/ collide with - # react-native-skia. The headers are preserved by preserve_paths and - # then made available by HEADER_SEARCH_PATHS. - s.exclude_files = [ - "common/rnexecutorch/tests/**/*", - "common/rnexecutorch/jsi/*.{h,hpp}" - ] s.header_mappings_dir = "common/rnexecutorch" s.header_dir = "rnexecutorch" s.preserve_paths = "common/rnexecutorch/jsi/*.{h,hpp}" - s.dependency "opencv-rne", "~> 4.11.0" + s.dependency "opencv-rne", "~> 4.11.0" if enable_opencv install_modules_dependencies(s) end diff --git a/packages/react-native-executorch/scripts/download-libs.js b/packages/react-native-executorch/scripts/download-libs.js new file mode 100644 index 0000000000..9dbe0da37d --- /dev/null +++ b/packages/react-native-executorch/scripts/download-libs.js @@ -0,0 +1,336 @@ +/** + * On-demand native library downloader + * + * Runs at postinstall time. Downloads prebuilt native artifacts from GitHub Releases + * and extracts them into third-party/ so the existing CMakeLists.txt / podspec + * can find them at build time without any other changes. + * + * Artifact layout on GitHub Releases (per version tag, e.g. v0.9.0): + * + * core-android-arm64-v8a.tar.gz -- executorch, pthreadpool, cpuinfo for arm64 + * core-android-x86_64.tar.gz -- executorch for x86_64 + * core-ios.tar.gz -- ExecutorchLib.xcframework (without xnnpack/coreml) + * opencv-android-arm64-v8a.tar.gz -- OpenCV for arm64 + * opencv-android-x86_64.tar.gz -- OpenCV for x86_64 + * opencv-ios.tar.gz -- OpenCV xcframework + * phonemizer-android-arm64-v8a.tar.gz + * phonemizer-android-x86_64.tar.gz + * phonemizer-ios.tar.gz + * xnnpack-android-arm64-v8a.tar.gz -- libxnnpack_executorch_backend.so (Android) + * xnnpack-android-x86_64.tar.gz + * xnnpack-ios.tar.gz -- XnnpackBackend.xcframework (iOS) + * vulkan-android-arm64-v8a.tar.gz -- libvulkan_executorch_backend.so (Android only) + * vulkan-android-x86_64.tar.gz + * coreml-ios.tar.gz -- CoreMLBackend.xcframework (iOS only) + * + * Each tarball extracts into third-party/android/libs/ or third-party/ios/ + * preserving the existing directory structure so CMakeLists/podspec need no changes. + * + * User configuration (in the app's package.json): + * "react-native-executorch": { + * "extras": ["opencv", "phonemizer", "xnnpack", "coreml", "vulkan"] // default: all enabled + * } + * + * Platform applicability of each extra: + * opencv Android + iOS + * phonemizer Android + iOS + * xnnpack Android (libxnnpack_executorch_backend.so) + iOS (XnnpackBackend.xcframework) + * coreml iOS only — toggles CoreMLBackend.xcframework. + * vulkan Android only — toggles libvulkan_executorch_backend.so. + * + * Environment variables: + * RNET_SKIP_DOWNLOAD=1 -- skip download entirely (for CI with pre-cached libs) + * RNET_LIBS_CACHE_DIR=/path -- use custom cache dir instead of default + * RNET_TARGET=android-arm64 -- force specific target (skip auto-detection) + * RNET_BASE_URL=http://localhost:8080 -- override base URL (useful for local testing: + * cd dist-artifacts && python3 -m http.server 8080) + * GITHUB_TOKEN=ghp_xxx -- GitHub token for accessing draft releases + */ + +'use strict'; + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// ---- Config ---------------------------------------------------------------- + +const PACKAGE_VERSION = require('../package.json').version; +const GITHUB_REPO = 'software-mansion/react-native-executorch'; +const BASE_URL = + process.env.RNET_BASE_URL || + `https://github.com/${GITHUB_REPO}/releases/download/v${PACKAGE_VERSION}`; + +const PACKAGE_ROOT = path.resolve(__dirname, '..'); +const THIRD_PARTY_DIR = path.join(PACKAGE_ROOT, 'third-party'); + +const DEFAULT_CACHE_DIR = path.join( + require('os').homedir(), + '.cache', + 'react-native-executorch', + PACKAGE_VERSION +); +const CACHE_DIR = process.env.RNET_LIBS_CACHE_DIR || DEFAULT_CACHE_DIR; + +// ---- User config ----------------------------------------------------------- + +function readUserExtras() { + // npm/yarn set INIT_CWD to the directory where install was invoked (project root) + const projectRoot = + process.env.INIT_CWD || process.env.npm_config_local_prefix; + if (!projectRoot) { + console.warn( + '[react-native-executorch] Could not determine project root, enabling all extras.' + ); + return ['opencv', 'phonemizer']; + } + + const userPackageJsonPath = path.join(projectRoot, 'package.json'); + try { + const userPackageJson = JSON.parse( + fs.readFileSync(userPackageJsonPath, 'utf8') + ); + const rneConfig = userPackageJson['react-native-executorch'] || {}; + return ( + rneConfig.extras ?? [ + 'opencv', + 'phonemizer', + 'xnnpack', + 'coreml', + 'vulkan', + ] + ); + } catch { + console.warn( + '[react-native-executorch] Could not read app package.json, enabling all extras.' + ); + return ['opencv', 'phonemizer', 'xnnpack', 'coreml', 'vulkan']; + } +} + +function writeBuildConfig(extras) { + const config = { + enableOpencv: extras.includes('opencv'), + enablePhonemizer: extras.includes('phonemizer'), + enableXnnpack: extras.includes('xnnpack'), + enableCoreml: extras.includes('coreml'), + enableVulkan: extras.includes('vulkan'), + }; + fs.writeFileSync( + path.join(PACKAGE_ROOT, 'rne-build-config.json'), + JSON.stringify(config, null, 2) + ); + return config; +} + +// Warn the user when an `extras` choice is platform-asymmetric and the toggle +// will be silently ignored on one of the target platforms. Better to surface +// it at install time than to have the user wonder why an opt-out had no effect. +function warnAboutPlatformAsymmetry(extras, targets) { + const hasAndroid = targets.some((t) => t.startsWith('android')); + const hasIos = targets.includes('ios'); + if (hasAndroid && extras.includes('coreml') && !hasIos) { + console.warn( + '[react-native-executorch] coreml is enabled but the build targets only Android; CoreML is iOS-only and the flag has no effect here.' + ); + } + if (hasIos && extras.includes('vulkan') && !hasAndroid) { + console.warn( + '[react-native-executorch] vulkan is enabled but the build targets only iOS; the Vulkan backend is Android-only and the flag has no effect here.' + ); + } +} + +// ---- Target detection ------------------------------------------------------ + +function detectTargets() { + if (process.env.RNET_TARGET) { + return [process.env.RNET_TARGET]; + } + + const targets = []; + if (process.platform === 'darwin') { + targets.push('ios'); + } + targets.push('android-arm64-v8a'); + if (!process.env.RNET_NO_X86_64) { + targets.push('android-x86_64'); + } + return targets; +} + +// ---- Artifact metadata ----------------------------------------------------- + +// Core artifacts are always downloaded; optional ones only if the extra is enabled. +function getArtifacts(targets, extras) { + const artifacts = []; + + for (const target of targets) { + const destDir = target.startsWith('android') + ? path.join(THIRD_PARTY_DIR, 'android', 'libs') + : path.join(THIRD_PARTY_DIR, 'ios'); + + // Core is always needed + artifacts.push(makeArtifact(`core-${target}`, destDir)); + + // iOS OpenCV is provided via CocoaPods (opencv-rne dependency), not a tarball + if (extras.includes('opencv') && target !== 'ios') { + artifacts.push(makeArtifact(`opencv-${target}`, destDir)); + } + + if (extras.includes('phonemizer')) { + artifacts.push(makeArtifact(`phonemizer-${target}`, destDir)); + } + + if (extras.includes('xnnpack')) { + artifacts.push(makeArtifact(`xnnpack-${target}`, destDir)); + } + + // CoreML is iOS only + if (extras.includes('coreml') && target === 'ios') { + artifacts.push(makeArtifact(`coreml-${target}`, destDir)); + } + + // Vulkan is Android only + if (extras.includes('vulkan') && target.startsWith('android')) { + artifacts.push(makeArtifact(`vulkan-${target}`, destDir)); + } + } + + return artifacts; +} + +function makeArtifact(name, destDir) { + return { + name, + url: `${BASE_URL}/${name}.tar.gz`, + checksumUrl: `${BASE_URL}/${name}.tar.gz.sha256`, + destDir, + cacheFile: path.join(CACHE_DIR, `${name}.tar.gz`), + cacheChecksumFile: path.join(CACHE_DIR, `${name}.tar.gz.sha256`), + }; +} + +// ---- Helpers --------------------------------------------------------------- + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function download(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + const get = (url) => { + const client = url.startsWith('http://') ? http : https; + const headers = {}; + if (process.env.GITHUB_TOKEN && !url.startsWith('http://')) { + headers['Authorization'] = `Bearer ${process.env.GITHUB_TOKEN}`; + } + client.get(url, { headers }, (res) => { + if (res.statusCode === 301 || res.statusCode === 302) { + return get(res.headers.location); + } + if (res.statusCode !== 200) { + return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); + } + res.pipe(file); + file.on('finish', () => file.close(resolve)); + }); + }; + get(url); + file.on('error', (err) => { + fs.unlinkSync(dest); + reject(err); + }); + }); +} + +function sha256(filePath) { + const result = execSync( + `sha256sum "${filePath}" || shasum -a 256 "${filePath}"` + ); + return result.toString().split(' ')[0].trim(); +} + +function isCacheValid(artifact) { + if (!fs.existsSync(artifact.cacheFile)) return false; + if (!fs.existsSync(artifact.cacheChecksumFile)) return false; + const expectedChecksum = fs + .readFileSync(artifact.cacheChecksumFile, 'utf8') + .trim(); + const actualChecksum = sha256(artifact.cacheFile); + return expectedChecksum === actualChecksum; +} + +function extract(tarball, destDir) { + ensureDir(destDir); + execSync(`tar -xzf "${tarball}" -C "${destDir}"`); +} + +// ---- Main ------------------------------------------------------------------ + +async function main() { + if (process.env.RNET_SKIP_DOWNLOAD) { + console.log( + '[react-native-executorch] Skipping native lib download (RNET_SKIP_DOWNLOAD set)' + ); + // Still write build config so the native build knows what features are enabled + const extras = readUserExtras(); + writeBuildConfig(extras); + return; + } + + const extras = readUserExtras(); + const buildConfig = writeBuildConfig(extras); + console.log( + `[react-native-executorch] Features: opencv=${buildConfig.enableOpencv}, phonemizer=${buildConfig.enablePhonemizer}, xnnpack=${buildConfig.enableXnnpack}, coreml=${buildConfig.enableCoreml}, vulkan=${buildConfig.enableVulkan}` + ); + + const targets = detectTargets(); + warnAboutPlatformAsymmetry(extras, targets); + const artifacts = getArtifacts(targets, extras); + + ensureDir(CACHE_DIR); + + for (const artifact of artifacts) { + console.log(`[react-native-executorch] Preparing ${artifact.name}...`); + + if (isCacheValid(artifact)) { + console.log(` ✓ Cache hit, skipping download`); + } else { + console.log(` ↓ Downloading ${artifact.url}`); + await download(artifact.checksumUrl, artifact.cacheChecksumFile); + await download(artifact.url, artifact.cacheFile); + + const expectedChecksum = fs + .readFileSync(artifact.cacheChecksumFile, 'utf8') + .trim(); + const actualChecksum = sha256(artifact.cacheFile); + if (expectedChecksum !== actualChecksum) { + throw new Error( + `Checksum mismatch for ${artifact.name}: expected ${expectedChecksum}, got ${actualChecksum}` + ); + } + console.log(` ✓ Downloaded and verified`); + } + + console.log(` ↓ Extracting to ${artifact.destDir}`); + extract(artifact.cacheFile, artifact.destDir); + console.log(` ✓ Done`); + } + + console.log('[react-native-executorch] Native libs ready.'); +} + +main().catch((err) => { + console.error( + '[react-native-executorch] Failed to download native libs:', + err.message + ); + console.error( + ' You can set RNET_SKIP_DOWNLOAD=1 to skip and provide libs manually.' + ); + process.exit(1); +}); diff --git a/packages/react-native-executorch/scripts/package-release-artifacts.sh b/packages/react-native-executorch/scripts/package-release-artifacts.sh new file mode 100755 index 0000000000..dfca0ef5c4 --- /dev/null +++ b/packages/react-native-executorch/scripts/package-release-artifacts.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# package-release-artifacts.sh +# +# Packages the currently committed native libs into release artifact tarballs +# ready to be uploaded to GitHub Releases. +# +# Run from the package root (packages/react-native-executorch/): +# ./scripts/package-release-artifacts.sh +# +# Output: dist-artifacts/ +# core-android-arm64-v8a.tar.gz + .sha256 +# core-android-x86_64.tar.gz + .sha256 +# opencv-android-arm64-v8a.tar.gz + .sha256 +# opencv-android-x86_64.tar.gz + .sha256 +# phonemizer-android-arm64-v8a.tar.gz + .sha256 +# phonemizer-android-x86_64.tar.gz + .sha256 +# xnnpack-android-arm64-v8a.tar.gz + .sha256 +# xnnpack-android-x86_64.tar.gz + .sha256 +# vulkan-android-arm64-v8a.tar.gz + .sha256 +# vulkan-android-x86_64.tar.gz + .sha256 +# core-ios.tar.gz + .sha256 +# phonemizer-ios.tar.gz + .sha256 +# xnnpack-ios.tar.gz + .sha256 +# coreml-ios.tar.gz + .sha256 +# +# Note: iOS OpenCV is provided via CocoaPods (opencv-rne), not a tarball. +# +# Testing the download flow +# ------------------------- +# Option A — local HTTP server (no GitHub needed): +# cd dist-artifacts && python3 -m http.server 8080 +# RNET_BASE_URL=http://localhost:8080 INIT_CWD= node scripts/download-libs.js +# +# Option B — GitHub pre-release: +# gh release create v0.9.0-libs-test --prerelease --title "libs test" \ +# --notes "Test release, will be deleted." \ +# --repo software-mansion/react-native-executorch +# gh release upload v0.9.0-libs-test dist-artifacts/* \ +# --repo software-mansion/react-native-executorch +# RNET_BASE_URL=https://github.com/software-mansion/react-native-executorch/releases/download/v0.9.0-libs-test \ +# INIT_CWD= node scripts/download-libs.js +# # cleanup: +# gh release delete v0.9.0-libs-test --repo software-mansion/react-native-executorch --yes + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PACKAGE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +ANDROID_LIBS="$PACKAGE_ROOT/third-party/android/libs" +IOS_DIR="$PACKAGE_ROOT/third-party/ios" +OUT="$PACKAGE_ROOT/dist-artifacts" + +VERSION=$(node -p "require('$PACKAGE_ROOT/package.json').version") + +echo "Packaging release artifacts for v$VERSION" +mkdir -p "$OUT" + +# ---- Helpers ---------------------------------------------------------------- + +package() { + local name=$1 + local src_dir=$2 + local out_file="$OUT/$name.tar.gz" + + echo " → $name" + + if [ ! -d "$src_dir" ]; then + echo " ✗ Source directory not found: $src_dir" >&2 + exit 1 + fi + + tar -czf "$out_file" -C "$src_dir" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" +} + +# Packages a single file into a tarball, placing it at the given relative path. +# package_file +package_file() { + local name=$1 + local rel_path=$2 # directory path inside the tarball + local src_file=$3 # full path to the source file + local out_file="$OUT/$name.tar.gz" + local tmp + tmp=$(mktemp -d) + + echo " → $name" + + if [ ! -f "$src_file" ]; then + echo " ✗ Source file not found: $src_file" >&2 + rm -rf "$tmp" + exit 1 + fi + + mkdir -p "$tmp/$rel_path" + cp "$src_file" "$tmp/$rel_path/" + + tar -czf "$out_file" -C "$tmp" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" + rm -rf "$tmp" +} + +# Packages multiple source directories into a single tarball by staging them +# into a temp directory first, preserving relative paths. +package_merged() { + local name=$1 + shift + local out_file="$OUT/$name.tar.gz" + local tmp + tmp=$(mktemp -d) + + echo " → $name" + + while [[ $# -gt 0 ]]; do + local rel_path=$1 # relative path inside the tarball + local src=$2 # source directory to copy from + shift 2 + + if [ ! -d "$src" ]; then + echo " ✗ Source directory not found: $src" >&2 + rm -rf "$tmp" + exit 1 + fi + + mkdir -p "$tmp/$rel_path" + cp -r "$src/." "$tmp/$rel_path/" + done + + tar -czf "$out_file" -C "$tmp" . + shasum -a 256 "$out_file" | awk '{print $1}' > "$out_file.sha256" + echo " ✓ $(du -sh "$out_file" | cut -f1)" + rm -rf "$tmp" +} + +# ---- Android ---------------------------------------------------------------- + +echo "" +echo "Android:" + +package_merged "core-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a" \ + "pthreadpool/arm64-v8a" "$ANDROID_LIBS/pthreadpool/arm64-v8a" \ + "cpuinfo/arm64-v8a" "$ANDROID_LIBS/cpuinfo/arm64-v8a" + +package_merged "core-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64" + +package_merged "opencv-android-arm64-v8a" \ + "opencv/arm64-v8a" "$ANDROID_LIBS/opencv/arm64-v8a" \ + "opencv-third-party/arm64-v8a" "$ANDROID_LIBS/opencv-third-party/arm64-v8a" + +package_merged "opencv-android-x86_64" \ + "opencv/x86_64" "$ANDROID_LIBS/opencv/x86_64" + +package_merged "phonemizer-android-arm64-v8a" \ + "phonemis/arm64-v8a" "$ANDROID_LIBS/phonemis/arm64-v8a" + +package_merged "phonemizer-android-x86_64" \ + "phonemis/x86_64" "$ANDROID_LIBS/phonemis/x86_64" + +# XNNPACK and Vulkan each ship as standalone shared libraries (opt-in extras). +package_file "xnnpack-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a/libxnnpack_executorch_backend.so" + +package_file "xnnpack-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64/libxnnpack_executorch_backend.so" + +package_file "vulkan-android-arm64-v8a" \ + "executorch/arm64-v8a" "$ANDROID_LIBS/executorch/arm64-v8a/libvulkan_executorch_backend.so" + +package_file "vulkan-android-x86_64" \ + "executorch/x86_64" "$ANDROID_LIBS/executorch/x86_64/libvulkan_executorch_backend.so" + +# ---- iOS -------------------------------------------------------------------- +# Note: OpenCV for iOS is provided by CocoaPods (opencv-rne dependency). +# No opencv-ios tarball is needed. + +echo "" +echo "iOS:" + +package_merged "core-ios" \ + "ExecutorchLib.xcframework" "$IOS_DIR/ExecutorchLib.xcframework" \ + "libs/executorch" "$IOS_DIR/libs/executorch" \ + "libs/pthreadpool" "$IOS_DIR/libs/pthreadpool" \ + "libs/cpuinfo" "$IOS_DIR/libs/cpuinfo" + +package_merged "phonemizer-ios" \ + "libs/phonemis" "$IOS_DIR/libs/phonemis" + +package_merged "xnnpack-ios" \ + "XnnpackBackend.xcframework" "$IOS_DIR/XnnpackBackend.xcframework" + +package_merged "coreml-ios" \ + "CoreMLBackend.xcframework" "$IOS_DIR/CoreMLBackend.xcframework" + +# ---- Summary ---------------------------------------------------------------- + +echo "" +echo "Done. Artifacts written to dist-artifacts/:" +ls -lh "$OUT" +echo "" +echo "Upload these files to the GitHub Release for v$VERSION:" +echo " https://github.com/software-mansion/react-native-executorch/releases/tag/v$VERSION" diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 96d167a7d2..8dcdb286c0 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -121,27 +121,22 @@ declare global { } // eslint-disable no-var -if ( - global.loadStyleTransfer == null || - global.loadSemanticSegmentation == null || - global.loadInstanceSegmentation == null || - global.loadTextToImage == null || - global.loadExecutorchModule == null || - global.loadClassification == null || - global.loadObjectDetection == null || - global.loadPoseEstimation == null || - global.loadTokenizerModule == null || - global.loadTextEmbeddings == null || - global.loadImageEmbeddings == null || - global.loadVAD == null || - global.loadLLM == null || - global.loadPrivacyFilter == null || - global.loadSpeechToText == null || - global.loadTextToSpeechKokoro == null || - global.loadOCR == null || - global.loadVerticalOCR == null || - global.__rne_isEmulator == null -) { +// Core globals are always installed regardless of which extras are enabled. +// Optional globals (opencv/phonemizer) may be absent if the library was built +// without those features — calling them at that point throws a runtime error +// from the native side with a clear message. +const CORE_GLOBALS = [ + 'loadExecutorchModule', + 'loadTokenizerModule', + 'loadLLM', + 'loadSpeechToText', + 'loadTextEmbeddings', + 'loadPrivacyFilter', + 'loadVAD', + '__rne_isEmulator', +] as const; + +if (CORE_GLOBALS.some((name) => global[name] == null)) { if (!ETInstallerNativeModule) { throw new Error( `Failed to install react-native-executorch: The native module could not be found.` diff --git a/packages/react-native-executorch/third-party/android/libs/cpuinfo/arm64-v8a/libcpuinfo.so b/packages/react-native-executorch/third-party/android/libs/cpuinfo/arm64-v8a/libcpuinfo.so deleted file mode 100755 index c97092dbce..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/cpuinfo/arm64-v8a/libcpuinfo.so and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so b/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so deleted file mode 100644 index 8c65aa5d85..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/executorch/arm64-v8a/libexecutorch.so and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so b/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so deleted file mode 100644 index a56a5d20ac..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/executorch/x86_64/libexecutorch.so and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv.a b/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv.a deleted file mode 100644 index aafb96d52a..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_hal.a b/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_hal.a deleted file mode 100644 index 2586c51da7..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_hal.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_thread.a b/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_thread.a deleted file mode 100644 index 21b46fd532..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv-third-party/arm64-v8a/libkleidicv_thread.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_core.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_core.a deleted file mode 100644 index dfa6cac97b..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_core.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_features2d.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_features2d.a deleted file mode 100644 index 2d9285ff48..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_features2d.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_highgui.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_highgui.a deleted file mode 100644 index f5935c0a49..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_highgui.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_imgproc.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_imgproc.a deleted file mode 100644 index 1230670d37..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_imgproc.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_photo.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_photo.a deleted file mode 100644 index 6fd7b5725c..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_photo.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_video.a b/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_video.a deleted file mode 100644 index 94afa14b0c..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/arm64-v8a/libopencv_video.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_core.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_core.a deleted file mode 100644 index 11dbb38c99..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_core.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_features2d.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_features2d.a deleted file mode 100644 index 7dda0f00f4..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_features2d.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_highgui.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_highgui.a deleted file mode 100644 index 074a6fbd7f..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_highgui.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_imgproc.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_imgproc.a deleted file mode 100644 index af4e846586..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_imgproc.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_photo.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_photo.a deleted file mode 100644 index d1cd183451..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_photo.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_video.a b/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_video.a deleted file mode 100644 index 6d76f3b5b5..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/opencv/x86_64/libopencv_video.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/phonemis/arm64-v8a/libphonemis.a b/packages/react-native-executorch/third-party/android/libs/phonemis/arm64-v8a/libphonemis.a deleted file mode 100644 index 5a38707580..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/phonemis/arm64-v8a/libphonemis.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/phonemis/x86_64/libphonemis.a b/packages/react-native-executorch/third-party/android/libs/phonemis/x86_64/libphonemis.a deleted file mode 100644 index 2306d4647a..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/phonemis/x86_64/libphonemis.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/android/libs/pthreadpool/arm64-v8a/libpthreadpool.so b/packages/react-native-executorch/third-party/android/libs/pthreadpool/arm64-v8a/libpthreadpool.so deleted file mode 100755 index 5b144aaa8c..0000000000 Binary files a/packages/react-native-executorch/third-party/android/libs/pthreadpool/arm64-v8a/libpthreadpool.so and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist deleted file mode 100644 index 6a6c556899..0000000000 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/Info.plist +++ /dev/null @@ -1,43 +0,0 @@ - - - - - AvailableLibraries - - - BinaryPath - ExecutorchLib.framework/ExecutorchLib - LibraryIdentifier - ios-arm64 - LibraryPath - ExecutorchLib.framework - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - BinaryPath - ExecutorchLib.framework/ExecutorchLib - LibraryIdentifier - ios-arm64-simulator - LibraryPath - ExecutorchLib.framework - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - - diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib deleted file mode 100755 index f74ed53c6a..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/ExecutorchLib and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist deleted file mode 100644 index bd0373672c..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64-simulator/ExecutorchLib.framework/Info.plist and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib deleted file mode 100755 index 61193b77ef..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/ExecutorchLib and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist b/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist deleted file mode 100644 index 2372838d49..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/ExecutorchLib.xcframework/ios-arm64/ExecutorchLib.framework/Info.plist and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj b/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj index a4d139dcaa..a7c0ce85bc 100644 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj +++ b/packages/react-native-executorch/third-party/ios/ExecutorchLib/ExecutorchLib.xcodeproj/project.pbxproj @@ -334,13 +334,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_ios.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_ios.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_ios.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_ios.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_ios.a", @@ -357,13 +354,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_simulator.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_simulator.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_simulator.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_simulator.a", @@ -426,13 +420,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_ios.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_ios.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_ios.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_ios.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_ios.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_ios.a", @@ -449,13 +440,10 @@ "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_llm_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_torchao_simulator.a", + "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkleidiai_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libkernels_quantized_simulator.a", "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a", - "-force_load", - "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_coreml_simulator.a", - "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libbackend_mps_simulator.a", "-force_load", "$(PROJECT_DIR)/../../../third-party/ios/libs/executorch/libexecutorch_simulator.a", diff --git a/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh b/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh index 0a410e237d..bc4191f5f1 100755 --- a/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh +++ b/packages/react-native-executorch/third-party/ios/ExecutorchLib/build.sh @@ -1,21 +1,30 @@ #!/bin/bash -# Builds ExecutorchLib.xcframework for iOS and iOS Simulator +# Builds ExecutorchLib.xcframework for iOS and iOS Simulator, plus separate +# static xcframeworks for optional backends (xnnpack, coreml). # # This script: # 1. Cleans previous builds # 2. Archives the framework for iOS device (arm64) # 3. Archives the framework for iOS Simulator (arm64) # 4. Combines both archives into a single .xcframework +# 5. Creates XnnpackBackend.xcframework from the backend .a files +# 6. Creates CoreMLBackend.xcframework from the backend .a files # -# Output: ./output/ExecutorchLib.xcframework +# Output: +# ./output/ExecutorchLib.xcframework +# ./output/XnnpackBackend.xcframework +# ./output/CoreMLBackend.xcframework # # Usage: ./build.sh +set -euo pipefail + # --- Configuration --- -PROJECT_NAME="ExecutorchLib" # Replace with your Xcode project name -SCHEME_NAME="ExecutorchLib" # Replace with your scheme name -OUTPUT_FOLDER="output" # Choose your desired output folder +PROJECT_NAME="ExecutorchLib" +SCHEME_NAME="ExecutorchLib" +OUTPUT_FOLDER="output" +LIBS_DIR="$(pwd)/../../../third-party/ios/libs/executorch" # --- Derived Variables --- BUILD_FOLDER="build" @@ -27,6 +36,7 @@ XCFRAMEWORK_PATH="$OUTPUT_FOLDER/$XCFRAMEWORK_NAME" # --- Script --- rm -rf "$BUILD_FOLDER" "$OUTPUT_FOLDER" +mkdir -p "$OUTPUT_FOLDER" xcodebuild clean -project "$PROJECT_NAME.xcodeproj" -scheme "$SCHEME_NAME" @@ -54,3 +64,28 @@ xcodebuild -create-xcframework \ -framework "$ARCHIVE_PATH_IOS.xcarchive/Products/Library/Frameworks/$FRAMEWORK_NAME" \ -framework "$ARCHIVE_PATH_SIMULATOR.xcarchive/Products/Library/Frameworks/$FRAMEWORK_NAME" \ -output "$XCFRAMEWORK_PATH" + +# --- Build XnnpackBackend.xcframework --- +# CocoaPods requires matching binary names across slices +STAGING=$(mktemp -d) +mkdir -p "$STAGING/ios" "$STAGING/sim" +cp "$LIBS_DIR/libbackend_xnnpack_ios.a" "$STAGING/ios/libXnnpackBackend.a" +cp "$LIBS_DIR/libbackend_xnnpack_simulator.a" "$STAGING/sim/libXnnpackBackend.a" +xcodebuild -create-xcframework \ + -library "$STAGING/ios/libXnnpackBackend.a" \ + -library "$STAGING/sim/libXnnpackBackend.a" \ + -output "$OUTPUT_FOLDER/XnnpackBackend.xcframework" + +# --- Build CoreMLBackend.xcframework --- +cp "$LIBS_DIR/libbackend_coreml_ios.a" "$STAGING/ios/libCoreMLBackend.a" +cp "$LIBS_DIR/libbackend_coreml_simulator.a" "$STAGING/sim/libCoreMLBackend.a" +xcodebuild -create-xcframework \ + -library "$STAGING/ios/libCoreMLBackend.a" \ + -library "$STAGING/sim/libCoreMLBackend.a" \ + -output "$OUTPUT_FOLDER/CoreMLBackend.xcframework" +rm -rf "$STAGING" + +echo "Done! Output:" +echo " $OUTPUT_FOLDER/ExecutorchLib.xcframework" +echo " $OUTPUT_FOLDER/XnnpackBackend.xcframework" +echo " $OUTPUT_FOLDER/CoreMLBackend.xcframework" diff --git a/packages/react-native-executorch/third-party/ios/libs/cpuinfo/libcpuinfo.a b/packages/react-native-executorch/third-party/ios/libs/cpuinfo/libcpuinfo.a deleted file mode 100644 index b5d3895694..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/cpuinfo/libcpuinfo.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_ios.a deleted file mode 100644 index 4e2a80a220..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_simulator.a deleted file mode 100644 index c5b16beb50..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_coreml_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_ios.a deleted file mode 100644 index ce63bfd904..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_simulator.a deleted file mode 100644 index 3679faaa46..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_mps_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_ios.a deleted file mode 100644 index 4049c0a640..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a deleted file mode 100644 index bd145d474f..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libbackend_xnnpack_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a deleted file mode 100644 index 221409be07..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a deleted file mode 100644 index 40b74e9c7d..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a deleted file mode 100644 index bec9105acb..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_llm_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a deleted file mode 100644 index 99449364f2..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libexecutorch_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_ios.a deleted file mode 100644 index 5217a54902..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_simulator.a deleted file mode 100644 index 9add6b355c..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_llm_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_ios.a deleted file mode 100644 index 9cc9a42bd2..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_simulator.a deleted file mode 100644 index cb06a86712..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_optimized_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_ios.a deleted file mode 100644 index d54805247e..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_simulator.a deleted file mode 100644 index 41f06f64fb..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_quantized_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_ios.a deleted file mode 100644 index 19db3e80f4..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_simulator.a deleted file mode 100644 index af26c633e1..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libkernels_torchao_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_ios.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_ios.a deleted file mode 100644 index 8c14cf924b..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_ios.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_simulator.a b/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_simulator.a deleted file mode 100644 index d6deeb5a00..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/executorch/libthreadpool_simulator.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/phonemis/physical-arm64-release/libphonemis.a b/packages/react-native-executorch/third-party/ios/libs/phonemis/physical-arm64-release/libphonemis.a deleted file mode 100644 index 78f5169308..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/phonemis/physical-arm64-release/libphonemis.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/phonemis/simulator-arm64-debug/libphonemis.a b/packages/react-native-executorch/third-party/ios/libs/phonemis/simulator-arm64-debug/libphonemis.a deleted file mode 100644 index ccf1d2fa64..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/phonemis/simulator-arm64-debug/libphonemis.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a deleted file mode 100644 index 652afff7bf..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/physical-arm64-release/libpthreadpool.a and /dev/null differ diff --git a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a b/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a deleted file mode 100644 index e3c3053a15..0000000000 Binary files a/packages/react-native-executorch/third-party/ios/libs/pthreadpool/simulator-arm64-debug/libpthreadpool.a and /dev/null differ