Skip to content

Commit 518a85e

Browse files
committed
feat: watchOS native app support (v0.3.2)
Add full watchOS app compilation via --target watchos/watchos-simulator. Uses a data-driven SwiftUI renderer: native code builds a UI tree via perry_ui_* FFI calls, and a fixed PerryWatchApp.swift (shipped with Perry) queries the tree and renders it as SwiftUI views reactively. No transpilation. - New perry-ui-watchos crate: data-driven widget tree, reactive state system, full perry_ui_* FFI surface (15 supported widgets + stubs for unsupported) - Cranelift ISA for aarch64-apple-watchos, COMPILE_TARGET=5 - swiftc-based linking (compiles PerryWatchApp.swift alongside native code) - watchOS .app bundling with WKApplication/WKWatchOnly Info.plist - perry setup watchos wizard (shares App Store Connect creds with iOS) - perry run watchos with Apple Watch simulator auto-detection - [watchos] config section in perry.toml - Platform docs: docs/src/platforms/watchos.md
1 parent e3f60b1 commit 518a85e

18 files changed

Lines changed: 2647 additions & 43 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
88

99
Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and Cranelift for code generation.
1010

11-
**Current Version:** 0.3.0
11+
**Current Version:** 0.3.2
1212

1313
## Workflow Requirements
1414

@@ -153,6 +153,9 @@ Projects can list npm packages to compile natively instead of routing to V8. Con
153153

154154
## Recent Changes
155155

156+
### v0.3.2
157+
- watchOS native app support (`--target watchos`/`--target watchos-simulator`): data-driven SwiftUI renderer, `perry-ui-watchos` crate with full `perry_ui_*` FFI surface, fixed PerryWatchApp.swift runtime, `perry setup watchos`, `perry run watchos`, `[watchos]` config in perry.toml
158+
156159
### v0.3.0
157160
- Compile-time i18n system (`perry/i18n` module): zero-ceremony localization for UI strings, `[i18n]` config in perry.toml, embedded 2D string table, native locale detection (all 6 platforms via OS APIs), `{param}` interpolation, CLDR plural rules (30+ locales), format wrappers (`Currency`, `Percent`, `ShortDate`, `LongDate`, `FormatNumber`, `FormatTime`, `Raw`), `perry i18n extract` CLI, iOS `.lproj` + Android `values-xx/` generation, compile-time validation
158161

Cargo.lock

Lines changed: 30 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ members = [
1717
"crates/perry-ui-android",
1818
"crates/perry-ui-gtk4",
1919
"crates/perry-ui-windows",
20+
"crates/perry-ui-watchos",
2021
"crates/perry-ui-geisterhand",
2122
"crates/perry-codegen-js",
2223
"crates/perry-codegen-swiftui",
@@ -79,7 +80,7 @@ opt-level = "s" # Optimize for size in stdlib
7980
opt-level = 3
8081

8182
[workspace.package]
82-
version = "0.3.1"
83+
version = "0.3.2"
8384
edition = "2021"
8485
license = "MIT"
8586
repository = "https://github.com/PerryTS/perry"

crates/perry-codegen/src/codegen.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ impl Compiler {
149149
.finish(settings::Flags::new(flag_builder))
150150
.map_err(|e| anyhow!("{}", e))?
151151
}
152+
Some("watchos") | Some("watchos-simulator") => {
153+
// Cross-compile for aarch64-apple-watchos (Mach-O)
154+
let triple = target_lexicon::Triple::from_str("aarch64-apple-watchos")
155+
.map_err(|e| anyhow!("Bad triple: {}", e))?;
156+
let isa_builder = cranelift::codegen::isa::lookup(triple)
157+
.map_err(|e| anyhow!("Failed to create watchOS ISA: {}", e))?;
158+
isa_builder
159+
.finish(settings::Flags::new(flag_builder))
160+
.map_err(|e| anyhow!("{}", e))?
161+
}
152162
Some("android") => {
153163
// Cross-compile for aarch64-linux-android (ELF)
154164
let triple = target_lexicon::Triple::from_str("aarch64-unknown-linux-android")
@@ -197,10 +207,11 @@ impl Compiler {
197207
let ctx = module.make_context();
198208

199209
// Determine the compile-time platform constant for __platform__:
200-
// 0 = macOS, 1 = iOS, 2 = Android, 3 = Windows, 4 = Linux
210+
// 0 = macOS, 1 = iOS, 2 = Android, 3 = Windows, 4 = Linux, 5 = watchOS
201211
let compile_target: i64 = match target {
202212
Some("ios") | Some("ios-simulator") => 1,
203213
Some("android") => 2,
214+
Some("watchos") | Some("watchos-simulator") => 5,
204215
Some("windows") => 3,
205216
Some("linux") => 4,
206217
_ => {

crates/perry-ui-watchos/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "perry-ui-watchos"
3+
version.workspace = true
4+
edition.workspace = true
5+
6+
[lib]
7+
crate-type = ["rlib", "staticlib"]
8+
9+
[features]
10+
default = []
11+
plugins = []
12+
geisterhand = []
13+
14+
[dependencies]
15+
perry-ui = { path = "../perry-ui" }
16+
perry-runtime = { path = "../perry-runtime", default-features = false }

crates/perry-ui-watchos/src/app.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! App lifecycle for watchOS.
2+
//!
3+
//! Since watchOS uses SwiftUI's @main App, the actual app lifecycle is managed
4+
//! by the fixed PerryWatchApp.swift. This module stores config and provides
5+
//! the entry point that Swift calls to run the compiled TypeScript init code.
6+
7+
use std::cell::RefCell;
8+
9+
use crate::tree::{self, NodeData, NodeKind};
10+
11+
thread_local! {
12+
static PENDING_BODY: RefCell<Option<i64>> = RefCell::new(None);
13+
}
14+
15+
pub fn app_create(_title_ptr: *const u8, _width: f64, _height: f64) -> i64 {
16+
// On watchOS, the app is created by the SwiftUI @main struct.
17+
// We just return a handle to satisfy the API contract.
18+
1
19+
}
20+
21+
pub fn app_set_body(_app_handle: i64, root_handle: i64) {
22+
tree::set_root(root_handle);
23+
PENDING_BODY.with(|b| {
24+
*b.borrow_mut() = Some(root_handle);
25+
});
26+
}
27+
28+
pub fn app_run(_app_handle: i64) {
29+
// On watchOS, the SwiftUI run loop is managed by PerryWatchApp.swift.
30+
// The compiled TypeScript calls perry_ui_app_run() at the end of init,
31+
// but on watchOS this is a no-op — the Swift @main struct drives the loop.
32+
//
33+
// perry_main_init() is called from Swift before the app body is rendered,
34+
// so by the time SwiftUI queries the tree, it's fully built.
35+
}

0 commit comments

Comments
 (0)