react-native-litert-lm 0.3.6 → 0.4.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 (87) hide show
  1. package/README.md +207 -158
  2. package/android/build.gradle +12 -0
  3. package/android/src/main/AndroidManifest.xml +5 -0
  4. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +316 -63
  5. package/android/src/main/java/dev/litert/litertlm/LiteRTLMPackage.kt +19 -2
  6. package/android/src/test/java/com/margelo/nitro/core/Promise.kt +46 -0
  7. package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +83 -0
  8. package/cpp/include/README.md +9 -11
  9. package/ios/HybridLiteRTLM.swift +1058 -0
  10. package/ios/Tests/HybridLiteRTLMTests.swift +67 -0
  11. package/lib/__mocks__/react-native-nitro-modules.d.ts +61 -0
  12. package/lib/__mocks__/react-native-nitro-modules.js +50 -0
  13. package/lib/__tests__/hooks.test.d.ts +1 -0
  14. package/lib/__tests__/hooks.test.js +124 -0
  15. package/lib/__tests__/memoryTracker.test.d.ts +1 -0
  16. package/lib/__tests__/memoryTracker.test.js +74 -0
  17. package/lib/__tests__/modelFactory.test.d.ts +1 -0
  18. package/lib/__tests__/modelFactory.test.js +52 -0
  19. package/lib/hooks.js +1 -1
  20. package/lib/index.d.ts +2 -4
  21. package/lib/index.js +12 -7
  22. package/lib/modelFactory.js +62 -63
  23. package/lib/specs/LiteRTLM.nitro.d.ts +71 -2
  24. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +62 -7
  25. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +3 -1
  26. package/nitrogen/generated/android/c++/JLLMConfig.hpp +40 -3
  27. package/nitrogen/generated/android/c++/JMultimodalPart.hpp +74 -0
  28. package/nitrogen/generated/android/c++/JPartType.hpp +61 -0
  29. package/nitrogen/generated/android/c++/JToolDefinition.hpp +65 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/GenerationStats.kt +23 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +10 -2
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +46 -3
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +19 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Message.kt +15 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MultimodalPart.kt +66 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/PartType.kt +24 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolDefinition.kt +61 -0
  38. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +57 -1
  39. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +414 -3
  40. package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Umbrella.hpp +41 -3
  41. package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +4 -6
  42. package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +10 -0
  43. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.cpp +11 -0
  44. package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +224 -0
  45. package/nitrogen/generated/ios/swift/Backend.swift +44 -0
  46. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  47. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  48. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  49. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  50. package/nitrogen/generated/ios/swift/Func_void_std__string_bool.swift +46 -0
  51. package/nitrogen/generated/ios/swift/GenerationStats.swift +54 -0
  52. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +69 -0
  53. package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +383 -0
  54. package/nitrogen/generated/ios/swift/LLMConfig.swift +203 -0
  55. package/nitrogen/generated/ios/swift/MemoryUsage.swift +44 -0
  56. package/nitrogen/generated/ios/swift/Message.swift +34 -0
  57. package/nitrogen/generated/ios/swift/MultimodalPart.swift +83 -0
  58. package/nitrogen/generated/ios/swift/PartType.swift +44 -0
  59. package/nitrogen/generated/ios/swift/Role.swift +44 -0
  60. package/nitrogen/generated/ios/swift/ToolDefinition.swift +39 -0
  61. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +2 -0
  62. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +7 -2
  63. package/nitrogen/generated/shared/c++/LLMConfig.hpp +22 -2
  64. package/nitrogen/generated/shared/c++/MultimodalPart.hpp +99 -0
  65. package/nitrogen/generated/shared/c++/PartType.hpp +80 -0
  66. package/nitrogen/generated/shared/c++/ToolDefinition.hpp +91 -0
  67. package/package.json +16 -8
  68. package/react-native-litert-lm.podspec +15 -19
  69. package/scripts/download-ios-frameworks.sh +14 -48
  70. package/scripts/postinstall.js +1 -2
  71. package/src/__mocks__/react-native-nitro-modules.ts +48 -0
  72. package/src/__tests__/hooks.test.ts +153 -0
  73. package/src/__tests__/memoryTracker.test.ts +87 -0
  74. package/src/__tests__/modelFactory.test.ts +68 -0
  75. package/src/hooks.ts +1 -1
  76. package/src/index.ts +12 -9
  77. package/src/modelFactory.ts +82 -80
  78. package/src/specs/LiteRTLM.nitro.ts +80 -2
  79. package/cpp/HybridLiteRTLM.cpp +0 -838
  80. package/cpp/HybridLiteRTLM.hpp +0 -167
  81. package/cpp/IOSDownloadHelper.h +0 -24
  82. package/ios/IOSDownloadHelper.mm +0 -129
  83. package/scripts/build-ios-engine.sh +0 -302
  84. package/scripts/stubs/cxx_bridge_stubs.cc +0 -224
  85. package/scripts/stubs/gemma_model_constraint_provider.cc +0 -46
  86. package/scripts/stubs/llguidance_stubs.c +0 -101
  87. package/src/templates.ts +0 -105
@@ -1,167 +0,0 @@
1
- //
2
- // HybridLiteRTLM.hpp
3
- // react-native-litert-lm
4
- //
5
- // High-performance LLM inference using LiteRT-LM.
6
- // Supports Gemma 4, Gemma 3n, and other .litertlm models.
7
- //
8
- // NOTE: This C++ implementation is used for iOS ONLY.
9
- // Android uses the Kotlin implementation in `android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt`.
10
- // Do not assume changes here will affect Android.
11
- //
12
-
13
- #pragma once
14
-
15
- #include "../nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp"
16
-
17
- // LiteRT-LM C API (iOS uses prebuilt framework with C ABI)
18
- #ifdef __APPLE__
19
- #include "include/litert_lm_engine.h"
20
- #endif
21
-
22
- // Memory usage headers
23
- #ifdef __APPLE__
24
- #include <mach/mach.h>
25
- #include <mach/mach_host.h>
26
- #endif
27
- #ifdef __ANDROID__
28
- #include <malloc.h>
29
- #include <fstream>
30
- #endif
31
-
32
- #include <string>
33
- #include <optional>
34
- #include <vector>
35
- #include <memory>
36
- #include <mutex>
37
- #include <functional>
38
- #include <atomic>
39
-
40
- namespace margelo::nitro::litertlm {
41
-
42
- /**
43
- * HybridLiteRTLM: React Native bindings for LiteRT-LM.
44
- *
45
- * On iOS, wraps the LiteRT-LM C API (engine.h) with prebuilt framework.
46
- * On Android, this class is unused — the Kotlin implementation is used instead.
47
- */
48
- class HybridLiteRTLM : public HybridLiteRTLMSpec {
49
- public:
50
- HybridLiteRTLM() : HybridObject(TAG) {}
51
-
52
- ~HybridLiteRTLM() override {
53
- close();
54
- }
55
-
56
- // Prevent copying
57
- HybridLiteRTLM(const HybridLiteRTLM&) = delete;
58
- HybridLiteRTLM& operator=(const HybridLiteRTLM&) = delete;
59
-
60
- public:
61
- // HybridLiteRTLMSpec interface implementation
62
-
63
- std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath,
64
- const std::optional<LLMConfig>& config) override;
65
-
66
- std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) override;
67
-
68
- std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message,
69
- const std::string& imagePath) override;
70
-
71
- std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url,
72
- const std::string& fileName,
73
- const std::optional<std::function<void(double)>>& onProgress) override;
74
-
75
- std::shared_ptr<Promise<void>> deleteModel(const std::string& fileName) override;
76
-
77
- std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message,
78
- const std::string& audioPath) override;
79
-
80
- void sendMessageAsync(
81
- const std::string& message,
82
- const std::function<void(const std::string&, bool)>& onToken
83
- ) override;
84
-
85
- std::vector<Message> getHistory() override;
86
-
87
- void resetConversation() override;
88
-
89
- bool isReady() override;
90
-
91
- GenerationStats getStats() override;
92
-
93
- MemoryUsage getMemoryUsage() override;
94
-
95
- void close() override;
96
-
97
- private:
98
- // LiteRT-LM C API resources (iOS only)
99
- #ifdef __APPLE__
100
- LiteRtLmEngine* engine_ = nullptr;
101
- LiteRtLmConversation* conversation_ = nullptr;
102
- LiteRtLmConversationConfig* conv_config_ = nullptr;
103
- LiteRtLmSessionConfig* session_config_ = nullptr;
104
- #endif
105
-
106
- // State
107
- bool isLoaded_ = false;
108
- std::vector<Message> history_;
109
- GenerationStats lastStats_{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
110
-
111
- // Thread safety
112
- mutable std::mutex mutex_;
113
-
114
- // Configuration - backend
115
- Backend backend_ = Backend::CPU;
116
-
117
- // System prompt / instruction
118
- std::string systemPrompt_;
119
-
120
- // Configuration - sampling parameters
121
- double temperature_ = 0.7;
122
- double topK_ = 40.0;
123
- double topP_ = 0.95;
124
- double maxTokens_ = 1024.0;
125
-
126
- // Helper to ensure model is loaded
127
- void ensureLoaded() const {
128
- if (!isLoaded_) {
129
- throw std::runtime_error("LiteRTLM: No model loaded. Call loadModel() first.");
130
- }
131
- }
132
-
133
- // Helper to create a new conversation from existing engine
134
- void createNewConversation();
135
-
136
- // JSON helpers for building C API message payloads
137
- static std::string escapeJson(const std::string& input);
138
- static std::string buildTextMessageJson(const std::string& text);
139
- static std::string buildImageMessageJson(const std::string& text, const std::string& imagePath);
140
- static std::string buildAudioMessageJson(const std::string& text, const std::string& audioPath);
141
- static std::string extractTextFromResponse(const std::string& jsonResponse);
142
-
143
- // Internal implementations (called from Promise lambdas)
144
- void loadModelInternal(const std::string& modelPath, const std::optional<LLMConfig>& config);
145
- std::string sendMessageInternal(const std::string& message);
146
- std::string sendMessageWithImageInternal(const std::string& message, const std::string& imagePath);
147
- std::string sendMessageWithAudioInternal(const std::string& message, const std::string& audioPath);
148
-
149
- // Streaming callback context (must be a plain struct for C function pointer)
150
- struct StreamContext {
151
- std::function<void(const std::string&, bool)> onToken;
152
- std::string fullResponse;
153
- std::vector<Message>* history;
154
- std::mutex* historyMutex;
155
- std::string userMessage;
156
- GenerationStats* lastStats;
157
- std::chrono::steady_clock::time_point startTime;
158
- int tokenCount;
159
- };
160
-
161
- // Static C callback for streaming (no captures needed)
162
- static void streamCallbackFn(void* callback_data, const char* chunk,
163
- bool is_final, const char* error_msg);
164
- };
165
-
166
- } // namespace margelo::nitro::litertlm
167
-
@@ -1,24 +0,0 @@
1
- #pragma once
2
-
3
- #include <string>
4
- #include <functional>
5
- #include <optional>
6
-
7
- namespace litert_lm {
8
-
9
- /**
10
- * Download a file from a URL to the app's Caches/litert_models directory.
11
- * Uses NSURLSession for efficient, resumable downloads.
12
- *
13
- * @param url HTTPS URL to download from
14
- * @param fileName Destination filename
15
- * @param onProgress Optional progress callback (0.0 to 1.0)
16
- * @return Absolute path to the downloaded file
17
- * @throws std::runtime_error on download failure
18
- */
19
- std::string downloadModelFile(
20
- const std::string& url,
21
- const std::string& fileName,
22
- const std::optional<std::function<void(double)>>& onProgress);
23
-
24
- } // namespace litert_lm
@@ -1,129 +0,0 @@
1
- #import <Foundation/Foundation.h>
2
- #include "../cpp/IOSDownloadHelper.h"
3
- #include <stdexcept>
4
-
5
- namespace litert_lm {
6
-
7
- std::string downloadModelFile(
8
- const std::string& url,
9
- const std::string& fileName,
10
- const std::optional<std::function<void(double)>>& onProgress) {
11
- @autoreleasepool {
12
- NSString* nsUrl = [NSString stringWithUTF8String:url.c_str()];
13
- NSString* nsFileName = [NSString stringWithUTF8String:fileName.c_str()];
14
-
15
- // Use Caches directory — survives app relaunch but can be
16
- // reclaimed by the system under storage pressure.
17
- NSString* cachesDir = NSSearchPathForDirectoriesInDomains(
18
- NSCachesDirectory, NSUserDomainMask, YES).firstObject;
19
- NSString* modelsDir = [cachesDir stringByAppendingPathComponent:@"litert_models"];
20
-
21
- // Create models subdirectory
22
- NSFileManager* fm = [NSFileManager defaultManager];
23
- if (![fm fileExistsAtPath:modelsDir]) {
24
- [fm createDirectoryAtPath:modelsDir
25
- withIntermediateDirectories:YES
26
- attributes:nil
27
- error:nil];
28
- }
29
-
30
- NSString* destPath = [modelsDir stringByAppendingPathComponent:nsFileName];
31
-
32
- // If the file already exists and has content, skip download
33
- if ([fm fileExistsAtPath:destPath]) {
34
- NSDictionary* attrs = [fm attributesOfItemAtPath:destPath error:nil];
35
- unsigned long long fileSize = [attrs fileSize];
36
- if (fileSize > 0) {
37
- NSLog(@"[LiteRTLM] Model already cached at %@ (%llu bytes), skipping download",
38
- destPath, fileSize);
39
- if (onProgress.has_value()) {
40
- onProgress.value()(1.0);
41
- }
42
- return std::string([destPath UTF8String]);
43
- }
44
- }
45
-
46
- NSLog(@"[LiteRTLM] Downloading model from %@ to %@", nsUrl, destPath);
47
-
48
- NSURL* requestUrl = [NSURL URLWithString:nsUrl];
49
- if (!requestUrl) {
50
- throw std::runtime_error("Invalid download URL: " + url);
51
- }
52
-
53
- // Synchronous download using NSURLSession on this background thread.
54
- __block NSError* downloadError = nil;
55
-
56
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
57
-
58
- NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
59
- config.timeoutIntervalForRequest = 30;
60
- config.timeoutIntervalForResource = 3600; // 1 hour for large models
61
-
62
- NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
63
- NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl];
64
- [request setHTTPMethod:@"GET"];
65
-
66
- // Use downloadTask for proper progress tracking and disk-efficient downloads
67
- NSURLSessionDownloadTask* task = [session downloadTaskWithRequest:request
68
- completionHandler:^(NSURL* location, NSURLResponse* response, NSError* error) {
69
- if (error) {
70
- downloadError = error;
71
- dispatch_semaphore_signal(semaphore);
72
- return;
73
- }
74
-
75
- // Check HTTP status
76
- if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
77
- NSInteger statusCode = [(NSHTTPURLResponse*)response statusCode];
78
- if (statusCode >= 400) {
79
- downloadError = [NSError errorWithDomain:@"LiteRTLM"
80
- code:statusCode
81
- userInfo:@{NSLocalizedDescriptionKey:
82
- [NSString stringWithFormat:@"HTTP %ld", (long)statusCode]}];
83
- dispatch_semaphore_signal(semaphore);
84
- return;
85
- }
86
- }
87
-
88
- // Move downloaded file to destination
89
- NSError* moveError = nil;
90
- [fm removeItemAtPath:destPath error:nil]; // Remove any partial file
91
- if (![fm moveItemAtURL:location
92
- toURL:[NSURL fileURLWithPath:destPath]
93
- error:&moveError]) {
94
- downloadError = moveError;
95
- }
96
-
97
- dispatch_semaphore_signal(semaphore);
98
- }];
99
-
100
- [task resume];
101
-
102
- // Poll for progress while waiting for completion
103
- while (dispatch_semaphore_wait(semaphore,
104
- dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC)) != 0) {
105
- if (onProgress.has_value() && task.countOfBytesExpectedToReceive > 0) {
106
- double progress = (double)task.countOfBytesReceived /
107
- (double)task.countOfBytesExpectedToReceive;
108
- onProgress.value()(progress);
109
- }
110
- }
111
-
112
- [session finishTasksAndInvalidate];
113
-
114
- if (downloadError) {
115
- throw std::runtime_error("Download failed: " +
116
- std::string([[downloadError localizedDescription] UTF8String]));
117
- }
118
-
119
- // Final progress callback
120
- if (onProgress.has_value()) {
121
- onProgress.value()(1.0);
122
- }
123
-
124
- NSLog(@"[LiteRTLM] Model downloaded successfully to %@", destPath);
125
- return std::string([destPath UTF8String]);
126
- }
127
- }
128
-
129
- } // namespace litert_lm
@@ -1,302 +0,0 @@
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_REPO="https://github.com/google-ai-edge/LiteRT-LM.git"
20
- FRAMEWORK_NAME="LiteRTLM"
21
-
22
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
23
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
24
- LITERT_LM_VERSION="$(node -e "console.log(require('$PROJECT_ROOT/package.json').litertLm.iosGitTag)")"
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.10.2</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