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.
- package/README.md +331 -150
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +140 -37
- package/app.plugin.js +33 -0
- package/cpp/HybridLiteRTLM.cpp +577 -378
- package/cpp/HybridLiteRTLM.hpp +66 -23
- package/cpp/IOSDownloadHelper.h +24 -0
- package/cpp/cpp-adapter.cpp +10 -2
- package/cpp/include/litert_lm_engine.h +502 -0
- package/ios/IOSDownloadHelper.mm +129 -0
- package/ios/LiteRTLMAutolinking.mm +30 -0
- package/lib/hooks.d.ts +33 -3
- package/lib/hooks.js +54 -23
- package/lib/index.d.ts +4 -1
- package/lib/index.js +6 -6
- package/lib/memoryTracker.d.ts +128 -0
- package/lib/memoryTracker.js +155 -0
- package/lib/modelFactory.d.ts +21 -2
- package/lib/modelFactory.js +78 -11
- package/lib/specs/LiteRTLM.nitro.d.ts +19 -0
- package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +28 -18
- package/nitrogen/generated/android/LiteRTLMOnLoad.hpp +13 -4
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +39 -36
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +20 -22
- package/nitrogen/generated/android/c++/JMemoryUsage.hpp +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +19 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +47 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/MemoryUsage.hpp +95 -0
- package/package.json +12 -5
- package/react-native-litert-lm.podspec +20 -7
- package/scripts/build-ios-engine.sh +283 -0
- package/scripts/download-ios-frameworks.sh +72 -0
- package/scripts/postinstall.js +116 -0
- package/scripts/stubs/cxx_bridge_stubs.cc +224 -0
- package/scripts/stubs/gemma_model_constraint_provider.cc +46 -0
- package/scripts/stubs/llguidance_stubs.c +101 -0
- package/src/hooks.ts +107 -41
- package/src/index.ts +13 -6
- package/src/memoryTracker.ts +268 -0
- package/src/modelFactory.ts +107 -11
- 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
48
|
-
#
|
|
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();
|