Add watchOS support: build, CI, Metal, and sysctl patch
- Add aarch64-apple-watchos and aarch64-apple-watchos-sim to build-swift-xcframework.sh - Add check-watchos and check-watchos-sim jobs to CI workflow - Enable mistralrs (Metal) and hf-hub for watchOS in Cargo.toml - Patch sysctl crate for watchOS support (pending upstream PR) - Update all platform gating and feature flags for target_os = "watchos" - Default to 1.5B model on watchOS due to memory constraints - Update LICENSE-APACHE copyright year and owner - Include watchOS in tvos_chkstk.s build.rs logic for linker workaround
Seto Elkahfi committed
Apr 9, 2026 at 21:50 UTC
ac1610b357488ce6ccd819fde8a15132b18b0cd5
10 files changed
+86
-16
.github/scripts/build-swift-xcframework.sh
+6
@@ -25,6 +25,8 @@ cargo +nightly rustc -Z build-std --target aarch64-apple-tvos --release --lib --
25
cargo +nightly rustc -Z build-std --target aarch64-apple-tvos-sim --release --lib --crate-type staticlib
26
cargo +nightly rustc -Z build-std --target aarch64-apple-visionos --release --lib --crate-type staticlib
27
cargo +nightly rustc -Z build-std --target aarch64-apple-visionos-sim --release --lib --crate-type staticlib
28
+cargo +nightly rustc -Z build-std --target aarch64-apple-watchos --release --lib --crate-type staticlib
29
+cargo +nightly rustc -Z build-std --target aarch64-apple-watchos-sim --release --lib --crate-type staticlib
30
31
"$BINDGEN" generate "$ROOT_DIR/target/aarch64-apple-ios/release/libonde.a" --language swift --out-dir "$PACKAGE_DIR/Sources/Onde"
32
@@ -37,6 +39,8 @@ cp "$ROOT_DIR/target/aarch64-apple-tvos-sim/release/libonde.a" "$DIST_DIR/libond
39
40
cp "$ROOT_DIR/target/aarch64-apple-visionos-sim/release/libonde.a" "$DIST_DIR/libonde-visionos-sim.a"
41
42
+cp "$ROOT_DIR/target/aarch64-apple-watchos-sim/release/libonde.a" "$DIST_DIR/libonde-watchos-sim.a"
43
+
44
cp "$ROOT_DIR/target/aarch64-apple-darwin/release/libonde.a" "$DIST_DIR/libonde-macos.a"
45
46
xcodebuild -create-xcframework \
@@ -46,6 +50,8 @@ xcodebuild -create-xcframework \
50
-library "$DIST_DIR/libonde-tvos-sim.a" -headers "$HEADERS_DIR" \
51
-library "$ROOT_DIR/target/aarch64-apple-visionos/release/libonde.a" -headers "$HEADERS_DIR" \
52
-library "$DIST_DIR/libonde-visionos-sim.a" -headers "$HEADERS_DIR" \
53
+ -library "$ROOT_DIR/target/aarch64-apple-watchos/release/libonde.a" -headers "$HEADERS_DIR" \
54
+ -library "$DIST_DIR/libonde-watchos-sim.a" -headers "$HEADERS_DIR" \
55
-library "$DIST_DIR/libonde-macos.a" -headers "$HEADERS_DIR" \
56
-output "$FRAMEWORK_DIR"
57
.github/workflows/ci.yml
+26
@@ -126,3 +126,29 @@ jobs:
126
with:
127
key: visionos-sim
128
- run: cargo +nightly check -Z build-std --target aarch64-apple-visionos-sim
129
+
130
+ check-watchos:
131
+ name: Check (watchOS device)
132
+ runs-on: macos-15
133
+ steps:
134
+ - uses: actions/checkout@v6
135
+ - uses: dtolnay/rust-toolchain@nightly
136
+ with:
137
+ components: rust-src
138
+ - uses: Swatinem/rust-cache@v2
139
+ with:
140
+ key: watchos
141
+ - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos
142
+
143
+ check-watchos-sim:
144
+ name: Check (watchOS simulator)
145
+ runs-on: macos-15
146
+ steps:
147
+ - uses: actions/checkout@v6
148
+ - uses: dtolnay/rust-toolchain@nightly
149
+ with:
150
+ components: rust-src
151
+ - uses: Swatinem/rust-cache@v2
152
+ with:
153
+ key: watchos-sim
154
+ - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos-sim
Cargo.lock
+10
-11
@@ -3093,7 +3093,7 @@ dependencies = [
3093
[[package]]
3094
name = "mistralrs"
3095
version = "0.8.1"
3096
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3096
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3097
dependencies = [
3098
"anyhow",
3099
"candle-core",
@@ -3120,7 +3120,7 @@ dependencies = [
3120
[[package]]
3121
name = "mistralrs-audio"
3122
version = "0.8.1"
3123
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3123
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3124
dependencies = [
3125
"anyhow",
3126
"apodize",
@@ -3131,7 +3131,7 @@ dependencies = [
3131
[[package]]
3132
name = "mistralrs-core"
3133
version = "0.8.1"
3134
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3134
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3135
dependencies = [
3136
"ahash",
3137
"akin",
@@ -3228,7 +3228,7 @@ dependencies = [
3228
[[package]]
3229
name = "mistralrs-macros"
3230
version = "0.8.1"
3231
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3231
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3232
dependencies = [
3233
"darling 0.23.0",
3234
"proc-macro2",
@@ -3239,7 +3239,7 @@ dependencies = [
3239
[[package]]
3240
name = "mistralrs-mcp"
3241
version = "0.8.1"
3242
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3242
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3243
dependencies = [
3244
"anyhow",
3245
"async-trait",
@@ -3259,7 +3259,7 @@ dependencies = [
3259
[[package]]
3260
name = "mistralrs-paged-attn"
3261
version = "0.8.1"
3262
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3262
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3263
dependencies = [
3264
"anyhow",
3265
"candle-core",
@@ -3275,7 +3275,7 @@ dependencies = [
3275
[[package]]
3276
name = "mistralrs-quant"
3277
version = "0.8.1"
3278
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3278
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3279
dependencies = [
3280
"byteorder",
3281
"candle-core",
@@ -3304,7 +3304,7 @@ dependencies = [
3304
[[package]]
3305
name = "mistralrs-vision"
3306
version = "0.8.1"
3307
-source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a0e354d85c478c642f2559125f59e5fb4c7d6f0b"
3307
+source = "git+https://github.com/setoelkahfi/mistral.rs?branch=fix%2Fall-platform-fixes#a27af8ea01123e5d5777120619413345989f4006"
3308
dependencies = [
3309
"candle-core",
3310
"image",
@@ -5486,14 +5486,13 @@ dependencies = [
5486
[[package]]
5487
name = "sysctl"
5488
version = "0.6.0"
5489
-source = "registry+https://github.com/rust-lang/crates.io-index"
5490
-checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
5489
+source = "git+https://github.com/setoelkahfi/sysctl-rs.git?branch=feature%2Fwatchos#961a771d6ab0422a34cdcb51588ecb8fd3a01250"
5490
dependencies = [
5491
"bitflags 2.11.0",
5492
"byteorder",
5493
"enum-as-inner",
5494
"libc",
5496
- "thiserror 1.0.69",
5495
+ "thiserror 2.0.18",
5496
"walkdir",
5497
]
5498
Cargo.toml
+15
-1
@@ -77,7 +77,15 @@ mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/a
77
# mistralrs-core = { version = "0.8.1", features = ["metal"] }
78
mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
79
80
-
80
+# watchOS: Metal-accelerated inference (Apple Watch Series 4+ has Apple GPU);
81
+# hf-hub + mistralrs-core needed for GLOBAL_HF_CACHE sandbox workaround.
82
+# Memory constrained — default to 1.5B model.
83
+[target.'cfg(target_os = "watchos")'.dependencies]
84
+hf-hub = { version = "0.4", features = ["tokio", "rustls-tls"], default-features = false }
85
+# mistralrs = { version = "0.8.1", features = ["metal"] }
86
+mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
87
+# mistralrs-core = { version = "0.8.1", features = ["metal"] }
88
+mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", features = ["metal"] }
89
90
# macOS: Metal-accelerated inference.
91
[target.'cfg(target_os = "macos")'.dependencies]
@@ -95,3 +103,9 @@ mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/a
103
[target.'cfg(target_os = "linux")'.dependencies]
104
# mistralrs = { version = "0.8.1" }
105
mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes" }
106
+
107
+# Patch sysctl with watchOS (target_os = "watchos") support until upstream merges it.
108
+# Upstream PR: https://github.com/johalun/sysctl-rs/pull/74
109
+# Fork branch: https://github.com/setoelkahfi/sysctl-rs/tree/feature/watchos
110
+[patch.crates-io]
111
+sysctl = { git = "https://github.com/setoelkahfi/sysctl-rs.git", branch = "feature/watchos" }
LICENSE-APACHE
+1
-1
@@ -187,7 +187,7 @@
187
same "printed page" as the copyright notice for easier
188
identification within third-party archives.
189
190
- Copyright [yyyy] [name of copyright owner]
190
+ Copyright 2026 Onde Inference (Splitfire AB)
191
192
Licensed under the Apache License, Version 2.0 (the "License");
193
you may not use this file except in compliance with the License.
build.rs
+1
-1
@@ -17,7 +17,7 @@ fn main() {
17
// instruction — only when targeting tvOS.
18
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
19
20
- if target_os == "tvos" || target_os == "visionos" {
20
+ if target_os == "tvos" || target_os == "visionos" || target_os == "watchos" {
21
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
22
let asm_path = std::path::Path::new(&manifest_dir).join("scripts/tvos_chkstk.s");
23
src/hf_cache.rs
+4
-1
@@ -251,6 +251,7 @@ fn list_revisions(model_dir: &Path) -> Vec<String> {
251
target_os = "ios",
252
target_os = "tvos",
253
target_os = "visionos",
254
+ target_os = "watchos",
255
target_os = "android"
256
))]
257
fn make_progress(model_id: &str, downloaded: u64, total: u64, done: bool) -> ModelDownloadProgress {
@@ -888,7 +889,8 @@ pub fn list_supported_hf_models() -> SupportedHfModelsResponse {
889
target_os = "macos",
890
target_os = "ios",
891
target_os = "tvos",
891
- target_os = "visionos"
892
+ target_os = "visionos",
893
+ target_os = "watchos"
894
))]
895
pub async fn download_model<F>(
896
model_id: String,
@@ -1197,6 +1199,7 @@ where
1199
target_os = "ios",
1200
target_os = "tvos",
1201
target_os = "visionos",
1202
+ target_os = "watchos",
1203
target_os = "android"
1204
)))]
1205
pub async fn download_model<F>(
src/inference/engine.rs
+19
-1
@@ -61,6 +61,7 @@
61
target_os = "ios",
62
target_os = "tvos",
63
target_os = "visionos",
64
+ target_os = "watchos",
65
target_os = "windows",
66
target_os = "linux",
67
target_os = "android"
@@ -72,6 +73,7 @@ use std::sync::Arc;
73
target_os = "ios",
74
target_os = "tvos",
75
target_os = "visionos",
76
+ target_os = "watchos",
77
target_os = "windows",
78
target_os = "linux",
79
target_os = "android"
@@ -87,6 +89,7 @@ use super::types::*;
89
target_os = "ios",
90
target_os = "tvos",
91
target_os = "visionos",
92
+ target_os = "watchos",
93
target_os = "windows",
94
target_os = "linux",
95
target_os = "android"
@@ -105,6 +108,7 @@ use mistralrs::{IsqBits, TextModelBuilder};
108
target_os = "ios",
109
target_os = "tvos",
110
target_os = "visionos",
111
+ target_os = "watchos",
112
target_os = "windows",
113
target_os = "linux",
114
target_os = "android"
@@ -120,6 +124,7 @@ enum LoadedModelConfig {
124
target_os = "ios",
125
target_os = "tvos",
126
target_os = "visionos",
127
+ target_os = "watchos",
128
target_os = "windows",
129
target_os = "linux",
130
target_os = "android"
@@ -148,6 +153,7 @@ impl LoadedModelConfig {
153
target_os = "ios",
154
target_os = "tvos",
155
target_os = "visionos",
156
+ target_os = "watchos",
157
target_os = "windows",
158
target_os = "linux",
159
target_os = "android"
@@ -185,6 +191,7 @@ struct LoadedModel {
191
target_os = "ios",
192
target_os = "tvos",
193
target_os = "visionos",
194
+ target_os = "watchos",
195
target_os = "windows",
196
target_os = "linux",
197
target_os = "android"
@@ -198,6 +205,7 @@ pub struct ChatEngine {
205
target_os = "ios",
206
target_os = "tvos",
207
target_os = "visionos",
208
+ target_os = "watchos",
209
target_os = "windows",
210
target_os = "linux",
211
target_os = "android"
@@ -262,7 +270,8 @@ impl ChatEngine {
270
target_os = "android",
271
target_os = "ios",
272
target_os = "tvos",
265
- target_os = "visionos"
273
+ target_os = "visionos",
274
+ target_os = "watchos"
275
))]
276
{
277
let hf_home = std::env::var("HF_HOME")
@@ -320,6 +329,7 @@ impl ChatEngine {
329
target_os = "ios",
330
target_os = "tvos",
331
target_os = "visionos",
332
+ target_os = "watchos",
333
target_os = "android"
334
)) {
335
SamplingConfig::mobile()
@@ -829,6 +839,7 @@ impl ChatEngine {
839
target_os = "ios",
840
target_os = "tvos",
841
target_os = "visionos",
842
+ target_os = "watchos",
843
target_os = "windows",
844
target_os = "linux",
845
target_os = "android"
@@ -849,6 +860,7 @@ impl Default for ChatEngine {
860
target_os = "ios",
861
target_os = "tvos",
862
target_os = "visionos",
863
+ target_os = "watchos",
864
target_os = "windows",
865
target_os = "linux",
866
target_os = "android"
@@ -881,6 +893,7 @@ fn build_request(loaded: &LoadedModel, user_message: &str) -> RequestBuilder {
893
target_os = "ios",
894
target_os = "tvos",
895
target_os = "visionos",
896
+ target_os = "watchos",
897
target_os = "windows",
898
target_os = "linux",
899
target_os = "android"
@@ -916,6 +929,7 @@ fn apply_sampling(mut req: RequestBuilder, sampling: &SamplingConfig) -> Request
929
target_os = "ios",
930
target_os = "tvos",
931
target_os = "visionos",
932
+ target_os = "watchos",
933
target_os = "windows",
934
target_os = "linux",
935
target_os = "android"
@@ -950,6 +964,7 @@ fn truncate_for_log(s: &str, max_len: usize) -> String {
964
target_os = "ios",
965
target_os = "tvos",
966
target_os = "visionos",
967
+ target_os = "watchos",
968
target_os = "windows",
969
target_os = "linux",
970
target_os = "android"
@@ -961,6 +976,7 @@ pub struct ChatEngine;
976
target_os = "ios",
977
target_os = "tvos",
978
target_os = "visionos",
979
+ target_os = "watchos",
980
target_os = "windows",
981
target_os = "linux",
982
target_os = "android"
@@ -1048,6 +1064,7 @@ impl ChatEngine {
1064
target_os = "ios",
1065
target_os = "tvos",
1066
target_os = "visionos",
1067
+ target_os = "watchos",
1068
target_os = "windows",
1069
target_os = "linux",
1070
target_os = "android"
@@ -1156,6 +1173,7 @@ impl GgufModelConfig {
1173
target_os = "ios",
1174
target_os = "tvos",
1175
target_os = "visionos",
1176
+ target_os = "watchos",
1177
target_os = "android"
1178
)) {
1179
Self::qwen25_coder_1_5b()
src/inference/token.rs
+3
@@ -38,6 +38,7 @@
38
target_os = "ios",
39
target_os = "tvos",
40
target_os = "visionos",
41
+ target_os = "watchos",
42
target_os = "windows",
43
target_os = "android"
44
))]
@@ -60,6 +61,7 @@ const BUILD_TIME_HF_TOKEN: Option<&str> = option_env!("HF_TOKEN");
61
target_os = "ios",
62
target_os = "tvos",
63
target_os = "visionos",
64
+ target_os = "watchos",
65
target_os = "windows",
66
target_os = "android"
67
))]
@@ -95,6 +97,7 @@ mod tests {
97
target_os = "ios",
98
target_os = "tvos",
99
target_os = "visionos",
100
+ target_os = "watchos",
101
target_os = "windows",
102
target_os = "android"
103
))]
src/lib.rs
+1
@@ -54,6 +54,7 @@ uniffi::setup_scaffolding!();
54
target_os = "ios",
55
target_os = "tvos",
56
target_os = "visionos",
57
+ target_os = "watchos",
58
target_os = "windows",
59
target_os = "linux",
60
target_os = "android"