react-native-litert-lm 0.2.1 → 0.3.0

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 (43) hide show
  1. package/README.md +331 -150
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +140 -37
  4. package/app.plugin.js +33 -0
  5. package/cpp/HybridLiteRTLM.cpp +577 -378
  6. package/cpp/HybridLiteRTLM.hpp +66 -23
  7. package/cpp/IOSDownloadHelper.h +24 -0
  8. package/cpp/cpp-adapter.cpp +10 -2
  9. package/cpp/include/litert_lm_engine.h +502 -0
  10. package/ios/IOSDownloadHelper.mm +129 -0
  11. package/ios/LiteRTLMAutolinking.mm +30 -0
  12. package/lib/hooks.d.ts +33 -3
  13. package/lib/hooks.js +54 -23
  14. package/lib/index.d.ts +4 -1
  15. package/lib/index.js +6 -6
  16. package/lib/memoryTracker.d.ts +128 -0
  17. package/lib/memoryTracker.js +155 -0
  18. package/lib/modelFactory.d.ts +21 -2
  19. package/lib/modelFactory.js +78 -11
  20. package/lib/specs/LiteRTLM.nitro.d.ts +19 -0
  21. package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +28 -18
  22. package/nitrogen/generated/android/LiteRTLMOnLoad.hpp +13 -4
  23. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +39 -36
  24. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +20 -22
  25. package/nitrogen/generated/android/c++/JMemoryUsage.hpp +69 -0
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +19 -18
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +47 -0
  28. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +1 -0
  29. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +4 -0
  30. package/nitrogen/generated/shared/c++/MemoryUsage.hpp +95 -0
  31. package/package.json +12 -5
  32. package/react-native-litert-lm.podspec +20 -7
  33. package/scripts/build-ios-engine.sh +283 -0
  34. package/scripts/download-ios-frameworks.sh +72 -0
  35. package/scripts/postinstall.js +116 -0
  36. package/scripts/stubs/cxx_bridge_stubs.cc +224 -0
  37. package/scripts/stubs/gemma_model_constraint_provider.cc +46 -0
  38. package/scripts/stubs/llguidance_stubs.c +101 -0
  39. package/src/hooks.ts +107 -41
  40. package/src/index.ts +13 -6
  41. package/src/memoryTracker.ts +268 -0
  42. package/src/modelFactory.ts +107 -11
  43. package/src/specs/LiteRTLM.nitro.ts +21 -0
@@ -0,0 +1,95 @@
1
+ ///
2
+ /// MemoryUsage.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
11
+ #include <NitroModules/JSIConverter.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
16
+ #include <NitroModules/NitroDefines.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/JSIHelpers.hpp>)
21
+ #include <NitroModules/JSIHelpers.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+ #if __has_include(<NitroModules/PropNameIDCache.hpp>)
26
+ #include <NitroModules/PropNameIDCache.hpp>
27
+ #else
28
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
29
+ #endif
30
+
31
+
32
+
33
+
34
+
35
+ namespace margelo::nitro::litertlm {
36
+
37
+ /**
38
+ * A struct which can be represented as a JavaScript object (MemoryUsage).
39
+ */
40
+ struct MemoryUsage final {
41
+ public:
42
+ double nativeHeapBytes SWIFT_PRIVATE;
43
+ double residentBytes SWIFT_PRIVATE;
44
+ double availableMemoryBytes SWIFT_PRIVATE;
45
+ bool isLowMemory SWIFT_PRIVATE;
46
+
47
+ public:
48
+ MemoryUsage() = default;
49
+ explicit MemoryUsage(double nativeHeapBytes, double residentBytes, double availableMemoryBytes, bool isLowMemory): nativeHeapBytes(nativeHeapBytes), residentBytes(residentBytes), availableMemoryBytes(availableMemoryBytes), isLowMemory(isLowMemory) {}
50
+
51
+ public:
52
+ friend bool operator==(const MemoryUsage& lhs, const MemoryUsage& rhs) = default;
53
+ };
54
+
55
+ } // namespace margelo::nitro::litertlm
56
+
57
+ namespace margelo::nitro {
58
+
59
+ // C++ MemoryUsage <> JS MemoryUsage (object)
60
+ template <>
61
+ struct JSIConverter<margelo::nitro::litertlm::MemoryUsage> final {
62
+ static inline margelo::nitro::litertlm::MemoryUsage fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
63
+ jsi::Object obj = arg.asObject(runtime);
64
+ return margelo::nitro::litertlm::MemoryUsage(
65
+ JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "nativeHeapBytes"))),
66
+ JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "residentBytes"))),
67
+ JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "availableMemoryBytes"))),
68
+ JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isLowMemory")))
69
+ );
70
+ }
71
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::litertlm::MemoryUsage& arg) {
72
+ jsi::Object obj(runtime);
73
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "nativeHeapBytes"), JSIConverter<double>::toJSI(runtime, arg.nativeHeapBytes));
74
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "residentBytes"), JSIConverter<double>::toJSI(runtime, arg.residentBytes));
75
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "availableMemoryBytes"), JSIConverter<double>::toJSI(runtime, arg.availableMemoryBytes));
76
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "isLowMemory"), JSIConverter<bool>::toJSI(runtime, arg.isLowMemory));
77
+ return obj;
78
+ }
79
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
80
+ if (!value.isObject()) {
81
+ return false;
82
+ }
83
+ jsi::Object obj = value.getObject(runtime);
84
+ if (!nitro::isPlainObject(runtime, obj)) {
85
+ return false;
86
+ }
87
+ if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "nativeHeapBytes")))) return false;
88
+ if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "residentBytes")))) return false;
89
+ if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "availableMemoryBytes")))) return false;
90
+ if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "isLowMemory")))) return false;
91
+ return true;
92
+ }
93
+ };
94
+
95
+ } // namespace margelo::nitro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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.33.2",
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.33.2"
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,283 @@
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
+ # ---- 2. Verify Bazel is available -----------------------------------------
61
+ echo ""
62
+ echo "==> Step 2: Checking Bazel..."
63
+ if command -v bazelisk &>/dev/null; then
64
+ BAZEL="bazelisk"
65
+ elif command -v bazel &>/dev/null; then
66
+ BAZEL="bazel"
67
+ else
68
+ echo "Error: Bazel is not installed."
69
+ echo "Install via: brew install bazelisk"
70
+ echo "Or download from: https://github.com/bazelbuild/bazelisk"
71
+ exit 1
72
+ fi
73
+
74
+ BAZEL_VERSION=$($BAZEL --version 2>&1 | head -1)
75
+ echo " Using: $BAZEL ($BAZEL_VERSION)"
76
+
77
+ # ---- 3. Build the C engine static library for iOS -------------------------
78
+ echo ""
79
+ echo "==> Step 3: Building //c:engine for iOS..."
80
+
81
+ cd "$LITERT_SRC"
82
+
83
+ # Get Bazel output base (where all .o files live in the actual filesystem)
84
+ BAZEL_OUTPUT_BASE=$($BAZEL info output_base 2>/dev/null)
85
+ BAZEL_EXECROOT="$BAZEL_OUTPUT_BASE/execroot"
86
+
87
+ STAGE_DIR="$BUILD_DIR/staged-libs"
88
+ mkdir -p "$STAGE_DIR"
89
+
90
+ # Helper: build for a config, then merge ALL transitive .o files into one .a
91
+ # Bazel's cc_library produces thin archives — the engine's transitive deps
92
+ # (absl, protobuf, runtime, KleidiAI, etc.) are separate .o files that must
93
+ # be merged into a single self-contained static library for Xcode.
94
+ build_fat_static_lib() {
95
+ local CONFIG="$1"
96
+ local CONFIG_DIR="$2" # e.g. "ios_arm64-opt" or "ios_sim_arm64-opt"
97
+ local OUTPUT_PATH="$3"
98
+
99
+ echo " Building for $CONFIG..."
100
+ # Build both the engine AND all cc_proto_library targets in a single Bazel
101
+ # invocation. Bazel's cc_proto_library compiles proto-generated code, but
102
+ # the .pb.o files only appear in the output tree if these targets are
103
+ # explicitly requested alongside the engine.
104
+ $BAZEL build \
105
+ //c:engine \
106
+ @sentencepiece//:sentencepiece_cc_proto \
107
+ @sentencepiece//:sentencepiece_model_cc_proto \
108
+ //runtime/proto:engine_cc_proto \
109
+ //runtime/proto:sampler_params_cc_proto \
110
+ //runtime/proto:llm_metadata_cc_proto \
111
+ //runtime/proto:token_cc_proto \
112
+ //runtime/proto:llm_model_type_cc_proto \
113
+ //runtime/util:external_file_cc_proto \
114
+ @com_google_protobuf//:protobuf \
115
+ @com_googlesource_code_re2//:re2 \
116
+ --config=$CONFIG 2>&1 | tail -5
117
+
118
+ echo " Collecting transitive object files from $CONFIG_DIR..."
119
+ local OBJ_LIST="$STAGE_DIR/${CONFIG}-objects.txt"
120
+ find "$BAZEL_EXECROOT" -path "*/${CONFIG_DIR}/bin/*" -name "*.o" \
121
+ ! -name "*.h.processed" 2>/dev/null | sort > "$OBJ_LIST"
122
+
123
+ # ---- Compile stubs for Rust/llguidance deps (unavailable on iOS) ----------
124
+ local EXTRA_OBJS="$STAGE_DIR/${CONFIG}-extra-objs"
125
+ rm -rf "$EXTRA_OBJS"
126
+ mkdir -p "$EXTRA_OBJS"
127
+
128
+ local STUBS_DIR="$PROJECT_ROOT/scripts/stubs"
129
+ local STUB_FILES=$(find "$STUBS_DIR" \( -name "*.cc" -o -name "*.c" \) 2>/dev/null)
130
+ if [ -n "$STUB_FILES" ]; then
131
+ echo " Compiling stubs for unavailable dependencies..."
132
+ local SDK_NAME="iphoneos"
133
+ local TARGET_TRIPLE="arm64-apple-ios15.0"
134
+ if [[ "$CONFIG_DIR" == *"sim"* ]]; then
135
+ SDK_NAME="iphonesimulator"
136
+ TARGET_TRIPLE="arm64-apple-ios15.0-simulator"
137
+ fi
138
+ local SDK_PATH=$(xcrun --sdk "$SDK_NAME" --show-sdk-path)
139
+
140
+ for STUB_SRC in $STUB_FILES; do
141
+ local STUB_BASE=$(basename "$STUB_SRC")
142
+ local STUB_NAME="${STUB_BASE%.*}"
143
+ local STUB_EXT="${STUB_BASE##*.}"
144
+ echo " → $STUB_NAME ($STUB_EXT)"
145
+
146
+ if [ "$STUB_EXT" = "cc" ]; then
147
+ xcrun clang++ -c -std=c++20 \
148
+ -target "$TARGET_TRIPLE" \
149
+ -isysroot "$SDK_PATH" \
150
+ -DNDEBUG \
151
+ -o "$EXTRA_OBJS/${STUB_NAME}.o" \
152
+ "$STUB_SRC" 2>&1 || true
153
+ else
154
+ xcrun clang -c \
155
+ -target "$TARGET_TRIPLE" \
156
+ -isysroot "$SDK_PATH" \
157
+ -DNDEBUG \
158
+ -o "$EXTRA_OBJS/${STUB_NAME}.o" \
159
+ "$STUB_SRC" 2>&1 || true
160
+ fi
161
+ done
162
+
163
+ # Add successfully compiled stubs to the object list
164
+ find "$EXTRA_OBJS" -name "*.o" -size +0c >> "$OBJ_LIST"
165
+ fi
166
+
167
+ local OBJ_COUNT=$(wc -l < "$OBJ_LIST" | tr -d ' ')
168
+ echo " Found $OBJ_COUNT total object files (including proto + stubs)"
169
+
170
+ if [ "$OBJ_COUNT" -eq 0 ]; then
171
+ echo "Error: No object files found for $CONFIG in $CONFIG_DIR"
172
+ exit 1
173
+ fi
174
+
175
+ # Merge all .o files into a single fat static library using libtool
176
+ echo " Merging into fat static library..."
177
+ xcrun libtool -static -o "$OUTPUT_PATH" -filelist "$OBJ_LIST" 2>&1 | grep -v "has no symbols" || true
178
+
179
+ local LIB_SIZE=$(du -h "$OUTPUT_PATH" | cut -f1)
180
+ echo " ✅ $CONFIG: $LIB_SIZE ($OBJ_COUNT objects)"
181
+ }
182
+
183
+ # Build for device (arm64)
184
+ DEVICE_LIB="$STAGE_DIR/libengine-device.a"
185
+ build_fat_static_lib "ios_arm64" "ios_arm64-opt" "$DEVICE_LIB"
186
+
187
+ echo ""
188
+
189
+ # Build for simulator (sim_arm64)
190
+ SIM_LIB="$STAGE_DIR/libengine-sim.a"
191
+ build_fat_static_lib "ios_sim_arm64" "ios_sim_arm64-opt" "$SIM_LIB"
192
+
193
+ echo ""
194
+ echo " Device lib: $DEVICE_LIB ($(du -h "$DEVICE_LIB" | cut -f1))"
195
+ echo " Simulator lib: $SIM_LIB ($(du -h "$SIM_LIB" | cut -f1))"
196
+
197
+ # ---- 4. Copy the C API header ---------------------------------------------
198
+ echo ""
199
+ echo "==> Step 4: Vendoring C API header..."
200
+ mkdir -p "$C_API_HEADER_DIR"
201
+ cp "$LITERT_SRC/c/engine.h" "$C_API_HEADER_DIR/litert_lm_engine.h"
202
+ echo " ✅ Copied engine.h → cpp/include/litert_lm_engine.h"
203
+
204
+ # ---- 5. Create XCFramework from static libraries --------------------------
205
+ echo ""
206
+ echo "==> Step 5: Creating XCFramework..."
207
+
208
+ rm -rf "$OUTPUT_DIR"
209
+ mkdir -p "$OUTPUT_DIR"
210
+
211
+ TMP_DIR="$(mktemp -d)"
212
+ cleanup() { rm -rf "$TMP_DIR"; }
213
+ trap cleanup EXIT
214
+
215
+ # Create framework bundles from static libraries
216
+ for ARCH_NAME in "device" "simulator"; do
217
+ if [ "$ARCH_NAME" = "device" ]; then
218
+ LIB_PATH="$DEVICE_LIB"
219
+ else
220
+ LIB_PATH="$SIM_LIB"
221
+ fi
222
+
223
+ FW_DIR="$TMP_DIR/$ARCH_NAME/$FRAMEWORK_NAME.framework"
224
+ mkdir -p "$FW_DIR/Headers"
225
+
226
+ # Copy static lib as the framework binary
227
+ cp "$LIB_PATH" "$FW_DIR/$FRAMEWORK_NAME"
228
+
229
+ # Copy headers
230
+ cp "$C_API_HEADER_DIR/litert_lm_engine.h" "$FW_DIR/Headers/"
231
+
232
+ # Create Info.plist
233
+ cat > "$FW_DIR/Info.plist" << PLIST
234
+ <?xml version="1.0" encoding="UTF-8"?>
235
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
236
+ <plist version="1.0">
237
+ <dict>
238
+ <key>CFBundleDevelopmentRegion</key>
239
+ <string>en</string>
240
+ <key>CFBundleExecutable</key>
241
+ <string>${FRAMEWORK_NAME}</string>
242
+ <key>CFBundleIdentifier</key>
243
+ <string>com.google.ai.edge.litert-lm</string>
244
+ <key>CFBundleInfoDictionaryVersion</key>
245
+ <string>6.0</string>
246
+ <key>CFBundleName</key>
247
+ <string>${FRAMEWORK_NAME}</string>
248
+ <key>CFBundlePackageType</key>
249
+ <string>FMWK</string>
250
+ <key>CFBundleShortVersionString</key>
251
+ <string>0.9.0</string>
252
+ <key>CFBundleVersion</key>
253
+ <string>1</string>
254
+ <key>MinimumOSVersion</key>
255
+ <string>15.0</string>
256
+ </dict>
257
+ </plist>
258
+ PLIST
259
+ done
260
+
261
+ # Create XCFramework
262
+ xcodebuild -create-xcframework \
263
+ -framework "$TMP_DIR/device/$FRAMEWORK_NAME.framework" \
264
+ -framework "$TMP_DIR/simulator/$FRAMEWORK_NAME.framework" \
265
+ -output "$OUTPUT_DIR/$FRAMEWORK_NAME.xcframework" 2>&1
266
+
267
+ echo " ✅ XCFramework created at: ios/Frameworks/${FRAMEWORK_NAME}.xcframework"
268
+
269
+ # ---- 6. Create zip for release asset --------------------------------------
270
+ echo ""
271
+ echo "==> Step 6: Creating release asset zip..."
272
+ cd "$OUTPUT_DIR"
273
+ zip -r "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" . -x ".*" 2>&1
274
+ ZIP_SIZE=$(du -h "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" | cut -f1)
275
+ echo " ✅ Created LiteRTLM-ios-frameworks.zip (${ZIP_SIZE})"
276
+
277
+ echo ""
278
+ echo "==> Done! iOS engine built and packaged."
279
+ echo ""
280
+ echo "Contents:"
281
+ find "$OUTPUT_DIR" -type f | head -20 | while read f; do
282
+ echo " $(echo "$f" | sed "s|$PROJECT_ROOT/||") ($(du -h "$f" | cut -f1))"
283
+ 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
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall.js
4
+ *
5
+ * Downloads prebuilt LiteRT-LM iOS frameworks from this package's GitHub
6
+ * releases when consumers run `npm install react-native-litert-lm`.
7
+ *
8
+ * Skips download if:
9
+ * - Not on macOS (iOS builds require macOS)
10
+ * - Frameworks already exist
11
+ * - CI environment with SKIP_IOS_FRAMEWORK_DOWNLOAD=1
12
+ */
13
+
14
+ const { execSync } = require('child_process');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const https = require('https');
18
+
19
+ const PACKAGE_JSON = require('../package.json');
20
+ const PACKAGE_VERSION = PACKAGE_JSON.version;
21
+ const GITHUB_REPO = 'hung-yueh/react-native-litert-lm';
22
+ const ASSET_NAME = 'LiteRTLM-ios-frameworks.zip';
23
+
24
+ const SCRIPT_DIR = __dirname;
25
+ const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '..');
26
+ const FRAMEWORKS_DIR = path.join(PACKAGE_ROOT, 'ios', 'Frameworks');
27
+
28
+ function log(msg) {
29
+ console.log(`[react-native-litert-lm] ${msg}`);
30
+ }
31
+
32
+ function shouldSkip() {
33
+ // Skip if not macOS
34
+ if (process.platform !== 'darwin') {
35
+ log('Skipping iOS framework download (not macOS).');
36
+ return true;
37
+ }
38
+
39
+ // Skip if explicitly disabled
40
+ if (process.env.SKIP_IOS_FRAMEWORK_DOWNLOAD === '1') {
41
+ log('Skipping iOS framework download (SKIP_IOS_FRAMEWORK_DOWNLOAD=1).');
42
+ return true;
43
+ }
44
+
45
+ // Skip if frameworks already exist
46
+ if (fs.existsSync(FRAMEWORKS_DIR) && fs.readdirSync(FRAMEWORKS_DIR).length > 0) {
47
+ log('iOS frameworks already present, skipping download.');
48
+ return true;
49
+ }
50
+
51
+ return false;
52
+ }
53
+
54
+ function downloadFile(url, destPath, maxRedirects = 5) {
55
+ return new Promise((resolve, reject) => {
56
+ if (maxRedirects <= 0) {
57
+ return reject(new Error('Too many redirects'));
58
+ }
59
+
60
+ const protocol = url.startsWith('https') ? https : require('http');
61
+
62
+ protocol.get(url, { headers: { 'User-Agent': 'react-native-litert-lm' } }, (res) => {
63
+ // Follow redirects
64
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
65
+ return downloadFile(res.headers.location, destPath, maxRedirects - 1)
66
+ .then(resolve)
67
+ .catch(reject);
68
+ }
69
+
70
+ if (res.statusCode !== 200) {
71
+ return reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
72
+ }
73
+
74
+ const file = fs.createWriteStream(destPath);
75
+ res.pipe(file);
76
+ file.on('finish', () => {
77
+ file.close();
78
+ resolve();
79
+ });
80
+ file.on('error', reject);
81
+ }).on('error', reject);
82
+ });
83
+ }
84
+
85
+ async function main() {
86
+ if (shouldSkip()) return;
87
+
88
+ const releaseUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${PACKAGE_VERSION}/${ASSET_NAME}`;
89
+
90
+ log(`Downloading iOS frameworks from: ${releaseUrl}`);
91
+
92
+ const tmpZip = path.join(PACKAGE_ROOT, '.ios-frameworks-tmp.zip');
93
+
94
+ try {
95
+ await downloadFile(releaseUrl, tmpZip);
96
+
97
+ // Extract
98
+ fs.mkdirSync(FRAMEWORKS_DIR, { recursive: true });
99
+ execSync(`unzip -o -q "${tmpZip}" -d "${FRAMEWORKS_DIR}"`, { stdio: 'inherit' });
100
+
101
+ // Cleanup
102
+ fs.unlinkSync(tmpZip);
103
+
104
+ log('iOS frameworks installed successfully.');
105
+ } catch (err) {
106
+ // Don't fail the install — iOS frameworks are optional (Android-only users)
107
+ log(`Warning: Could not download iOS frameworks: ${err.message}`);
108
+ log('iOS builds will not work until frameworks are available.');
109
+ log('Run: scripts/download-ios-frameworks.sh to download manually.');
110
+
111
+ // Cleanup partial download
112
+ try { fs.unlinkSync(tmpZip); } catch {}
113
+ }
114
+ }
115
+
116
+ main();