-
Notifications
You must be signed in to change notification settings - Fork 5
feat: enable iOS TTS via ONNX Runtime #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
📝 WalkthroughWalkthroughThe PR adds iOS support for ONNX Runtime and TTS functionality. Changes include CI workflow updates for caching and building the iOS xcframework, Cargo configuration for iOS targets, a build script automating ONNX Runtime compilation for iOS, backend Rust code enabling TTS on iOS with AppHandle-based path resolution, and frontend platform detection updates. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
Deploying maple with
|
| Latest commit: |
afd965f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://139c539e.maple-ca8.pages.dev |
| Branch Preview URL: | https://ios-tts-v2.maple-ca8.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greptile Overview
Greptile Summary
Summary
This PR enables iOS TTS support for the Maple application using ONNX Runtime 1.22.2. The implementation parallels the existing desktop TTS functionality while properly handling iOS-specific constraints.
Key Changes:
- Backend (Rust/Tauri): Added iOS support for TTS via conditional compilation (
#[cfg(any(desktop, target_os = "ios"))]) - Model Management: Changed from using the
dirscrate to Tauri'sapp_local_data_dir()for platform-independent model storage - Dependencies: Added iOS-specific Cargo dependencies with modified ORT configuration (disabling unnecessary features like rayon)
- Build System: Created ONNX Runtime iOS xcframework build script and GitHub Actions caching
- Frontend: Extended TTS context to support iOS platform detection alongside desktop
Architecture:
- Desktop (macOS/Linux/Windows): Uses ORT with full features + rayon for parallel ndarray operations
- iOS: Uses ORT with limited features (std, ndarray) - rayon disabled for iOS compatibility
- Android: No TTS support (remains excluded)
- GitHub Actions: Caches 264 MB ONNX Runtime xcframework to avoid expensive rebuilds
Strengths:
- Clean conditional compilation separating iOS from desktop implementation
- Proper platform detection using existing platform utility functions
- Comprehensive model validation with SHA256 checksums and size verification
- Generous 90-minute timeout for ONNX Runtime builds in CI/CD
- Correct build script validation of required tools before compilation
Concerns Identified:
- Shallow clone issue: The build script uses
--depth 1but later tries to checkout specific tags, which could fail - Path resolution: .cargo/config.toml uses relative paths that depend on working directory being
frontend - Redundant validation: Download logic performs duplicate size checks against HTTP content-length and expected_size
- Feature compatibility: ndarray on iOS lacks rayon feature compared to desktop - potential build conflict with ORT's ndarray requirement needs verification
- AudioContext on iOS: Web Audio API creation may have restrictions on iOS Safari/Tauri requiring user interaction (should be satisfied by button click)
- Platform detection timing: TTSContext calls platform functions at render time without guaranteeing waitForPlatform() was called first
- Cache validation: GitHub Actions doesn't verify xcframework integrity after cache restore
Confidence Score: 3/5
- This PR is moderately safe to merge but requires verification of iOS-specific build behavior and proper testing on actual iOS devices before production deployment.
- The PR implements a well-structured iOS TTS feature with proper conditional compilation and platform detection. However, several concerns lower the confidence score: (1) the shallow git clone with subsequent tag checkout could fail unpredictably, (2) path configuration depends on working directory conventions that aren't enforced, (3) feature flag compatibility between ORT's ndarray requirement and iOS's rayon-less ndarray needs verification, (4) platform detection relies on proper initialization sequencing, and (5) AudioContext behavior on iOS Safari needs real-device testing. The code quality is generally good with proper error handling and validation, but iOS-specific runtime behavior requires actual device testing which cannot be covered by static analysis.
- frontend/src-tauri/scripts/build-ios-onnxruntime.sh (git clone/checkout logic), frontend/.cargo/config.toml (path resolution), frontend/src-tauri/Cargo.toml (feature flag verification on iOS), frontend/src/services/tts/TTSContext.tsx (platform detection timing), .github/workflows/mobile-build.yml and testflight-on-comment.yml (cache validation)
Important Files Changed
File Analysis
| Filename | Score | Overview |
|---|---|---|
| frontend/src-tauri/Cargo.toml | 3/5 | iOS and desktop TTS dependencies are configured. Desktop uses ORT with full features and ndarray with rayon, while iOS uses ORT with limited features (std, ndarray only) and ndarray without rayon. The rayon dependency may be problematic on iOS platforms but is correctly omitted for compatibility. |
| frontend/src-tauri/src/tts.rs | 2/5 | TTS implementation for iOS and desktop. Changed get_tts_models_dir() to accept AppHandle parameter for app-local data directory access instead of using dirs crate. Uses ORT with ONNX models for synthesis. Found potential issue: the function signature changed but verification needed for all call sites. |
| frontend/.cargo/config.toml | 4/5 | iOS ONNX Runtime xcframework configuration properly set up with ORT_LIB_LOCATION for different iOS targets (device arm64, simulator arm64, simulator x86_64). Paths correctly use relative addressing relative to the .cargo/config.toml location in frontend/. |
| frontend/src-tauri/scripts/build-ios-onnxruntime.sh | 3/5 | Bash script builds ONNX Runtime 1.22.2 xcframework for iOS with device (arm64) and simulator (arm64, x86_64) architectures. Caches result and creates universal simulator binary with lipo. Properly validates tool availability and error handling. |
| frontend/src/services/tts/TTSContext.tsx | 3/5 | Frontend TTS context updated to support iOS in addition to desktop. Added iOS platform detection via isTauri() && isIOS(). Uses Web Audio API for audio playback. Includes proper event listener cleanup and reference management. |
| .github/workflows/mobile-build.yml | 3/5 | GitHub Actions workflow for iOS builds now includes ONNX Runtime xcframework caching and building. Caches based on script hash. Build step has 90-minute timeout which is reasonable for ORT compilation. Workflow runs on all pushes and PRs. |
Sequence Diagram
sequenceDiagram
participant User as User
participant Frontend as Frontend (React)
participant Tauri as Tauri iOS Backend
participant FileSystem as App Local Storage
participant ORT as ONNX Runtime
participant Speaker as Audio Output
User->>Frontend: Click "Speak" button
Frontend->>Tauri: invoke("tts_synthesize", {text})
alt Models Not Loaded
Tauri->>FileSystem: Check model files
FileSystem-->>Tauri: Models exist?
alt Models Missing
Tauri->>Frontend: emit("models_not_downloaded")
Frontend->>Tauri: invoke("tts_download_models")
loop For each model file
Tauri->>FileSystem: Download & validate (SHA256)
end
end
Tauri->>Tauri: load_tts_engine()
Tauri->>ORT: Load ONNX sessions (4 models)
end
Tauri->>Tauri: preprocess_text()
Tauri->>Tauri: chunk_text()
loop For each text chunk
Tauri->>ORT: text_encoder.run()
Tauri->>ORT: duration_predictor.run()
Tauri->>ORT: vector_estimator.run() (diffusion loop)
Tauri->>ORT: vocoder.run()
end
Tauri->>Tauri: wav_to_base64()
Tauri-->>Frontend: TTSSynthesizeResponse {audio_base64, sample_rate, duration}
Frontend->>Frontend: base64ToBlob()
Frontend->>Frontend: Create AudioContext
Frontend->>Frontend: Decode audio data
Frontend->>Speaker: source.start()
Speaker-->>User: Audio plays
| ort = { version = "2.0.0-rc.10", default-features = false, features = ["std", "ndarray"] } | ||
| ndarray = { version = "0.16" } | ||
| rand = "0.8" | ||
| rand_distr = "0.4" | ||
| hound = "3.5" | ||
| unicode-normalization = "0.1" | ||
| regex = "1.10" | ||
| reqwest = { version = "0.12", features = ["stream"] } | ||
| futures-util = "0.3" | ||
| sha2 = "0.10" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iOS ORT configuration specifies features = ["std", "ndarray"] but ndarray is declared without the "rayon" feature. While this is intentional (to avoid parallel computation dependencies on iOS), there could be a feature conflict if ORT's ndarray feature implicitly expects rayon. Recommend verifying that this configuration builds successfully on actual iOS devices and doesn't cause linking issues with the xcframework.
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src-tauri/Cargo.toml
Line: 57:66
Comment:
The iOS ORT configuration specifies `features = ["std", "ndarray"]` but ndarray is declared without the "rayon" feature. While this is intentional (to avoid parallel computation dependencies on iOS), there could be a feature conflict if ORT's ndarray feature implicitly expects rayon. Recommend verifying that this configuration builds successfully on actual iOS devices and doesn't cause linking issues with the xcframework.
How can I resolve this? If you propose a fix, please make it concise.| git clone --depth 1 --branch "v${ORT_VERSION}" --recursive \ | ||
| https://github.com/microsoft/onnxruntime.git "${SRC_DIR}" | ||
| else | ||
| cd "${SRC_DIR}" | ||
| git fetch --tags | ||
| git checkout "v${ORT_VERSION}" | ||
| git submodule update --init --recursive | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script clones ONNX Runtime with --depth 1 --recursive which creates a shallow clone. However, when checking out a specific version in the else branch (line 37), it uses git checkout "v${ORT_VERSION}" on an already-cloned shallow repository. This could fail because the shallow clone might not have all the necessary refs. Consider removing --depth 1 for a full clone to ensure all tags are available, or use git fetch --tags before checkout (which is already done on line 36).
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src-tauri/scripts/build-ios-onnxruntime.sh
Line: 32:39
Comment:
The script clones ONNX Runtime with `--depth 1 --recursive` which creates a shallow clone. However, when checking out a specific version in the else branch (line 37), it uses `git checkout "v${ORT_VERSION}"` on an already-cloned shallow repository. This could fail because the shallow clone might not have all the necessary refs. Consider removing `--depth 1` for a full clone to ensure all tags are available, or use `git fetch --tags` before checkout (which is already done on line 36).
How can I resolve this? If you propose a fix, please make it concise.| - name: Cache ONNX Runtime iOS xcframework | ||
| uses: actions/cache@v4 | ||
| id: cache-onnxruntime | ||
| with: | ||
| path: | | ||
| frontend/src-tauri/onnxruntime-ios | ||
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | ||
| restore-keys: | | ||
| onnxruntime-ios-xcframework-1.22.2- | ||
| - name: Build ONNX Runtime xcframework (if needed) | ||
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | ||
| working-directory: ./frontend/src-tauri | ||
| run: | | ||
| chmod +x scripts/build-ios-onnxruntime.sh | ||
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | ||
| timeout-minutes: 90 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ONNX Runtime build is cached using hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') as the cache key. While the 90-minute timeout is generous, if the build script changes but produces the same binary output, the cache won't invalidate. Consider also including the ORT version number in the cache key, or add a way to force cache invalidation if needed. Additionally, the build script should verify that all required dependencies (git, python3, cmake, xcodebuild, lipo) are available before starting a 90-minute build.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/mobile-build.yml
Line: 61:77
Comment:
The ONNX Runtime build is cached using `hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh')` as the cache key. While the 90-minute timeout is generous, if the build script changes but produces the same binary output, the cache won't invalidate. Consider also including the ORT version number in the cache key, or add a way to force cache invalidation if needed. Additionally, the build script should verify that all required dependencies (git, python3, cmake, xcodebuild, lipo) are available before starting a 90-minute build.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| [target.aarch64-apple-ios.env] | ||
| ORT_LIB_LOCATION = { value = "src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64", relative = true } | ||
|
|
||
| [target.aarch64-apple-ios-sim.env] | ||
| ORT_LIB_LOCATION = { value = "src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64_x86_64-simulator", relative = true } | ||
|
|
||
| [target.x86_64-apple-ios.env] | ||
| ORT_LIB_LOCATION = { value = "src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64_x86_64-simulator", relative = true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ORT_LIB_LOCATION paths use forward slashes (/) which should work on macOS, but the relative = true means these paths are relative to the .cargo/config.toml location. During iOS builds from GitHub Actions, the working directory is ./frontend, so the relative path src-tauri/onnxruntime-ios/... should resolve correctly. However, if developers run builds from different directories (e.g., from the root), the paths will fail. Consider documenting that builds must be run with frontend as the working directory, or use absolute paths computed at build time instead.
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/.cargo/config.toml
Line: 6:13
Comment:
The ORT_LIB_LOCATION paths use forward slashes (/) which should work on macOS, but the `relative = true` means these paths are relative to the .cargo/config.toml location. During iOS builds from GitHub Actions, the working directory is `./frontend`, so the relative path `src-tauri/onnxruntime-ios/...` should resolve correctly. However, if developers run builds from different directories (e.g., from the root), the paths will fail. Consider documenting that builds must be run with `frontend` as the working directory, or use absolute paths computed at build time instead.
How can I resolve this? If you propose a fix, please make it concise.| // TTS is available on desktop and iOS (not Android) | ||
| #[cfg(any(desktop, target_os = "ios"))] | ||
| mod tts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TTS module is conditionally compiled with #[cfg(any(desktop, target_os = "ios"))]. This correctly enables TTS on desktop and iOS while excluding Android. However, the module declaration and the actual TTS state management should be verified to ensure there's no accidental compilation of TTS code on Android due to feature flag interactions with other dependencies. The conditional compilation looks correct, but document that TTS must remain iOS/desktop-only.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src-tauri/src/lib.rs
Line: 6:8
Comment:
The TTS module is conditionally compiled with `#[cfg(any(desktop, target_os = "ios"))]`. This correctly enables TTS on desktop and iOS while excluding Android. However, the module declaration and the actual TTS state management should be verified to ensure there's no accidental compilation of TTS code on Android due to feature flag interactions with other dependencies. The conditional compilation looks correct, but document that TTS must remain iOS/desktop-only.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| - name: Cache ONNX Runtime iOS xcframework | ||
| uses: actions/cache@v4 | ||
| id: cache-onnxruntime | ||
| with: | ||
| path: | | ||
| frontend/src-tauri/onnxruntime-ios | ||
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | ||
| restore-keys: | | ||
| onnxruntime-ios-xcframework-1.22.2- | ||
| - name: Build ONNX Runtime xcframework (if needed) | ||
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | ||
| working-directory: ./frontend/src-tauri | ||
| run: | | ||
| chmod +x scripts/build-ios-onnxruntime.sh | ||
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | ||
| timeout-minutes: 90 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ONNX Runtime caching in the TestFlight workflow mirrors the mobile-build.yml, but the build step has no error handling if the xcframework build fails. A 90-minute build timeout could fail without proper logging. Additionally, if the cache is hit, there's no verification that the xcframework files actually exist and are valid. Consider adding a validation step after cache restoration to ensure the xcframework is present before proceeding to the Tauri build.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/testflight-on-comment.yml
Line: 119:135
Comment:
The ONNX Runtime caching in the TestFlight workflow mirrors the mobile-build.yml, but the build step has no error handling if the xcframework build fails. A 90-minute build timeout could fail without proper logging. Additionally, if the cache is hit, there's no verification that the xcframework files actually exist and are valid. Consider adding a validation step after cache restoration to ensure the xcframework is present before proceeding to the Tauri build.
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (3)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Prompt To Fix With AIThis is a comment left during a code review.
Path: frontend/src-tauri/src/tts.rs
Line: 847:863
Comment:
The download validation logic performs two separate size checks: first against HTTP `content_length` (lines 847-854), then again against the hardcoded `expected_size` (lines 857-862). The first check is redundant since the second check against `expected_size` is the authoritative validation. The first check can be removed or consolidated to reduce confusion. Additionally, if both checks pass but the second check fails, the error message format differs, which could confuse users.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix With AIThis is a comment left during a code review.
Path: frontend/src/services/tts/TTSContext.tsx
Line: 235:246
Comment:
Creating a new AudioContext on each speak call may fail or behave unexpectedly on iOS due to browser security restrictions. On iOS Safari and Tauri iOS, AudioContext requires user interaction (tap/click) to allow audio playback. Since TTS synthesis is invoked from a button click, this should work, but there's no try-catch around the AudioContext constructor. If it fails, the entire speak operation fails silently. Additionally, ensure the AudioContext is compatible with Tauri's iOS environment and that it doesn't require additional permissions.
How can I resolve this? If you propose a fix, please make it concise.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Prompt To Fix With AIThis is a comment left during a code review.
Path: frontend/src/services/tts/TTSContext.tsx
Line: 166:187
Comment:
The `stop()` function wraps `sourceNodeRef.current.stop()` in a try-catch (line 173), which is good for handling already-stopped sources. However, the same error handling pattern is duplicated in the cleanup effect (line 282). Consider extracting this into a helper function to avoid repetition and ensure consistent error handling across all cleanup paths.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src-tauri/src/lib.rs (1)
291-311: Misplaced comment and code duplication.Issues:
- The comment on line 301 states "iOS does not support runtime registration" but this is in the Android block
- The deep link setup code (lines 298-310) is duplicated from the iOS block above (lines 276-285)
♻️ Refactor to reduce duplication
Consider extracting the common mobile deep link setup into a helper function or using a shared setup block for both iOS and Android:
+ // Shared mobile deep link setup + #[cfg(not(desktop))] + fn setup_mobile_deep_links(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { + let app_handle = app.handle().clone(); + + // Note: iOS and Android do not support runtime registration of deep link schemes + // but the handler for incoming URLs still works + app.deep_link().on_open_url(move |event| { + if let Some(url) = event.urls().first() { + handle_deep_link_event(url.as_ref(), &app_handle); + } + }); + + Ok(()) + }Then in both iOS and Android setup blocks:
.setup(|app| { - // Set up the deep link handler for mobile - let app_handle = app.handle().clone(); - - // Register deep link handler - note that iOS does not support runtime registration - // but the handler for incoming URLs still works - app.deep_link().on_open_url(move |event| { - if let Some(url) = event.urls().first() { - handle_deep_link_event(url.as_ref(), &app_handle); - } - }); - + setup_mobile_deep_links(app)?; Ok(()) })frontend/src-tauri/src/tts.rs (1)
757-888: Avoid silently keeping stale/partial files when re-downloading.You delete the final file only for “zero-byte or unreadable”; but if the file exists with wrong size, you currently remove it (good) yet partial
.partfiles are always removed (good). The only nit:app.emit(...)failures are ignored; on mobile that can hide UX issues—consider logging emit errors at debug level.
🤖 Fix all issues with AI agents
In @.github/workflows/mobile-build.yml:
- Around line 61-78: The cache/restore logic can restore a stale
onnxruntime.xcframework so when cache miss triggers the build step the script
may early-exit and leave stale artifacts; modify the job so that before running
scripts/build-ios-onnxruntime.sh (when steps.cache-onnxruntime.outputs.cache-hit
!= 'true') you delete frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework
if it exists (e.g., rm -rf
frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework) to force a clean
rebuild, and also strengthen the cache key by including IOS_DEPLOYMENT_TARGET
and the Xcode version (e.g., include ${IOS_DEPLOYMENT_TARGET} and the workflow
Xcode version variable such as matrix.xcode or runner.os/XCODE_VERSION) in the
key/restore-keys so incompatible builds don’t share the same cache.
In @.github/workflows/testflight-on-comment.yml:
- Around line 119-136: The workflow can trigger the build step even when a stale
xcframework folder was restored via a prefix-match restore-key; to fix, add a
small preparatory step (e.g., step id: check-onnx) that checks for the existence
of frontend/src-tauri/onnxruntime-ios and sets an output (exists=true/false),
then change the Build ONNX Runtime xcframework step condition to only run when
neither cache hit nor existing dir are present (if:
steps.cache-onnxruntime.outputs.cache-hit != 'true' &&
steps.check-onnx.outputs.exists != 'true'), or alternatively modify
scripts/build-ios-onnxruntime.sh to refuse early exit when cache-hit != 'true'
and instead force a rebuild; reference the actions/cache step id
cache-onnxruntime and the script scripts/build-ios-onnxruntime.sh to locate the
change.
In @frontend/src-tauri/scripts/build-ios-onnxruntime.sh:
- Around line 71-81: The current DEVICE_LIB, SIM_ARM64_LIB and SIM_X64_LIB find
commands can return incorrect files when multiple libonnxruntime.a exist; update
the find invocations that set DEVICE_LIB, SIM_ARM64_LIB, and SIM_X64_LIB to
narrow the search to the expected output subdirectory (for example prefer paths
matching */Release/*/libonnxruntime.a or use -path or -maxdepth to target the
build/Release output) so the script consistently picks the correct release
static library for each of DEVICE_BUILD_DIR, SIM_ARM64_BUILD_DIR and
SIM_X64_BUILD_DIR; keep the existing error check logic but ensure the find
patterns only match the intended Release output.
- Around line 94-100: After successfully creating the XC framework, write the
built-version marker (${ORT_VERSION}) to the version file so the version-aware
early-exit works correctly; specifically, only write ${ORT_VERSION} after the
xcodebuild -create-xcframework step completes without error (the block using
XC_DIR, DEVICE_LIB, SIM_UNIVERSAL_LIB and HEADERS_DIR), e.g., check xcodebuild
exit status and then echo "${ORT_VERSION}" > "${XC_DIR}/.built_version" (or the
existing version file name) and log success; do not write the marker before or
regardless of xcodebuild success.
🧹 Nitpick comments (1)
frontend/src-tauri/scripts/build-ios-onnxruntime.sh (1)
31-39: Make the source checkout reproducible/clean before building.When
${SRC_DIR}exists, consider ensuring a clean tree before building (ORT builds can be sensitive to leftover artifacts). At minimum:git reset --hardafter checkout; optionallygit clean -fdx(if you’re OK blowing away local changes).
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
.github/workflows/mobile-build.yml.github/workflows/testflight-on-comment.ymlfrontend/.cargo/config.tomlfrontend/src-tauri/.gitignorefrontend/src-tauri/Cargo.tomlfrontend/src-tauri/build.rsfrontend/src-tauri/scripts/build-ios-onnxruntime.shfrontend/src-tauri/src/lib.rsfrontend/src-tauri/src/tts.rsfrontend/src/services/tts/TTSContext.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use path aliases (@/*maps to./src/*) for imports in TypeScript/React files
Use 2-space indentation, double quotes, and enforce 100-character line limit in TypeScript/React code
Maintain strict TypeScript and avoid usinganytype
Use PascalCase for component names and camelCase for variables and function names
Use functional components with React hooks instead of class components
Use React context for global state management and TanStack Query for server state management
Runjust format,just lint, andjust buildafter making TypeScript/React changes to ensure code quality and compilation
Files:
frontend/src/services/tts/TTSContext.tsx
🧠 Learnings (4)
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to src-tauri/**/*.rs : Follow standard Rust conventions and rustfmt defaults for code formatting
Applied to files:
frontend/src-tauri/build.rs
📚 Learning: 2026-01-08T17:09:40.432Z
Learnt from: AnthonyRonning
Repo: OpenSecretCloud/Maple PR: 372
File: frontend/src-tauri/Cargo.toml:9-9
Timestamp: 2026-01-08T17:09:40.432Z
Learning: The OpenSecretCloud/Maple repository has comprehensive GitHub Actions workflows (e.g., desktop-build.yml) that automatically validate compilation and testing, so manual compilation verification reminders are unnecessary when reviewing version bumps or dependency updates.
Applied to files:
.github/workflows/testflight-on-comment.yml
📚 Learning: 2025-03-25T19:50:07.925Z
Learnt from: AnthonyRonning
Repo: OpenSecretCloud/Maple PR: 23
File: frontend/src-tauri/src/lib.rs:14-16
Timestamp: 2025-03-25T19:50:07.925Z
Learning: In Tauri applications, updater security configurations (including endpoints, public key for verification, and dialog settings) should be defined in the tauri.conf.json file rather than duplicated in the code. When initialized with tauri_plugin_updater::Builder::new().build(), the plugin automatically reads and uses these settings from the configuration file.
Applied to files:
frontend/src-tauri/src/lib.rs
📚 Learning: 2025-03-25T19:50:07.925Z
Learnt from: AnthonyRonning
Repo: OpenSecretCloud/Maple PR: 23
File: frontend/src-tauri/src/lib.rs:14-16
Timestamp: 2025-03-25T19:50:07.925Z
Learning: In Tauri applications, the updater configuration (including security settings like server URL and public key) should be defined in tauri.conf.json rather than hardcoded in the application code. The code only needs to initialize the updater plugin with tauri_plugin_updater::Builder::new().build(), which will automatically use the settings from the configuration file.
Applied to files:
frontend/src-tauri/src/lib.rs
🧬 Code graph analysis (1)
frontend/src-tauri/src/lib.rs (2)
frontend/src-tauri/src/tts.rs (8)
new(174-176)new(451-456)tts_get_status(723-746)tts_download_models(757-888)tts_load_models(891-912)tts_synthesize(922-964)tts_unload_models(967-973)tts_delete_models(976-995)frontend/src-tauri/src/pdf_extractor.rs (1)
extract_document_content(18-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: build-linux
- GitHub Check: build-macos (universal-apple-darwin)
- GitHub Check: build-ios
- GitHub Check: build-android
- GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
frontend/src-tauri/.gitignore (1)
5-8: LGTM!The gitignore entries correctly exclude ONNX Runtime iOS build artifacts from version control, aligning with the new iOS build infrastructure.
frontend/src/services/tts/TTSContext.tsx (2)
10-10: LGTM!The import correctly adds
isIOSfor platform detection.
63-64: LGTM!The expanded environment check correctly enables TTS for iOS alongside desktop. The logic
isTauriDesktop() || (isTauri() && isIOS())properly excludes Android while including iOS, aligning with the backend TTS module availability.frontend/src-tauri/build.rs (1)
2-6: LGTM!The iOS-specific Accelerate framework linking is correctly implemented. The build script properly detects iOS targets and emits the necessary linker directive for ONNX Runtime's accelerated math operations.
frontend/src-tauri/src/lib.rs (1)
6-8: LGTM!The conditional compilation correctly enables the TTS module for desktop and iOS platforms, excluding Android as intended.
frontend/src-tauri/Cargo.toml (1)
55-66: Review iOS ort configuration for missing download-binaries feature.The
ort = "2.0.0-rc.10"version is appropriate for iOS—it was explicitly designed with cross-platform improvements and alternative backends (ort-tract, ort-candle) to support iOS builds. Disabling default features on iOS is the correct approach to control dependencies.However, the iOS configuration differs from desktop in an important way: desktop uses all defaults (
ort = "2.0.0-rc.10"), which includesdownload-binariesfor prebuilt ORT artifacts, while iOS explicitly disables defaults and only enables["std", "ndarray"]. Since linking native ONNX Runtime binaries on iOS is complex, confirm whetherdownload-binariesshould be explicitly enabled in the iOS configuration:ort = { version = "2.0.0-rc.10", default-features = false, features = ["std", "ndarray", "download-binaries"] }frontend/src-tauri/src/tts.rs (3)
17-17: ConfirmAppHandle/Manager/Emitterusage is valid for tauri 2.9.2 on iOS.This relies on Tauri’s command injection of
AppHandleand theManager::path()API on mobile; please confirm this compiles/behaves as expected on iOS with tauri 2.9.2.
723-746: Good: status now uses app-local models dir; consider treating missing dir as “not downloaded” without error.Current behavior is fine (metadata failures =>
false), just ensureget_tts_models_dirfailures are truly exceptional on iOS (e.g., permissions/entitlements).
891-912: LGTM:AppHandlepropagation keeps iOS paths consistent across load/delete.This aligns load/delete with the new app-local storage strategy.
Also applies to: 976-995
frontend/.cargo/config.toml (1)
1-13: VerifyORT_LIB_LOCATIONsemantics and the xcframework slice paths match whatort-sysexpects.Two things to confirm:
- Cargo’s
relative = trueresolves relative to the intended base (and works the same in CI/local).src-tauri/onnxruntime-ios/onnxruntime.xcframework/{ios-arm64,ios-arm64_x86_64-simulator}actually contains the library file name/layout thatort-sysuses for linking.If either is off, builds will fail only on iOS.
frontend/src-tauri/scripts/build-ios-onnxruntime.sh (1)
43-59: Verify ONNX Runtime’sbuild.shflags for iOS are correct for v1.22.2.This depends on upstream ORT’s build interface (
--use_xcode,--ios,--apple_sysroot, and especially--osx_archfor iOS). Please confirm these are the intended flags for ORT v1.22.2 iOS builds.
| - name: Cache ONNX Runtime iOS xcframework | ||
| uses: actions/cache@v4 | ||
| id: cache-onnxruntime | ||
| with: | ||
| path: | | ||
| frontend/src-tauri/onnxruntime-ios | ||
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | ||
| restore-keys: | | ||
| onnxruntime-ios-xcframework-1.22.2- | ||
| - name: Build ONNX Runtime xcframework (if needed) | ||
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | ||
| working-directory: ./frontend/src-tauri | ||
| run: | | ||
| chmod +x scripts/build-ios-onnxruntime.sh | ||
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | ||
| timeout-minutes: 90 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd build-ios-onnxruntime.shRepository: OpenSecretCloud/Maple
Length of output: 116
🏁 Script executed:
cat -n frontend/src-tauri/scripts/build-ios-onnxruntime.shRepository: OpenSecretCloud/Maple
Length of output: 3971
Bug: restore-keys can restore stale ORT artifacts when combined with the early-exit check in the build script.
The script exits early (line 16-19) if onnxruntime.xcframework exists. With the current restore-keys fallback, a partial cache hit restores an older xcframework, cache-hit remains false, the build step runs, but the script exits without rebuilding. The stale artifact then gets saved under the new cache key.
Fix by deleting the xcframework before rebuild:
Proposed workflow fix
- name: Build ONNX Runtime xcframework (if needed)
if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
working-directory: ./frontend/src-tauri
run: |
+ rm -rf onnxruntime-ios/onnxruntime.xcframework
chmod +x scripts/build-ios-onnxruntime.sh
./scripts/build-ios-onnxruntime.sh 1.22.2
timeout-minutes: 90Cache key missing critical inputs. IOS_DEPLOYMENT_TARGET (used at script line 54) and Xcode version are not included in the cache key, though both affect binary compatibility. Different runner environments could produce incompatible binaries under the same key.
🤖 Prompt for AI Agents
In @.github/workflows/mobile-build.yml around lines 61 - 78, The cache/restore
logic can restore a stale onnxruntime.xcframework so when cache miss triggers
the build step the script may early-exit and leave stale artifacts; modify the
job so that before running scripts/build-ios-onnxruntime.sh (when
steps.cache-onnxruntime.outputs.cache-hit != 'true') you delete
frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework if it exists (e.g.,
rm -rf frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework) to force a
clean rebuild, and also strengthen the cache key by including
IOS_DEPLOYMENT_TARGET and the Xcode version (e.g., include
${IOS_DEPLOYMENT_TARGET} and the workflow Xcode version variable such as
matrix.xcode or runner.os/XCODE_VERSION) in the key/restore-keys so incompatible
builds don’t share the same cache.
| - name: Cache ONNX Runtime iOS xcframework | ||
| uses: actions/cache@v4 | ||
| id: cache-onnxruntime | ||
| with: | ||
| path: | | ||
| frontend/src-tauri/onnxruntime-ios | ||
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | ||
| restore-keys: | | ||
| onnxruntime-ios-xcframework-1.22.2- | ||
| - name: Build ONNX Runtime xcframework (if needed) | ||
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | ||
| working-directory: ./frontend/src-tauri | ||
| run: | | ||
| chmod +x scripts/build-ios-onnxruntime.sh | ||
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | ||
| timeout-minutes: 90 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the build script and workflow files
find . -name "build-ios-onnxruntime.sh" -o -name "testflight-on-comment.yml" -o -name "mobile-build.yml" | head -20Repository: OpenSecretCloud/Maple
Length of output: 201
🏁 Script executed:
# Look for the ONNX build script
fd -t f "build-ios-onnxruntime.sh"Repository: OpenSecretCloud/Maple
Length of output: 116
🏁 Script executed:
# Look for workflow files
fd -t f "testflight-on-comment.yml"Repository: OpenSecretCloud/Maple
Length of output: 47
🏁 Script executed:
# Look for mobile-build.yml
fd -t f "mobile-build.yml"Repository: OpenSecretCloud/Maple
Length of output: 47
🏁 Script executed:
cat -n ./frontend/src-tauri/scripts/build-ios-onnxruntime.shRepository: OpenSecretCloud/Maple
Length of output: 3971
🏁 Script executed:
cat -n ./.github/workflows/testflight-on-comment.yml | sed -n '100,150p'Repository: OpenSecretCloud/Maple
Length of output: 2162
🏁 Script executed:
cat -n ./.github/workflows/mobile-build.yml | grep -A 30 "cache-onnxruntime"Repository: OpenSecretCloud/Maple
Length of output: 2007
Same stale-cache risk as mobile CI: A restore-keys prefix match can restore an old cached onnxruntime.xcframework, but since the exact key doesn't match, the workflow condition if: steps.cache-onnxruntime.outputs.cache-hit != 'true' triggers the build step. The script then checks if the directory exists (lines 16-18) and exits early without rebuilding, leaving stale artifacts in place.
Proposed fix
- name: Build ONNX Runtime xcframework (if needed)
if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
working-directory: ./frontend/src-tauri
run: |
+ rm -rf onnxruntime-ios/onnxruntime.xcframework
chmod +x scripts/build-ios-onnxruntime.sh
./scripts/build-ios-onnxruntime.sh 1.22.2
timeout-minutes: 90📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Cache ONNX Runtime iOS xcframework | |
| uses: actions/cache@v4 | |
| id: cache-onnxruntime | |
| with: | |
| path: | | |
| frontend/src-tauri/onnxruntime-ios | |
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | |
| restore-keys: | | |
| onnxruntime-ios-xcframework-1.22.2- | |
| - name: Build ONNX Runtime xcframework (if needed) | |
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | |
| working-directory: ./frontend/src-tauri | |
| run: | | |
| chmod +x scripts/build-ios-onnxruntime.sh | |
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | |
| timeout-minutes: 90 | |
| - name: Cache ONNX Runtime iOS xcframework | |
| uses: actions/cache@v4 | |
| id: cache-onnxruntime | |
| with: | |
| path: | | |
| frontend/src-tauri/onnxruntime-ios | |
| key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }} | |
| restore-keys: | | |
| onnxruntime-ios-xcframework-1.22.2- | |
| - name: Build ONNX Runtime xcframework (if needed) | |
| if: steps.cache-onnxruntime.outputs.cache-hit != 'true' | |
| working-directory: ./frontend/src-tauri | |
| run: | | |
| rm -rf onnxruntime-ios/onnxruntime.xcframework | |
| chmod +x scripts/build-ios-onnxruntime.sh | |
| ./scripts/build-ios-onnxruntime.sh 1.22.2 | |
| timeout-minutes: 90 |
🤖 Prompt for AI Agents
In @.github/workflows/testflight-on-comment.yml around lines 119 - 136, The
workflow can trigger the build step even when a stale xcframework folder was
restored via a prefix-match restore-key; to fix, add a small preparatory step
(e.g., step id: check-onnx) that checks for the existence of
frontend/src-tauri/onnxruntime-ios and sets an output (exists=true/false), then
change the Build ONNX Runtime xcframework step condition to only run when
neither cache hit nor existing dir are present (if:
steps.cache-onnxruntime.outputs.cache-hit != 'true' &&
steps.check-onnx.outputs.exists != 'true'), or alternatively modify
scripts/build-ios-onnxruntime.sh to refuse early exit when cache-hit != 'true'
and instead force a rebuild; reference the actions/cache step id
cache-onnxruntime and the script scripts/build-ios-onnxruntime.sh to locate the
change.
| if [ -d "${XC_DIR}" ]; then | ||
| echo "ONNX Runtime xcframework already exists at ${XC_DIR}" | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Script executed:
find . -name "build-ios-onnxruntime.sh" -type fRepository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
./frontend/src-tauri/scripts/build-ios-onnxruntime.sh
Script executed:
cat -n frontend/src-tauri/scripts/build-ios-onnxruntime.sh | head -50Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
1 #!/usr/bin/env bash
2
3 set -euo pipefail
4
5 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6 TAURI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
7
8 ORT_VERSION="${1:-1.22.2}"
9 IOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET:-13.0}"
10
11 BUILD_ROOT="${TAURI_DIR}/onnxruntime-build"
12 SRC_DIR="${BUILD_ROOT}/onnxruntime"
13 OUT_DIR="${TAURI_DIR}/onnxruntime-ios"
14 XC_DIR="${OUT_DIR}/onnxruntime.xcframework"
15
16 if [ -d "${XC_DIR}" ]; then
17 echo "ONNX Runtime xcframework already exists at ${XC_DIR}"
18 exit 0
19 fi
20
21 mkdir -p "${BUILD_ROOT}"
22 mkdir -p "${OUT_DIR}"
23
24 for tool in git python3 cmake xcodebuild lipo; do
25 if ! command -v "${tool}" >/dev/null 2>&1; then
26 echo "Error: required tool '${tool}' not found"
27 exit 1
28 fi
29 done
30
31 if [ ! -d "${SRC_DIR}" ]; then
32 git clone --depth 1 --branch "v${ORT_VERSION}" --recursive \
33 https://github.com/microsoft/onnxruntime.git "${SRC_DIR}"
34 else
35 cd "${SRC_DIR}"
36 git fetch --tags
37 git checkout "v${ORT_VERSION}"
38 git submodule update --init --recursive
39 fi
40
41 cd "${SRC_DIR}"
42
43 build_one() {
44 local sysroot="$1"
45 local arch="$2"
46 local build_dir="$3"
47
48 ./build.sh \
49 --config Release \
50 --use_xcode \
Script executed:
# Check if there's any CI configuration that uses this script
find . -name "*.yml" -o -name "*.yaml" | head -20Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
./.github/workflows/android-build.yml
./.github/workflows/claude.yml
./.github/workflows/desktop-build.yml
./.github/workflows/release.yml
./.github/workflows/mobile-build.yml
./.github/workflows/testflight-on-comment.yml
./frontend/src-tauri/gen/apple/project.yml
Script executed:
# Look for any version tracking or ORT_VERSION references in the repo
rg "ORT_VERSION" --type sh --type yaml --type yml -B2 -A2Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 2
stderr:
unrecognized file type: yml
Script executed:
rg "ORT_VERSION" -A2 -B2Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-TAURI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-
frontend/src-tauri/scripts/build-ios-onnxruntime.sh:ORT_VERSION="${1:-1.22.2}"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-IOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET:-13.0}"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-
--
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-if [ ! -d "${SRC_DIR}" ]; then
frontend/src-tauri/scripts/build-ios-onnxruntime.sh: git clone --depth 1 --branch "v${ORT_VERSION}" --recursive \
frontend/src-tauri/scripts/build-ios-onnxruntime.sh- https://github.com/microsoft/onnxruntime.git "${SRC_DIR}"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-else
frontend/src-tauri/scripts/build-ios-onnxruntime.sh- cd "${SRC_DIR}"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh- git fetch --tags
frontend/src-tauri/scripts/build-ios-onnxruntime.sh: git checkout "v${ORT_VERSION}"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh- git submodule update --init --recursive
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-fi
--
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-}
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-
frontend/src-tauri/scripts/build-ios-onnxruntime.sh:echo "Building ONNX Runtime v${ORT_VERSION} for iOS (deployment target ${IOS_DEPLOYMENT_TARGET})"
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-
frontend/src-tauri/scripts/build-ios-onnxruntime.sh-DEVICE_BUILD_DIR="${BUILD_ROOT}/build-iphoneos-arm64"
Script executed:
cat -n ./.github/workflows/mobile-build.yml | head -100Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
1 name: Mobile App CI
2
3 on:
4 push:
5 branches: [ master ]
6 pull_request:
7 branches: [ master ]
8
9 jobs:
10 build-ios:
11 runs-on: macos-latest-xlarge
12 steps:
13 - uses: actions/checkout@v4
14
15 - name: Setup Bun
16 uses: oven-sh/setup-bun@v1
17 with:
18 bun-version: 1.2.2
19
20 - name: Install Rust
21 uses: dtolnay/rust-toolchain@stable
22 with:
23 targets: aarch64-apple-ios
24
25 - name: Cache Homebrew
26 uses: actions/cache@v4
27 with:
28 path: |
29 ~/Library/Caches/Homebrew
30 /opt/homebrew/Cellar/openssl@3
31 key: ${{ runner.os }}-brew-${{ hashFiles('.github/workflows/mobile-build.yml') }}
32 restore-keys: |
33 ${{ runner.os }}-brew-
34
35 - name: Cache Xcode DerivedData and SourcePackages
36 uses: irgaly/xcode-cache@v1
37 with:
38 key: xcode-cache-ios-${{ github.workflow }}-${{ hashFiles('**/Cargo.lock') }}
39 restore-keys: |
40 xcode-cache-ios-${{ github.workflow }}-
41 xcode-cache-ios-
42
43 - name: Cache Cargo bin (Tauri CLI)
44 uses: actions/cache@v4
45 with:
46 path: ~/.cargo/bin/
47 key: ${{ runner.os }}-cargo-bin-tauri-cli-2.9.2
48 restore-keys: |
49 ${{ runner.os }}-cargo-bin-tauri-cli-
50 ${{ runner.os }}-cargo-bin-
51
52 - name: Install frontend dependencies
53 working-directory: ./frontend
54 run: bun install
55
56 - name: Setup Xcode
57 uses: maxim-lobanov/setup-xcode@v1
58 with:
59 xcode-version: '16.4'
60
61 - name: Cache ONNX Runtime iOS xcframework
62 uses: actions/cache@v4
63 id: cache-onnxruntime
64 with:
65 path: |
66 frontend/src-tauri/onnxruntime-ios
67 key: onnxruntime-ios-xcframework-1.22.2-${{ hashFiles('frontend/src-tauri/scripts/build-ios-onnxruntime.sh') }}
68 restore-keys: |
69 onnxruntime-ios-xcframework-1.22.2-
70
71 - name: Build ONNX Runtime xcframework (if needed)
72 if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
73 working-directory: ./frontend/src-tauri
74 run: |
75 chmod +x scripts/build-ios-onnxruntime.sh
76 ./scripts/build-ios-onnxruntime.sh 1.22.2
77 timeout-minutes: 90
78
79 - name: Install Tauri CLI
80 run: |
81 if ! command -v cargo-tauri &> /dev/null; then
82 cargo install tauri-cli --version "2.9.2" --locked
83 else
84 echo "Tauri CLI already installed"
85 fi
86 env:
87 # Workaround for getrandom v0.2.16 (https://github.com/rust-random/getrandom/issues/641)
88 # cargo install builds for the host; on macOS runners target_os is "macos"
89 CARGO_CFG_TARGET_OS: macos
90
91 - name: Set up API Key
92 run: |
93 mkdir -p ~/.private_keys
94 echo "${{ secrets.APPLE_API_PRIVATE_KEY }}" | base64 --decode > ~/.private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8
95 chmod 600 ~/.private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8
96 echo "APPLE_API_KEY_PATH=~/.private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8" >> $GITHUB_ENV
97
98 - name: Build Tauri iOS App
99 working-directory: ./frontend
100 run: |
Script executed:
cat -n frontend/src-tauri/scripts/build-ios-onnxruntime.sh | tail -n +40Repository: OpenSecretCloud/Maple
Repository: OpenSecretCloud/Maple
Exit code: 0
stdout:
40
41 cd "${SRC_DIR}"
42
43 build_one() {
44 local sysroot="$1"
45 local arch="$2"
46 local build_dir="$3"
47
48 ./build.sh \
49 --config Release \
50 --use_xcode \
51 --ios \
52 --apple_sysroot "${sysroot}" \
53 --osx_arch "${arch}" \
54 --apple_deploy_target "${IOS_DEPLOYMENT_TARGET}" \
55 --build_dir "${build_dir}" \
56 --parallel \
57 --skip_tests \
58 --compile_no_warning_as_error
59 }
60
61 echo "Building ONNX Runtime v${ORT_VERSION} for iOS (deployment target ${IOS_DEPLOYMENT_TARGET})"
62
63 DEVICE_BUILD_DIR="${BUILD_ROOT}/build-iphoneos-arm64"
64 SIM_ARM64_BUILD_DIR="${BUILD_ROOT}/build-iphonesimulator-arm64"
65 SIM_X64_BUILD_DIR="${BUILD_ROOT}/build-iphonesimulator-x86_64"
66
67 build_one "iphoneos" "arm64" "${DEVICE_BUILD_DIR}"
68 build_one "iphonesimulator" "arm64" "${SIM_ARM64_BUILD_DIR}"
69 build_one "iphonesimulator" "x86_64" "${SIM_X64_BUILD_DIR}"
70
71 DEVICE_LIB="$(find "${DEVICE_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)"
72 SIM_ARM64_LIB="$(find "${SIM_ARM64_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)"
73 SIM_X64_LIB="$(find "${SIM_X64_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)"
74
75 if [ -z "${DEVICE_LIB}" ] || [ -z "${SIM_ARM64_LIB}" ] || [ -z "${SIM_X64_LIB}" ]; then
76 echo "Error: failed to locate libonnxruntime.a outputs"
77 echo " DEVICE_LIB=${DEVICE_LIB}"
78 echo " SIM_ARM64_LIB=${SIM_ARM64_LIB}"
79 echo " SIM_X64_LIB=${SIM_X64_LIB}"
80 exit 1
81 fi
82
83 HEADERS_DIR="${OUT_DIR}/Headers"
84 mkdir -p "${HEADERS_DIR}"
85
86 cp -f "${SRC_DIR}/include/onnxruntime/core/session/onnxruntime_c_api.h" "${HEADERS_DIR}/"
87 cp -f "${SRC_DIR}/include/onnxruntime/core/session/onnxruntime_cxx_api.h" "${HEADERS_DIR}/"
88 cp -f "${SRC_DIR}/include/onnxruntime/core/session/onnxruntime_cxx_inline.h" "${HEADERS_DIR}/"
89 cp -f "${SRC_DIR}/include/onnxruntime/core/providers/cpu/cpu_provider_factory.h" "${HEADERS_DIR}/"
90
91 SIM_UNIVERSAL_LIB="${OUT_DIR}/libonnxruntime-simulator-universal.a"
92 lipo -create "${SIM_ARM64_LIB}" "${SIM_X64_LIB}" -output "${SIM_UNIVERSAL_LIB}"
93
94 rm -rf "${XC_DIR}"
95 xcodebuild -create-xcframework \
96 -library "${DEVICE_LIB}" -headers "${HEADERS_DIR}" \
97 -library "${SIM_UNIVERSAL_LIB}" -headers "${HEADERS_DIR}" \
98 -output "${XC_DIR}"
99
100 echo "Created ${XC_DIR}"
Early-exit must check if built version matches requested ORT_VERSION.
Currently, if the xcframework directory exists, the script exits unconditionally. This breaks when ORT_VERSION changes, and worse—when CI restores a cached older framework from restore-keys, the script skips rebuilding and silently uses the wrong version. For example, if a restored cache contains framework v1.22.2 but the script is called with v1.23.0, the old version is used without warning.
The proposed fix adds a version marker file and checks it before early-exit, preventing stale cached artifacts from blocking necessary rebuilds. This also supports FORCE_REBUILD for manual overrides. The fix logic is sound, though the diff should also show where VERSION_FILE is written after a successful build to ensure the marker stays in sync.
Proposed fix: version marker + FORCE_REBUILD
ORT_VERSION="${1:-1.22.2}"
IOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET:-13.0}"
+FORCE_REBUILD="${FORCE_REBUILD:-0}"
BUILD_ROOT="${TAURI_DIR}/onnxruntime-build"
SRC_DIR="${BUILD_ROOT}/onnxruntime"
OUT_DIR="${TAURI_DIR}/onnxruntime-ios"
XC_DIR="${OUT_DIR}/onnxruntime.xcframework"
+VERSION_FILE="${OUT_DIR}/ORT_VERSION"
-if [ -d "${XC_DIR}" ]; then
- echo "ONNX Runtime xcframework already exists at ${XC_DIR}"
- exit 0
-fi
+if [ -d "${XC_DIR}" ] && [ "${FORCE_REBUILD}" != "1" ] && [ -f "${VERSION_FILE}" ] && grep -qx "${ORT_VERSION}" "${VERSION_FILE}"; then
+ echo "ONNX Runtime xcframework already exists at ${XC_DIR} (v${ORT_VERSION})"
+ exit 0
+fiCommittable suggestion skipped: line range outside the PR's diff.
| DEVICE_LIB="$(find "${DEVICE_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)" | ||
| SIM_ARM64_LIB="$(find "${SIM_ARM64_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)" | ||
| SIM_X64_LIB="$(find "${SIM_X64_BUILD_DIR}" -name "libonnxruntime.a" -type f | head -n 1 || true)" | ||
|
|
||
| if [ -z "${DEVICE_LIB}" ] || [ -z "${SIM_ARM64_LIB}" ] || [ -z "${SIM_X64_LIB}" ]; then | ||
| echo "Error: failed to locate libonnxruntime.a outputs" | ||
| echo " DEVICE_LIB=${DEVICE_LIB}" | ||
| echo " SIM_ARM64_LIB=${SIM_ARM64_LIB}" | ||
| echo " SIM_X64_LIB=${SIM_X64_LIB}" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find ... | head -n 1 can pick the wrong lib if multiple matches exist.
Safer to constrain the search to the expected output subdir (or at least prefer */Release/*/libonnxruntime.a).
🤖 Prompt for AI Agents
In @frontend/src-tauri/scripts/build-ios-onnxruntime.sh around lines 71 - 81,
The current DEVICE_LIB, SIM_ARM64_LIB and SIM_X64_LIB find commands can return
incorrect files when multiple libonnxruntime.a exist; update the find
invocations that set DEVICE_LIB, SIM_ARM64_LIB, and SIM_X64_LIB to narrow the
search to the expected output subdirectory (for example prefer paths matching
*/Release/*/libonnxruntime.a or use -path or -maxdepth to target the
build/Release output) so the script consistently picks the correct release
static library for each of DEVICE_BUILD_DIR, SIM_ARM64_BUILD_DIR and
SIM_X64_BUILD_DIR; keep the existing error check logic but ensure the find
patterns only match the intended Release output.
| rm -rf "${XC_DIR}" | ||
| xcodebuild -create-xcframework \ | ||
| -library "${DEVICE_LIB}" -headers "${HEADERS_DIR}" \ | ||
| -library "${SIM_UNIVERSAL_LIB}" -headers "${HEADERS_DIR}" \ | ||
| -output "${XC_DIR}" | ||
|
|
||
| echo "Created ${XC_DIR}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write the built version marker after creating the xcframework.
If you adopt the version-aware early-exit, ensure you write ${ORT_VERSION} only after successful xcodebuild -create-xcframework.
Proposed addition
xcodebuild -create-xcframework \
-library "${DEVICE_LIB}" -headers "${HEADERS_DIR}" \
-library "${SIM_UNIVERSAL_LIB}" -headers "${HEADERS_DIR}" \
-output "${XC_DIR}"
+echo "${ORT_VERSION}" > "${VERSION_FILE}"
echo "Created ${XC_DIR}"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| rm -rf "${XC_DIR}" | |
| xcodebuild -create-xcframework \ | |
| -library "${DEVICE_LIB}" -headers "${HEADERS_DIR}" \ | |
| -library "${SIM_UNIVERSAL_LIB}" -headers "${HEADERS_DIR}" \ | |
| -output "${XC_DIR}" | |
| echo "Created ${XC_DIR}" | |
| rm -rf "${XC_DIR}" | |
| xcodebuild -create-xcframework \ | |
| -library "${DEVICE_LIB}" -headers "${HEADERS_DIR}" \ | |
| -library "${SIM_UNIVERSAL_LIB}" -headers "${HEADERS_DIR}" \ | |
| -output "${XC_DIR}" | |
| echo "${ORT_VERSION}" > "${VERSION_FILE}" | |
| echo "Created ${XC_DIR}" |
🤖 Prompt for AI Agents
In @frontend/src-tauri/scripts/build-ios-onnxruntime.sh around lines 94 - 100,
After successfully creating the XC framework, write the built-version marker
(${ORT_VERSION}) to the version file so the version-aware early-exit works
correctly; specifically, only write ${ORT_VERSION} after the xcodebuild
-create-xcframework step completes without error (the block using XC_DIR,
DEVICE_LIB, SIM_UNIVERSAL_LIB and HEADERS_DIR), e.g., check xcodebuild exit
status and then echo "${ORT_VERSION}" > "${XC_DIR}/.built_version" (or the
existing version file name) and log success; do not write the marker before or
regardless of xcodebuild success.
| // iOS (with TTS) | ||
| #[cfg(all(not(desktop), target_os = "ios"))] | ||
| let app = builder | ||
| .manage(tts::TTSState::new()) | ||
| .invoke_handler(tauri::generate_handler![ | ||
| pdf_extractor::extract_document_content, | ||
| tts::tts_get_status, | ||
| tts::tts_download_models, | ||
| tts::tts_load_models, | ||
| tts::tts_synthesize, | ||
| tts::tts_unload_models, | ||
| tts::tts_delete_models, | ||
| ]) | ||
| .setup(|app| { | ||
| // Set up the deep link handler for mobile | ||
| let app_handle = app.handle().clone(); | ||
|
|
||
| // Register deep link handler - note that iOS does not support runtime registration | ||
| // but the handler for incoming URLs still works | ||
| app.deep_link().on_open_url(move |event| { | ||
| if let Some(url) = event.urls().first() { | ||
| handle_deep_link_event(url.as_ref(), &app_handle); | ||
| } | ||
| }); | ||
|
|
||
| Ok(()) | ||
| }) | ||
| .plugin(tauri_plugin_updater::Builder::new().build()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Tauri updater plugin iOS App Store support compatibility
💡 Result:
Short answer: Tauri's updater plugin lists iOS as a supported platform, but using it to push executable/binary updates to apps distributed through the App Store would conflict with Apple's rules — App Store apps must use App Store delivery for app binary updates and may not download/execute code that changes app features. Use of Tauri updater on iOS is therefore appropriate only for non‑App‑Store distribution (e.g., enterprise/ad‑hoc/TestFlight) or for updating non‑executable content that fits Apple's allowances. [1][2][3]
Sources:
- Tauri Updater plugin (supported platforms includes iOS). [1]
- Apple App Store Review Guideline 2.5.2 (no downloading/installing/executing code that changes app features/functionality). [2]
- Apple Developer Program License Agreement (section on not downloading/installing executable code; distribution must go via App Store/TestFlight/Custom/Ad‑Hoc as specified). [3]
🌐 Web query:
Tauri iOS updater plugin limitations requirements
💡 Result:
Short summary — key requirements and limitations for Tauri's iOS updater plugin
- Official support: the updater plugin lists iOS as a supported platform and requires a compatible Tauri/tooling stack (see plugin docs). [1]
- Rust/tooling: the plugin requires a recent Rust toolchain (docs note Rust >= ~1.77.x for the plugin). Ensure your Tauri/tauri-cli version supports iOS bundle options. [1][2]
- Signing required: updates must be signed — you must provide the public key in tauri.conf (createUpdaterArtifacts and pubkey configuration). The updater verifies signatures before installing. [1]
- Server/JSON format: your update server (or static JSON) must return the required fields (url, version, signature, etc.) in the format the plugin expects. [1]
- Capabilities/permissions: you must enable updater permissions in src-tauri/capabilities (e.g., updater:default or the granular updater:allow-* scopes). [1]
- App Store / iOS policy limitation (critical): iOS apps may not download and install executable code or change the app’s binary/primary functionality outside App Store mechanisms. Any updater behavior that would replace or alter executable code or add new binary functionality can violate Apple’s App Store Review Guidelines (2.5.2 / 3.3.x) and lead to rejection. In practice, you cannot use an in‑app updater to bypass App Store update/review for changes that modify executable code. Use the updater only for content/data updates or workflows that comply with Apple policy. [3]
- Practical implications for Tauri on iOS:
- You can deliver non-executable/content updates (assets, remote content) but not replace the app binary or add new executable features without App Store review. [3][1]
- Test and document exactly what your update bundle contains (no downloaded executables, dynamic frameworks, or behavior-altering code) to avoid App Review rejections. [1][3]
If you want, I can list the exact tauri.conf fields and example static JSON and capability entries required for an iOS build.
Remove or conditionally disable tauri_plugin_updater for iOS App Store distribution.
The updater plugin on line 289 is problematic for iOS apps distributed via the App Store. Apple's App Store Review Guidelines (2.5.2) prohibit apps from downloading and executing code that changes app functionality or binaries. If this iOS build targets App Store distribution, the updater plugin should either be removed or conditionally compiled out for App Store builds (#[cfg(...)]). The plugin is only appropriate on iOS for non-App Store distribution channels (TestFlight, enterprise) or for updating non-executable content/assets that don't alter app functionality.
| fn get_tts_models_dir(app: &AppHandle) -> Result<PathBuf> { | ||
| let data_dir = app | ||
| .path() | ||
| .app_local_data_dir() | ||
| .context("Failed to get app local data directory")? | ||
| .join("tts_models"); | ||
| Ok(data_dir) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential “model path migration” regression (old downloads won’t be detected).
Switching to app_local_data_dir()/tts_models will make previously downloaded models in the old location appear “missing” unless you migrate/copy them (or document the change). Consider adding a one-time migration fallback (check old path → move/copy).
Enables the existing Supertonic/ONNX-based TTS pipeline on iOS.
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.