react-native-litert-lm 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +269 -186
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +93 -37
  4. package/app.plugin.js +33 -0
  5. package/cpp/HybridLiteRTLM.cpp +604 -450
  6. package/cpp/HybridLiteRTLM.hpp +54 -23
  7. package/cpp/IOSDownloadHelper.h +24 -0
  8. package/cpp/cpp-adapter.cpp +2 -2
  9. package/cpp/include/litert_lm_engine.h +509 -0
  10. package/ios/IOSDownloadHelper.mm +129 -0
  11. package/ios/LiteRTLMAutolinking.mm +30 -0
  12. package/lib/hooks.d.ts +9 -4
  13. package/lib/hooks.js +34 -20
  14. package/lib/index.d.ts +1 -0
  15. package/lib/index.js +2 -5
  16. package/lib/memoryTracker.d.ts +1 -1
  17. package/lib/memoryTracker.js +1 -1
  18. package/lib/modelFactory.d.ts +11 -5
  19. package/lib/modelFactory.js +9 -4
  20. package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +11 -4
  21. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +31 -37
  22. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +19 -22
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +15 -18
  24. package/package.json +12 -5
  25. package/react-native-litert-lm.podspec +20 -7
  26. package/scripts/build-ios-engine.sh +302 -0
  27. package/scripts/download-ios-frameworks.sh +72 -0
  28. package/scripts/postinstall.js +116 -0
  29. package/scripts/stubs/cxx_bridge_stubs.cc +224 -0
  30. package/scripts/stubs/gemma_model_constraint_provider.cc +46 -0
  31. package/scripts/stubs/llguidance_stubs.c +101 -0
  32. package/src/hooks.ts +62 -39
  33. package/src/index.ts +4 -7
  34. package/src/memoryTracker.ts +1 -1
  35. package/src/modelFactory.ts +30 -5
@@ -18,34 +18,33 @@ namespace margelo::nitro::litertlm {
18
18
 
19
19
  using namespace facebook;
20
20
 
21
- class JHybridLiteRTLMSpec: public jni::HybridClass<JHybridLiteRTLMSpec, JHybridObject>,
22
- public virtual HybridLiteRTLMSpec {
21
+ class JHybridLiteRTLMSpec: public virtual HybridLiteRTLMSpec, public virtual JHybridObject {
23
22
  public:
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec;";
25
- static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
26
- static void registerNatives();
27
-
28
- protected:
29
- // C++ constructor (called from Java via `initHybrid()`)
30
- explicit JHybridLiteRTLMSpec(jni::alias_ref<jhybridobject> jThis) :
31
- HybridObject(HybridLiteRTLMSpec::TAG),
32
- HybridBase(jThis),
33
- _javaPart(jni::make_global(jThis)) {}
23
+ struct JavaPart: public jni::JavaClass<JavaPart, JHybridObject::JavaPart> {
24
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec;";
25
+ std::shared_ptr<JHybridLiteRTLMSpec> getJHybridLiteRTLMSpec();
26
+ };
27
+ struct CxxPart: public jni::HybridClass<CxxPart, JHybridObject::CxxPart> {
28
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec$CxxPart;";
29
+ static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
30
+ static void registerNatives();
31
+ using HybridBase::HybridBase;
32
+ protected:
33
+ std::shared_ptr<JHybridObject> createHybridObject(const jni::local_ref<JHybridObject::JavaPart>& javaPart) override;
34
+ };
34
35
 
35
36
  public:
37
+ explicit JHybridLiteRTLMSpec(const jni::local_ref<JHybridLiteRTLMSpec::JavaPart>& javaPart):
38
+ HybridObject(HybridLiteRTLMSpec::TAG),
39
+ JHybridObject(javaPart),
40
+ _javaPart(jni::make_global(javaPart)) {}
36
41
  ~JHybridLiteRTLMSpec() override {
37
42
  // Hermes GC can destroy JS objects on a non-JNI Thread.
38
43
  jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); });
39
44
  }
40
45
 
41
46
  public:
42
- size_t getExternalMemorySize() noexcept override;
43
- bool equals(const std::shared_ptr<HybridObject>& other) override;
44
- void dispose() noexcept override;
45
- std::string toString() override;
46
-
47
- public:
48
- inline const jni::global_ref<JHybridLiteRTLMSpec::javaobject>& getJavaPart() const noexcept {
47
+ inline const jni::global_ref<JHybridLiteRTLMSpec::JavaPart>& getJavaPart() const noexcept {
49
48
  return _javaPart;
50
49
  }
51
50
 
@@ -70,9 +69,7 @@ namespace margelo::nitro::litertlm {
70
69
  void close() override;
71
70
 
72
71
  private:
73
- friend HybridBase;
74
- using HybridBase::HybridBase;
75
- jni::global_ref<JHybridLiteRTLMSpec::javaobject> _javaPart;
72
+ jni::global_ref<JHybridLiteRTLMSpec::JavaPart> _javaPart;
76
73
  };
77
74
 
78
75
  } // namespace margelo::nitro::litertlm
@@ -25,23 +25,6 @@ import com.margelo.nitro.core.HybridObject
25
25
  "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName"
26
26
  )
27
27
  abstract class HybridLiteRTLMSpec: HybridObject() {
28
- @DoNotStrip
29
- private var mHybridData: HybridData = initHybrid()
30
-
31
- init {
32
- super.updateNative(mHybridData)
33
- }
34
-
35
- override fun updateNative(hybridData: HybridData) {
36
- mHybridData = hybridData
37
- super.updateNative(hybridData)
38
- }
39
-
40
- // Default implementation of `HybridObject.toString()`
41
- override fun toString(): String {
42
- return "[HybridObject LiteRTLM]"
43
- }
44
-
45
28
  // Properties
46
29
 
47
30
 
@@ -108,7 +91,21 @@ abstract class HybridLiteRTLMSpec: HybridObject() {
108
91
  @Keep
109
92
  abstract fun close(): Unit
110
93
 
111
- private external fun initHybrid(): HybridData
94
+ // Default implementation of `HybridObject.toString()`
95
+ override fun toString(): String {
96
+ return "[HybridObject LiteRTLM]"
97
+ }
98
+
99
+ // C++ backing class
100
+ @DoNotStrip
101
+ @Keep
102
+ protected open class CxxPart(javaPart: HybridLiteRTLMSpec): HybridObject.CxxPart(javaPart) {
103
+ // C++ JHybridLiteRTLMSpec::CxxPart::initHybrid(...)
104
+ external override fun initHybrid(): HybridData
105
+ }
106
+ override fun createCxxPart(): CxxPart {
107
+ return CxxPart(this)
108
+ }
112
109
 
113
110
  companion object {
114
111
  protected const val TAG = "HybridLiteRTLMSpec"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "High-performance LLM inference for React Native using LiteRT-LM. Optimized for Gemma 3n and other on-device language models.",
5
5
  "license": "MIT",
6
6
  "author": "Hugh Chen (https://github.com/hung-yueh)",
@@ -37,9 +37,14 @@
37
37
  "android/src",
38
38
  "android/build.gradle",
39
39
  "android/CMakeLists.txt",
40
- "ios",
40
+ "ios/*.mm",
41
+ "ios/*.m",
41
42
  "cpp",
42
43
  "nitrogen/generated",
44
+ "scripts/postinstall.js",
45
+ "scripts/download-ios-frameworks.sh",
46
+ "scripts/build-ios-engine.sh",
47
+ "scripts/stubs",
43
48
  "react-native.config.js",
44
49
  "react-native-litert-lm.podspec",
45
50
  "app.plugin.js",
@@ -50,12 +55,14 @@
50
55
  "node": ">=22"
51
56
  },
52
57
  "scripts": {
58
+ "postinstall": "node scripts/postinstall.js",
53
59
  "build": "tsc",
54
60
  "typecheck": "tsc --noEmit",
55
61
  "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
56
62
  "prepack": "npm run build",
57
63
  "specs": "npx nitrogen",
58
- "clean": "rm -rf lib android/build ios/build nitrogen/generated",
64
+ "clean": "rm -rf lib android/build ios/build ios/Frameworks nitrogen/generated",
65
+ "download-frameworks": "scripts/download-ios-frameworks.sh",
59
66
  "android": "expo run:android",
60
67
  "android:clean": "cd android && ./gradlew clean",
61
68
  "ios": "expo run:ios",
@@ -65,7 +72,7 @@
65
72
  "@expo/config-plugins": "~54.0.4",
66
73
  "@types/react": "~19.1.10",
67
74
  "expo": "^54.0.31",
68
- "nitrogen": "^0.34.1",
75
+ "nitrogen": "^0.35.0",
69
76
  "react": "19.1.0",
70
77
  "react-native": "0.81.5",
71
78
  "release-it": "^19.2.4",
@@ -82,6 +89,6 @@
82
89
  }
83
90
  },
84
91
  "dependencies": {
85
- "react-native-nitro-modules": "^0.34.1"
92
+ "react-native-nitro-modules": "^0.35.0"
86
93
  }
87
94
  }
@@ -16,36 +16,49 @@ Pod::Spec.new do |s|
16
16
 
17
17
  s.source_files = [
18
18
  # Implementation (C++)
19
- "cpp/**/*.{hpp,cpp}",
19
+ "cpp/**/*.{hpp,cpp,h}",
20
20
  # Autolinking (Objective-C++)
21
21
  "ios/**/*.{m,mm}",
22
22
  # Nitrogen generated iOS bridge
23
23
  "nitrogen/generated/ios/**/*.{mm,swift}",
24
24
  ]
25
25
 
26
+ # Exclude Android-only JNI files from iOS build
27
+ s.exclude_files = [
28
+ "cpp/cpp-adapter.cpp",
29
+ ]
30
+
31
+ # Prebuilt LiteRT-LM C engine (static library built from Bazel //c:engine target).
32
+ # Downloaded from GitHub releases by postinstall.js, or built locally via:
33
+ # scripts/build-ios-engine.sh
34
+ s.vendored_frameworks = 'ios/Frameworks/LiteRTLM.xcframework'
35
+
26
36
  s.pod_target_xcconfig = {
27
37
  'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
28
38
  'CLANG_CXX_LIBRARY' => 'libc++',
29
39
  'HEADER_SEARCH_PATHS' => [
30
40
  '"$(PODS_TARGET_SRCROOT)/cpp"',
41
+ '"$(PODS_TARGET_SRCROOT)/cpp/include"',
31
42
  '"$(PODS_TARGET_SRCROOT)/nitrogen/generated/shared/c++"',
32
43
  '"$(PODS_TARGET_SRCROOT)/nitrogen/generated/ios"',
33
44
  ].join(' '),
34
- # Stub mode - LiteRT-LM iOS not yet available
35
- 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LITERT_LM_IOS_STUB=1',
45
+ 'OTHER_LDFLAGS' => '$(inherited) -ObjC',
36
46
  }
37
47
 
38
48
  # Load nitrogen autolinking
39
49
  load 'nitrogen/generated/ios/LiteRTLM+autolinking.rb'
40
50
  add_nitrogen_files(s)
41
51
 
42
- # Core dependencies only - no LLM runtime yet
52
+ # Core React Native dependencies
43
53
  s.dependency 'React-jsi'
44
54
  s.dependency 'React-callinvoker'
45
55
  s.dependency 'ReactCommon/turbomodule/core'
46
-
47
- # TODO: Add LiteRT-LM iOS dependency when officially released
48
- # s.dependency 'LiteRTLM', '~> 1.0'
56
+
57
+ # Apple frameworks needed by LiteRT-LM engine
58
+ # Metal/MPS: GPU inference, Accelerate: BLAS/LAPACK, CoreML: delegate
59
+ s.frameworks = ['Metal', 'MetalPerformanceShaders', 'Accelerate', 'CoreML', 'CoreGraphics']
60
+ s.libraries = ['c++']
49
61
 
50
62
  install_modules_dependencies(s)
51
63
  end
64
+
@@ -0,0 +1,302 @@
1
+ #!/bin/bash
2
+ # build-ios-engine.sh
3
+ #
4
+ # Builds the LiteRT-LM C engine as a static library for iOS (device + simulator)
5
+ # using Bazel, then packages it into an XCFramework for CocoaPods.
6
+ #
7
+ # Prerequisites:
8
+ # - Bazel 7.6.1+ (via Bazelisk recommended)
9
+ # - Xcode command line tools
10
+ #
11
+ # Usage:
12
+ # ./scripts/build-ios-engine.sh
13
+ #
14
+ # Output:
15
+ # ios/Frameworks/LiteRTLM.xcframework/ (static library + headers)
16
+
17
+ set -euo pipefail
18
+
19
+ LITERT_LM_VERSION="v0.9.0"
20
+ LITERT_LM_REPO="https://github.com/google-ai-edge/LiteRT-LM.git"
21
+ FRAMEWORK_NAME="LiteRTLM"
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
25
+ OUTPUT_DIR="$PROJECT_ROOT/ios/Frameworks"
26
+ C_API_HEADER_DIR="$PROJECT_ROOT/cpp/include"
27
+ BUILD_DIR="$PROJECT_ROOT/.litert-lm-build"
28
+
29
+ echo "==> Building LiteRT-LM ${LITERT_LM_VERSION} C engine for iOS..."
30
+ echo ""
31
+
32
+ # ---- 1. Clone / update the LiteRT-LM repo --------------------------------
33
+ echo "==> Step 1: Preparing LiteRT-LM source..."
34
+ if [ -f "$BUILD_DIR/LiteRT-LM/.bazelrc" ] && [ -f "$BUILD_DIR/LiteRT-LM/requirements.txt" ]; then
35
+ echo " Source already exists, checking out ${LITERT_LM_VERSION}..."
36
+ cd "$BUILD_DIR/LiteRT-LM"
37
+ git fetch --tags 2>/dev/null || true
38
+ git checkout "$LITERT_LM_VERSION" 2>/dev/null || git checkout "tags/$LITERT_LM_VERSION"
39
+ else
40
+ # Clean up any failed previous clone
41
+ rm -rf "$BUILD_DIR/LiteRT-LM"
42
+ mkdir -p "$BUILD_DIR"
43
+ echo " Cloning LiteRT-LM (shallow, skipping LFS)..."
44
+
45
+ # Clone without LFS filter to avoid requiring git-lfs installation.
46
+ # The prebuilt/ directory will contain LFS pointer files but we don't
47
+ # need those — we're building the engine from source.
48
+ GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 --branch "$LITERT_LM_VERSION" \
49
+ -c filter.lfs.smudge=cat \
50
+ -c filter.lfs.process= \
51
+ -c filter.lfs.clean=cat \
52
+ -c filter.lfs.required=false \
53
+ "$LITERT_LM_REPO" "$BUILD_DIR/LiteRT-LM"
54
+
55
+ cd "$BUILD_DIR/LiteRT-LM"
56
+ fi
57
+
58
+ LITERT_SRC="$BUILD_DIR/LiteRT-LM"
59
+
60
+ # ---- 1b. Apply iOS-specific patches ---------------------------------------
61
+ # These patches fix:
62
+ # - mmap PROT_WRITE removal (iOS rejects CoW for large files)
63
+ # - Error capture API (litert_lm_get_last_error)
64
+ # - Engine registerer moved outside anonymous namespace (iOS linker stripping)
65
+ # - Minijinja/Rust stub replacement (custom C++ prompt template)
66
+ PATCHES_DIR="$PROJECT_ROOT/scripts/patches"
67
+ if [ -d "$PATCHES_DIR" ]; then
68
+ for PATCH_FILE in "$PATCHES_DIR"/*.patch; do
69
+ if [ -f "$PATCH_FILE" ]; then
70
+ echo " Applying patch: $(basename "$PATCH_FILE")..."
71
+ cd "$LITERT_SRC"
72
+ git apply --check "$PATCH_FILE" 2>/dev/null && \
73
+ git apply "$PATCH_FILE" || \
74
+ echo " (patch already applied or conflicts, skipping)"
75
+ fi
76
+ done
77
+ fi
78
+
79
+ # ---- 2. Verify Bazel is available -----------------------------------------
80
+ echo ""
81
+ echo "==> Step 2: Checking Bazel..."
82
+ if command -v bazelisk &>/dev/null; then
83
+ BAZEL="bazelisk"
84
+ elif command -v bazel &>/dev/null; then
85
+ BAZEL="bazel"
86
+ else
87
+ echo "Error: Bazel is not installed."
88
+ echo "Install via: brew install bazelisk"
89
+ echo "Or download from: https://github.com/bazelbuild/bazelisk"
90
+ exit 1
91
+ fi
92
+
93
+ BAZEL_VERSION=$($BAZEL --version 2>&1 | head -1)
94
+ echo " Using: $BAZEL ($BAZEL_VERSION)"
95
+
96
+ # ---- 3. Build the C engine static library for iOS -------------------------
97
+ echo ""
98
+ echo "==> Step 3: Building //c:engine for iOS..."
99
+
100
+ cd "$LITERT_SRC"
101
+
102
+ # Get Bazel output base (where all .o files live in the actual filesystem)
103
+ BAZEL_OUTPUT_BASE=$($BAZEL info output_base 2>/dev/null)
104
+ BAZEL_EXECROOT="$BAZEL_OUTPUT_BASE/execroot"
105
+
106
+ STAGE_DIR="$BUILD_DIR/staged-libs"
107
+ mkdir -p "$STAGE_DIR"
108
+
109
+ # Helper: build for a config, then merge ALL transitive .o files into one .a
110
+ # Bazel's cc_library produces thin archives — the engine's transitive deps
111
+ # (absl, protobuf, runtime, KleidiAI, etc.) are separate .o files that must
112
+ # be merged into a single self-contained static library for Xcode.
113
+ build_fat_static_lib() {
114
+ local CONFIG="$1"
115
+ local CONFIG_DIR="$2" # e.g. "ios_arm64-opt" or "ios_sim_arm64-opt"
116
+ local OUTPUT_PATH="$3"
117
+
118
+ echo " Building for $CONFIG..."
119
+ # Build both the engine AND all cc_proto_library targets in a single Bazel
120
+ # invocation. Bazel's cc_proto_library compiles proto-generated code, but
121
+ # the .pb.o files only appear in the output tree if these targets are
122
+ # explicitly requested alongside the engine.
123
+ $BAZEL build \
124
+ //c:engine \
125
+ @sentencepiece//:sentencepiece_cc_proto \
126
+ @sentencepiece//:sentencepiece_model_cc_proto \
127
+ //runtime/proto:engine_cc_proto \
128
+ //runtime/proto:sampler_params_cc_proto \
129
+ //runtime/proto:llm_metadata_cc_proto \
130
+ //runtime/proto:token_cc_proto \
131
+ //runtime/proto:llm_model_type_cc_proto \
132
+ //runtime/util:external_file_cc_proto \
133
+ @com_google_protobuf//:protobuf \
134
+ @com_googlesource_code_re2//:re2 \
135
+ --config=$CONFIG 2>&1 | tail -5
136
+
137
+ echo " Collecting transitive object files from $CONFIG_DIR..."
138
+ local OBJ_LIST="$STAGE_DIR/${CONFIG}-objects.txt"
139
+ find "$BAZEL_EXECROOT" -path "*/${CONFIG_DIR}/bin/*" -name "*.o" \
140
+ ! -name "*.h.processed" 2>/dev/null | sort > "$OBJ_LIST"
141
+
142
+ # ---- Compile stubs for Rust/llguidance deps (unavailable on iOS) ----------
143
+ local EXTRA_OBJS="$STAGE_DIR/${CONFIG}-extra-objs"
144
+ rm -rf "$EXTRA_OBJS"
145
+ mkdir -p "$EXTRA_OBJS"
146
+
147
+ local STUBS_DIR="$PROJECT_ROOT/scripts/stubs"
148
+ local STUB_FILES=$(find "$STUBS_DIR" \( -name "*.cc" -o -name "*.c" \) 2>/dev/null)
149
+ if [ -n "$STUB_FILES" ]; then
150
+ echo " Compiling stubs for unavailable dependencies..."
151
+ local SDK_NAME="iphoneos"
152
+ local TARGET_TRIPLE="arm64-apple-ios15.0"
153
+ if [[ "$CONFIG_DIR" == *"sim"* ]]; then
154
+ SDK_NAME="iphonesimulator"
155
+ TARGET_TRIPLE="arm64-apple-ios15.0-simulator"
156
+ fi
157
+ local SDK_PATH=$(xcrun --sdk "$SDK_NAME" --show-sdk-path)
158
+
159
+ for STUB_SRC in $STUB_FILES; do
160
+ local STUB_BASE=$(basename "$STUB_SRC")
161
+ local STUB_NAME="${STUB_BASE%.*}"
162
+ local STUB_EXT="${STUB_BASE##*.}"
163
+ echo " → $STUB_NAME ($STUB_EXT)"
164
+
165
+ if [ "$STUB_EXT" = "cc" ]; then
166
+ xcrun clang++ -c -std=c++20 \
167
+ -target "$TARGET_TRIPLE" \
168
+ -isysroot "$SDK_PATH" \
169
+ -DNDEBUG \
170
+ -o "$EXTRA_OBJS/${STUB_NAME}.o" \
171
+ "$STUB_SRC" 2>&1 || true
172
+ else
173
+ xcrun clang -c \
174
+ -target "$TARGET_TRIPLE" \
175
+ -isysroot "$SDK_PATH" \
176
+ -DNDEBUG \
177
+ -o "$EXTRA_OBJS/${STUB_NAME}.o" \
178
+ "$STUB_SRC" 2>&1 || true
179
+ fi
180
+ done
181
+
182
+ # Add successfully compiled stubs to the object list
183
+ find "$EXTRA_OBJS" -name "*.o" -size +0c >> "$OBJ_LIST"
184
+ fi
185
+
186
+ local OBJ_COUNT=$(wc -l < "$OBJ_LIST" | tr -d ' ')
187
+ echo " Found $OBJ_COUNT total object files (including proto + stubs)"
188
+
189
+ if [ "$OBJ_COUNT" -eq 0 ]; then
190
+ echo "Error: No object files found for $CONFIG in $CONFIG_DIR"
191
+ exit 1
192
+ fi
193
+
194
+ # Merge all .o files into a single fat static library using libtool
195
+ echo " Merging into fat static library..."
196
+ xcrun libtool -static -o "$OUTPUT_PATH" -filelist "$OBJ_LIST" 2>&1 | grep -v "has no symbols" || true
197
+
198
+ local LIB_SIZE=$(du -h "$OUTPUT_PATH" | cut -f1)
199
+ echo " ✅ $CONFIG: $LIB_SIZE ($OBJ_COUNT objects)"
200
+ }
201
+
202
+ # Build for device (arm64)
203
+ DEVICE_LIB="$STAGE_DIR/libengine-device.a"
204
+ build_fat_static_lib "ios_arm64" "ios_arm64-opt" "$DEVICE_LIB"
205
+
206
+ echo ""
207
+
208
+ # Build for simulator (sim_arm64)
209
+ SIM_LIB="$STAGE_DIR/libengine-sim.a"
210
+ build_fat_static_lib "ios_sim_arm64" "ios_sim_arm64-opt" "$SIM_LIB"
211
+
212
+ echo ""
213
+ echo " Device lib: $DEVICE_LIB ($(du -h "$DEVICE_LIB" | cut -f1))"
214
+ echo " Simulator lib: $SIM_LIB ($(du -h "$SIM_LIB" | cut -f1))"
215
+
216
+ # ---- 4. Copy the C API header ---------------------------------------------
217
+ echo ""
218
+ echo "==> Step 4: Vendoring C API header..."
219
+ mkdir -p "$C_API_HEADER_DIR"
220
+ cp "$LITERT_SRC/c/engine.h" "$C_API_HEADER_DIR/litert_lm_engine.h"
221
+ echo " ✅ Copied engine.h → cpp/include/litert_lm_engine.h"
222
+
223
+ # ---- 5. Create XCFramework from static libraries --------------------------
224
+ echo ""
225
+ echo "==> Step 5: Creating XCFramework..."
226
+
227
+ rm -rf "$OUTPUT_DIR"
228
+ mkdir -p "$OUTPUT_DIR"
229
+
230
+ TMP_DIR="$(mktemp -d)"
231
+ cleanup() { rm -rf "$TMP_DIR"; }
232
+ trap cleanup EXIT
233
+
234
+ # Create framework bundles from static libraries
235
+ for ARCH_NAME in "device" "simulator"; do
236
+ if [ "$ARCH_NAME" = "device" ]; then
237
+ LIB_PATH="$DEVICE_LIB"
238
+ else
239
+ LIB_PATH="$SIM_LIB"
240
+ fi
241
+
242
+ FW_DIR="$TMP_DIR/$ARCH_NAME/$FRAMEWORK_NAME.framework"
243
+ mkdir -p "$FW_DIR/Headers"
244
+
245
+ # Copy static lib as the framework binary
246
+ cp "$LIB_PATH" "$FW_DIR/$FRAMEWORK_NAME"
247
+
248
+ # Copy headers
249
+ cp "$C_API_HEADER_DIR/litert_lm_engine.h" "$FW_DIR/Headers/"
250
+
251
+ # Create Info.plist
252
+ cat > "$FW_DIR/Info.plist" << PLIST
253
+ <?xml version="1.0" encoding="UTF-8"?>
254
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
255
+ <plist version="1.0">
256
+ <dict>
257
+ <key>CFBundleDevelopmentRegion</key>
258
+ <string>en</string>
259
+ <key>CFBundleExecutable</key>
260
+ <string>${FRAMEWORK_NAME}</string>
261
+ <key>CFBundleIdentifier</key>
262
+ <string>com.google.ai.edge.litert-lm</string>
263
+ <key>CFBundleInfoDictionaryVersion</key>
264
+ <string>6.0</string>
265
+ <key>CFBundleName</key>
266
+ <string>${FRAMEWORK_NAME}</string>
267
+ <key>CFBundlePackageType</key>
268
+ <string>FMWK</string>
269
+ <key>CFBundleShortVersionString</key>
270
+ <string>0.9.0</string>
271
+ <key>CFBundleVersion</key>
272
+ <string>1</string>
273
+ <key>MinimumOSVersion</key>
274
+ <string>15.0</string>
275
+ </dict>
276
+ </plist>
277
+ PLIST
278
+ done
279
+
280
+ # Create XCFramework
281
+ xcodebuild -create-xcframework \
282
+ -framework "$TMP_DIR/device/$FRAMEWORK_NAME.framework" \
283
+ -framework "$TMP_DIR/simulator/$FRAMEWORK_NAME.framework" \
284
+ -output "$OUTPUT_DIR/$FRAMEWORK_NAME.xcframework" 2>&1
285
+
286
+ echo " ✅ XCFramework created at: ios/Frameworks/${FRAMEWORK_NAME}.xcframework"
287
+
288
+ # ---- 6. Create zip for release asset --------------------------------------
289
+ echo ""
290
+ echo "==> Step 6: Creating release asset zip..."
291
+ cd "$OUTPUT_DIR"
292
+ zip -r "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" . -x ".*" 2>&1
293
+ ZIP_SIZE=$(du -h "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" | cut -f1)
294
+ echo " ✅ Created LiteRTLM-ios-frameworks.zip (${ZIP_SIZE})"
295
+
296
+ echo ""
297
+ echo "==> Done! iOS engine built and packaged."
298
+ echo ""
299
+ echo "Contents:"
300
+ find "$OUTPUT_DIR" -type f | head -20 | while read f; do
301
+ echo " $(echo "$f" | sed "s|$PROJECT_ROOT/||") ($(du -h "$f" | cut -f1))"
302
+ done
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ # download-ios-frameworks.sh
3
+ #
4
+ # Downloads prebuilt LiteRT-LM iOS static engine from this project's GitHub
5
+ # releases. If the prebuilt asset is not available, falls back to building
6
+ # from source via Bazel (see build-ios-engine.sh).
7
+ #
8
+ # The XCFramework contains a static library compiled from the LiteRT-LM
9
+ # C engine (//c:engine Bazel target) for both device (arm64) and simulator
10
+ # (sim_arm64).
11
+ #
12
+ # Usage:
13
+ # ./scripts/download-ios-frameworks.sh
14
+
15
+ set -euo pipefail
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
18
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
19
+ OUTPUT_DIR="$PROJECT_ROOT/ios/Frameworks"
20
+ C_API_HEADER_DIR="$PROJECT_ROOT/cpp/include"
21
+
22
+ LITERT_LM_VERSION="v0.9.0"
23
+ GITHUB_RAW="https://github.com/google-ai-edge/LiteRT-LM/raw/${LITERT_LM_VERSION}"
24
+
25
+ # Read version from package.json
26
+ PACKAGE_VERSION=$(node -e "console.log(require('$PROJECT_ROOT/package.json').version)" 2>/dev/null || echo "0.0.0")
27
+ GITHUB_REPO="hung-yueh/react-native-litert-lm"
28
+ ASSET_NAME="LiteRTLM-ios-frameworks.zip"
29
+
30
+ # Skip if already present
31
+ if [ -d "$OUTPUT_DIR" ] && [ "$(find "$OUTPUT_DIR" -name "*.xcframework" 2>/dev/null | wc -l)" -gt 0 ]; then
32
+ echo "[LiteRT-LM] iOS frameworks already present at ios/Frameworks/, skipping."
33
+ exit 0
34
+ fi
35
+
36
+ # ---- Ensure C API header is vendored --------------------------------------
37
+ echo "[LiteRT-LM] Vendoring C API header..."
38
+ mkdir -p "$C_API_HEADER_DIR"
39
+ curl -fsSL -o "$C_API_HEADER_DIR/litert_lm_engine.h" \
40
+ "${GITHUB_RAW}/c/engine.h" 2>/dev/null || true
41
+
42
+ # ---- Try downloading prebuilt from our GitHub releases --------------------
43
+ RELEASE_URL="https://github.com/${GITHUB_REPO}/releases/download/v${PACKAGE_VERSION}/${ASSET_NAME}"
44
+
45
+ echo "[LiteRT-LM] Attempting to download prebuilt iOS engine from:"
46
+ echo " ${RELEASE_URL}"
47
+
48
+ TMP_ZIP="$PROJECT_ROOT/.ios-frameworks-tmp.zip"
49
+ if curl -fsSL -o "$TMP_ZIP" "$RELEASE_URL" 2>/dev/null; then
50
+ echo "[LiteRT-LM] Download successful, extracting..."
51
+ rm -rf "$OUTPUT_DIR"
52
+ mkdir -p "$OUTPUT_DIR"
53
+ unzip -o -q "$TMP_ZIP" -d "$OUTPUT_DIR"
54
+ rm -f "$TMP_ZIP"
55
+ echo "[LiteRT-LM] ✅ iOS frameworks installed from prebuilt release."
56
+ exit 0
57
+ fi
58
+
59
+ rm -f "$TMP_ZIP"
60
+ echo "[LiteRT-LM] Prebuilt not available for v${PACKAGE_VERSION}."
61
+
62
+ # ---- Fall back to building from source ------------------------------------
63
+ echo "[LiteRT-LM] Falling back to building from source via Bazel..."
64
+ echo ""
65
+
66
+ if [ -x "$SCRIPT_DIR/build-ios-engine.sh" ]; then
67
+ exec "$SCRIPT_DIR/build-ios-engine.sh"
68
+ else
69
+ echo "Error: build-ios-engine.sh not found or not executable."
70
+ echo "Run manually: ./scripts/build-ios-engine.sh"
71
+ exit 1
72
+ fi