Build the library for tvOS: make NemoTextProcessing platform-conditional - #890
Build the library for tvOS: make NemoTextProcessing platform-conditional#890cakesmachina wants to merge 1 commit into
Conversation
The prebuilt NemoTextProcessing.xcframework ships ios, ios-simulator and macos slices only, so `swift build --triple arm64-apple-tvos26.0` fails with "no library for this platform was found". It is imported by exactly two files (ITN/TextNormalizer, TTS/Shared/NemoTextNormalizer) and used at two TTS call sites; the ASR pipeline never touches it. - Package.swift: the dependency is `.when(platforms: [.macOS, .iOS])`. - The two importers are wrapped in `#if canImport(CNemoTextProcessing)`. - EnglishTextNormalizer falls back to its own baseline normaliser and the Mandarin Kokoro path passes text through where the FST engine is absent. Witnessed: `swift build --product FluidAudio` completes for both arm64-apple-tvos26.0-simulator and arm64-apple-tvos26.0 (Xcode 27 beta). No behaviour change on macOS or iOS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZbYTEH3aemaaG2otBWesj
|
Thanks. Conditional binary dependency is the right call (confirmed v0.3.0 ships only macOS/iOS slices). Two fixes before merge: 1. Don't remove 2. Mandarin numeric-only input. Also:
|
…consumers (#880, #888) (#892) Closes #888, closes #880. ## Problem Since 0.15.5 every consumer links `NemoTextProcessing`, a ~18 MB-per-slice prebuilt Rust staticlib that only the TTS frontends and the ITN API call. #888 measured +18 MB on a universal macOS binary for an ASR/diarization-only app. #880 can't link at all: a second Rust runtime duplicates `_rust_eh_personality` and 143 std symbols. ## Approach: a package trait, not a module split Splitting TTS/ITN into a separate target would force every shared internal (`ModelHub`, `AppLogger`, download/audio utils) public and change every TTS consumer's imports. A SwiftPM trait keeps one module and one API: - **`Package@swift-6.2.swift`** — same manifest plus a default-on `NemoTextProcessing` trait; the binary target becomes a conditional dependency. Gated at 6.2, not 6.1: SwiftPM 6.1 (Xcode 16.4) accepts `--disable-default-traits` but still links the trait-conditioned binary target (first CI run: 1944 symbols); Swift 6.2 links 0. Consumers opt out with: ```swift .package(url: "https://github.com/FluidInference/FluidAudio.git", from: "0.15.7", traits: []) ``` `Package.swift` stays at tools 6.0 (CI's Xcode 16 iOS job, older consumers) and always links the engine. - **`TextNormalizer` / `NemoTextNormalizer` stay public on every build.** Only the import and the FFI call bodies are guarded with `canImport(CNemoTextProcessing)`, which is true under both manifests whenever the engine is a dependency. Trait off: `isNativeAvailable`, `isTnAvailable`, and the new `NemoTextNormalizer.isAvailable` report `false`, every call passes text through, `version` is `nil`, rule mutations log a warning. This is the explicit-availability contract, not the silent dlopen no-op #867 removed. - **Kokoro Mandarin** verbalizes numeric-only input with `MandarinNumberNormalizer` when the engine is absent, so `$5.50` still reaches G2P instead of the bopomofo passthrough. - **Tests**: engine-backed classes skip when the trait is off; new `TextNormalizerUnavailableTests` / `NemoTextNormalizerUnavailableTests` pin the passthrough contract and run only then. - **CI**: new macos-15 job on Xcode 26.3 builds `fluidaudiocli` with `--disable-default-traits`, asserts zero `text_processing_rs` / `nemo_` symbols in the binary, and runs the normalizer tests trait-off. - **Docs**: PostProcessing.md "Opting out of the engine" section, README pointer. ## Verification (local, Swift 6.2.3) Universal release build of `fluidaudiocli` (arm64 + x86_64 slices built per-triple, `lipo -create`), `strip -x` applied: | build | arm64 slice | x86_64 slice | universal | engine symbols/slice | |---|---|---|---|---| | default | 16.15 MB | 16.90 MB | 33.05 MB | 671 / 680 | | `traits: []` | 7.98 MB | 8.75 MB | 16.76 MB | 0 / 0 | Delta: **-16.3 MB universal (-8.2 MB per slice)**, the same order as the +18 MB @JulianPscheid measured across two releases on a universal app in #888. Library + CLI compile clean both ways; `swift format lint` clean. XCTest is unavailable locally, so the test target is exercised by CI. ## Notes - Relates to #890 (tvOS): the same `canImport` guards are what that PR's review asked for, so it can reduce to the `.when(platforms:)` condition on top of this. - @JulianPscheid offered a before/after universal build in #888; a run of your app against this branch with `traits: []` would confirm the size delta on a real target. 🤖 Generated with [Claude Code](https://claude.com/claude-code) b
The prebuilt
NemoTextProcessing.xcframeworkships ios, ios-simulator and macos slices only, so building theFluidAudiolibrary for tvOS fails with "no library for this platform was found". The framework is imported by exactly two files and used at two TTS call sites; the Parakeet ASR pipeline never touches it.This PR:
.when(platforms: [.macOS, .iOS])inPackage.swift;ITN/TextNormalizer.swiftandTTS/Shared/NemoTextNormalizer.swiftin#if canImport(CNemoTextProcessing);EnglishTextNormalizer.normalizeForFrontendfall back to its baseline and the Mandarin Kokoro path pass text through when the FST engine is absent.Witnessed with Xcode 27 beta:
swift build --product FluidAudio --triple arm64-apple-tvos26.0-simulatorand--triple arm64-apple-tvos26.0both complete. macOS and iOS builds are unchanged.platforms:inPackage.swiftis deliberately left alone; adding.tvOSthere is your call.Use case: bundling
parakeet-tdt-0.6b-v3-coremlinside an Apple TV app for offline read-along transcripts.🤖 Generated with Claude Code
https://claude.ai/code/session_01HZbYTEH3aemaaG2otBWesj