Skip to content

feat(wearos): full Wear OS app target (--target wearos / perry run wearos)#5173

Merged
proggeramlug merged 3 commits into
mainfrom
feat/wearos-app-target
Jun 15, 2026
Merged

feat(wearos): full Wear OS app target (--target wearos / perry run wearos)#5173
proggeramlug merged 3 commits into
mainfrom
feat/wearos-app-target

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a full Wear OS app target. Until now Wear OS support was limited to glanceable Tiles (--target wearos-tile); there was no way to ship a full watch app.

Wear OS is Android on a watch — same ART/JNI runtime, same View system — so this reuses the entire perry-ui-android backend and aarch64-linux-android toolchain rather than introducing a parallel UI crate. The compiled .so is identical to an Android phone build; only the APK packaging differs. No new workspace crate, so the CI exclusion list is unchanged.

Platform::Wearos (+ wearos/wear/wear-os aliases) cross-compiles the same .so as android, then build_and_run_wearos packages it through a watch form-factor overlay applied to the existing Android Gradle template:

  • android.hardware.type.watch feature
  • com.google.android.wearable.standalone meta-data + com.google.android.wearable uses-library
  • androidx.wear:wear dependency
  • minSdk raised to 30 (Wear OS 3 — the Google Play floor for watch APKs)

Everything else (jniLibs, companion .so bundling, assets, applicationId, deep links, signing, install, logcat) is shared with the phone path.

Bug fixes (also affect the existing android path)

Found while bringing the target up on a real Wear OS emulator:

  1. android_cross_env now sets AR=llvm-ar. NDK r27+ removed the per-target aarch64-linux-android-ar wrapper that cc-rs probes for, so the runtime/stdlib C deps (mimalloc) failed to cross-build.
  2. resolve_target_triple maps wearosaarch64-unknown-linux-android so codegen emits Android ELF objects (it was falling back to the host Mach-O triple → .o: unknown file type at link).
  3. The gradlew invocation uses an absolute path. A relative android-build/gradlew combined with .current_dir(&build_dir) resolved against the new cwd (android-build/android-build/gradlew) → perry run failed with ENOENT.

Testing

  • cargo test -p perry: all green (incl. new apply_wear_overlay regression + idempotency tests).
  • Live, end-to-end on a Wear OS 5 (API 34) arm64 emulator: perry run wearos compiled the Android ELF .so, built a watch APK (verified android.hardware.type.watch + wearable.standalone + minSdk 30 + bundled libperry_app.so), installed, and launched. libperry_app.so loaded over JNI without crash and the perry/ui Text+Button tree rendered as native Android views on the round watch face.

Adds docs/src/platforms/wearos.md (wired into nav + cross-linked) and docs/examples/platforms/ui/wearos_app.ts.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Wear OS (--target wearos) with aliases (wear, wear-os) for build, run, and publishing workflows.
    • Enabled full Wear OS app builds and deployment by reusing the Android pipeline with a Wear-specific Gradle overlay.
    • Added Wear OS UI example and expanded platform documentation, including a dedicated Wear OS guide.
  • Bug Fixes

    • Improved Gradle reliability by canonicalizing the gradlew path.
    • Made the Wear overlay injection idempotent and adjusted Wear OS handling across stripping and TLS/linking behaviors.
  • Documentation

    • Linked Wear OS from platform overviews and updated supported-platform counts and examples.

…aros)

Wear OS is Android on a watch, so this reuses the perry-ui-android backend
and the aarch64-linux-android toolchain; only the APK packaging differs.
`Platform::Wearos` (+ `wearos`/`wear` aliases) cross-compiles the same .so as
`android` and packages it through a watch form-factor overlay applied to the
Android Gradle template: the android.hardware.type.watch feature, the
com.google.android.wearable.standalone meta-data, the androidx.wear dependency,
and minSdk 30 (the Google Play floor for watch APKs). Everything else (jniLibs,
companion .so bundling, signing, install, logcat) is shared with the phone path,
so no new UI crate or CI exclusion is needed.

Also fixes three issues found while bringing the target up on a real Wear OS
emulator, all of which also affect the existing Android path:

- android_cross_env now sets AR=llvm-ar. NDK r27+ removed the per-target
  `aarch64-linux-android-ar` wrapper that cc-rs probes for, so the runtime/
  stdlib C deps (mimalloc) failed to cross-build.
- resolve_target_triple maps `wearos` -> aarch64-unknown-linux-android so
  codegen emits Android ELF objects (it was falling back to the host Mach-O
  triple, producing `.o: unknown file type` at link).
- the `gradlew` invocation uses an absolute path. A relative `android-build/
  gradlew` combined with `.current_dir(&build_dir)` resolved against the new
  cwd (`android-build/android-build/gradlew`) and failed `perry run` with ENOENT.

Verified end-to-end on a Wear OS 5 (API 34) arm64 emulator: `perry run wearos`
compiles the Android ELF .so, builds a watch APK (watch feature + standalone +
bundled libperry_app.so), installs, and launches; libperry_app.so loads over
JNI without crash and the perry/ui Text+Button tree renders as native Android
views. Adds an apply_wear_overlay regression test, a docs page, and an example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Wear OS as a fully supported Perry target platform by aliasing it to the Android pipeline throughout the compile, link, run, and publish subsystems. Introduces build_and_run_wearos backed by a new apply_wear_overlay function that patches the Android Gradle template for watch form-factor deployment, plus a complete documentation page and example.

Changes

Wear OS Platform Support

Layer / File(s) Summary
Platform enum and CLI wiring
crates/perry/src/main.rs, crates/perry/src/commands/run/mod.rs
Platform::Wearos variant added to the Platform enum with Clap docs; parse_platform extended with wearos/wear/wear-os aliases; RunArgs platform list and module-level re-exports (build_and_run_wearos, is_wear_os_device) updated; needs_cross includes wearos.
Compile pipeline: wearos aliased to android
crates/perry-codegen/src/codegen/helpers.rs, crates/perry/src/commands/compile.rs, crates/perry/src/commands/compile/app_metadata.rs, crates/perry/src/commands/compile/lock_scan.rs, crates/perry/src/commands/compile/resolve/native_library.rs, crates/perry/src/commands/compile/post_link.rs
wearos mapped to the aarch64-*-android LLVM/Rust triples, the android lockfile and bundle-section keys, the arm64 arch token, is_android/is_mobile detection booleans, and the strip-skip early-exit.
Compile pipeline: library search, linking, and optimized libs
crates/perry/src/commands/compile/library_search.rs, crates/perry/src/commands/compile/link/mod.rs, crates/perry/src/commands/compile/optimized_libs.rs
wearos added to Android-family branches in find_ui_library, find_geisterhand_ui, build_geisterhand_libs, build_optimized_libs (NDK env, TLS flags, UI bitcode crate), and build_and_run_link; android_cross_env updated to derive archiver from llvm-ar and export AR_<triple> for NDK r27+.
Run: Wear OS device detection and resolution
crates/perry/src/commands/run/devices.rs, crates/perry/src/commands/run/entry.rs
New is_wear_os_device helper probes ADB devices for watch characteristics; resolve_target adds a Platform::Wearos branch detecting Android devices and filtering to Wear OS watches, returning (Some("wearos"), Some(serial)); rust_target_triple maps wearos to aarch64-linux-android.
Run: Wear OS Gradle overlay and dispatch
crates/perry/src/commands/run/android.rs, crates/perry/src/commands/run/launch.rs
build_and_run_android refactored into build_and_run_android_impl(wear); new build_and_run_wearos public entry point; apply_wear_overlay patches AndroidManifest.xml (watch feature, standalone meta-data) and app/build.gradle.kts (minSdk 30, androidx.wear:wear:1.3.0); gradlew path canonicalized; launch dispatcher adds "wearos" arm; unit tests verify correctness and idempotency.
Publish command
crates/perry/src/commands/publish/mod.rs, crates/perry/src/commands/publish/preflight.rs
Platform::Wearos mapped to "android" in both run() target hint and run_async() target name; added to the app_type = "gui" preflight set.
Documentation and examples
docs/src/platforms/wearos.md, docs/examples/platforms/ui/wearos_app.ts, docs/src/SUMMARY.md, docs/src/platforms/overview.md, docs/src/platforms/android.md, docs/src/widgets/wearos.md, README.md
New wearos.md platform page covering requirements, compile/run flow, lifecycle, state, platform detection, config, and limitations; TypeScript counter example; SUMMARY and platform overview counts updated; Wear OS added to README table and compile example.

Sequence Diagram

sequenceDiagram
    participant CLI as perry run wearos
    participant Entry as resolve_target
    participant Launch as launch dispatcher
    participant Impl as build_and_run_android_impl
    participant Overlay as apply_wear_overlay
    participant Gradle as gradlew (canonicalized path)

    CLI->>Entry: Platform::Wearos
    Entry->>Entry: detect ADB devices via adb
    Entry-->>Launch: (Some("wearos"), Some(serial))
    Launch->>Impl: build_and_run_wearos(so_path, bundle_id, serial, format)
    Impl->>Impl: cross-compile → libperry_app.so
    Impl->>Impl: copy Android Gradle template
    Impl->>Overlay: apply_wear_overlay(template_dir)
    Overlay->>Overlay: patch AndroidManifest.xml (watch feature + standalone meta-data)
    Overlay->>Overlay: patch app/build.gradle.kts (minSdk 30, androidx.wear dep)
    Overlay-->>Impl: Ok(())
    Impl->>Gradle: assembleDebug → sign → install → logcat
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PerryTS/perry#5085: Both PRs update the publish preflight logic to treat a platform variant as a GUI app by extending the platform-to-app-type mapping.

Poem

🐰 A new watch face blooms on the wrist today,
The rabbit hops from Android all the way —
wearos aliased, the overlay applied,
Manifests patched, with minSdk set to pride,
Round screens rejoice, as gradlew runs straight,
One more platform shipped — isn't that great! ⌚

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a full Wear OS app target with CLI support (--target wearos / perry run wearos).
Description check ✅ Passed The description comprehensively covers the summary, changes, testing, and includes proper documentation updates, following the template structure and providing concrete implementation details.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wearos-app-target

Comment @coderabbitai help to get the list of available commands and usage tips.

Fixes the `lint` CI job (cargo fmt --all -- --check).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry/src/commands/run/entry.rs`:
- Around line 112-126: After calling detect_android_devices() in the wearos
command handler, filter the returned devices list to include only Wear OS
capable devices before performing the is_empty() check and device selection
logic. Add a filter operation that validates each device's Wear OS capability
(check device properties or API level support for Wear OS) so that only
compatible devices are available for auto-selection when len == 1 or for
prompting via pick_device() when multiple devices exist.

In `@crates/perry/src/main.rs`:
- Around line 80-82: The Platform::Wearos variant in the ValueEnum used by the
publish --platform command is missing Clap aliases, while the run command's
manual parsing accepts "wear" and "wear-os" as alternatives to "wearos". Add
Clap alias attributes to the Wearos variant to accept both "wear" and "wear-os"
as valid CLI input values, ensuring consistent behavior across both the run and
publish commands.

In `@docs/examples/platforms/ui/wearos_app.ts`:
- Line 3: The platform comment at line 3 currently lists only desktop platforms
(macos, linux, windows) but this is a Wear OS example file, making it appear
desktop-only despite being Wear-focused. Update the comment to include Wear
OS/Android platforms to accurately reflect the actual platforms this example
demonstrates.

In `@docs/src/platforms/overview.md`:
- Line 15: The intro sentence at line 3 states "9 platform families" but with
the addition of the Wear OS platform row in the table, this count is now
outdated. Update the platform family count in the introductory sentence to
reflect the new total number of platform families now listed in the table (which
should be 10 after adding Wear OS).

In `@docs/src/platforms/wearos.md`:
- Around line 38-39: The documentation currently recommends using x86/x86_64
Wear images on Intel hosts in the wearos.md file, which conflicts with the
implemented ABI path that uses aarch64-linux-android with arm64-v8a native
libraries. Remove the second sentence (the guidance about Intel hosts using
x86/x86_64 images) to align the documentation with the actual implementation and
prevent users from attempting incompatible native library configurations.

In `@docs/src/widgets/wearos.md`:
- Around line 5-7: Remove the blank line between the two consecutive blockquote
lines in the wearos.md file. The blockquote line starting with "For full **Wear
OS apps**" and the blockquote line starting with "**Status:**" should be
adjacent to each other with no blank line in between, as required by the MD028
Markdown lint rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e6050df-bf0a-4b15-9d36-1e2a446de8fe

📥 Commits

Reviewing files that changed from the base of the PR and between 08819f0 and 211639d.

📒 Files selected for processing (22)
  • crates/perry-codegen/src/codegen/helpers.rs
  • crates/perry/src/commands/compile.rs
  • crates/perry/src/commands/compile/app_metadata.rs
  • crates/perry/src/commands/compile/library_search.rs
  • crates/perry/src/commands/compile/link/mod.rs
  • crates/perry/src/commands/compile/lock_scan.rs
  • crates/perry/src/commands/compile/optimized_libs.rs
  • crates/perry/src/commands/compile/post_link.rs
  • crates/perry/src/commands/compile/resolve/native_library.rs
  • crates/perry/src/commands/publish/mod.rs
  • crates/perry/src/commands/publish/preflight.rs
  • crates/perry/src/commands/run/android.rs
  • crates/perry/src/commands/run/entry.rs
  • crates/perry/src/commands/run/launch.rs
  • crates/perry/src/commands/run/mod.rs
  • crates/perry/src/main.rs
  • docs/examples/platforms/ui/wearos_app.ts
  • docs/src/SUMMARY.md
  • docs/src/platforms/android.md
  • docs/src/platforms/overview.md
  • docs/src/platforms/wearos.md
  • docs/src/widgets/wearos.md

Comment thread crates/perry/src/commands/run/entry.rs Outdated
Comment thread crates/perry/src/main.rs
@@ -0,0 +1,27 @@
// demonstrates: minimal Wear OS App() lifecycle snippet from the Wear OS docs page
// docs: docs/src/platforms/wearos.md
// platforms: macos, linux, windows

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix misleading platform header comment for this Wear OS example.

The comment at Line 3 currently excludes Wear OS/Android, which makes this snippet look desktop-only despite the file and surrounding comments being Wear-focused.

💡 Suggested doc tweak
-// platforms: macos, linux, windows
+// platforms: wearos, android, macos, linux, windows
📝 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.

Suggested change
// platforms: macos, linux, windows
// platforms: wearos, android, macos, linux, windows
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/examples/platforms/ui/wearos_app.ts` at line 3, The platform comment at
line 3 currently lists only desktop platforms (macos, linux, windows) but this
is a Wear OS example file, making it appear desktop-only despite being
Wear-focused. Update the comment to include Wear OS/Android platforms to
accurately reflect the actual platforms this example demonstrates.

Comment thread docs/src/platforms/overview.md
Comment thread docs/src/platforms/wearos.md Outdated
Comment thread docs/src/widgets/wearos.md Outdated
…E entry

CodeRabbit review:
- main.rs: clap aliases `wear`/`wear-os` on Platform::Wearos (parity with the
  run command's manual parsing).
- run/entry.rs: filter `perry run wearos` device detection to actual watches
  (ro.build.characteristics contains "watch") so a paired phone on the same adb
  isn't selected; add run/devices.rs::is_wear_os_device.
- docs/platforms/overview.md: 9 -> 10 platform families (Wear OS row added).
- docs/platforms/wearos.md: drop the incorrect "use x86/x86_64 Wear image on
  Intel hosts" guidance — Perry's Wear APKs are arm64-only (arm64-v8a .so).
- docs/widgets/wearos.md: fix MD028 (blank line between blockquotes).
  (Skipped the suggestion to add android/wearos to the example's `// platforms:`
  header: that directive routes CI host lanes, and the example validates the
  perry/ui API on host backends exactly like watchos_app.ts.)

Docs/README:
- Flesh out the wearos.md Requirements with the full verified toolchain
  (JDK 17 for AGP 8.8.2, Gradle 8.x, NDK r27+, arm64 Wear image + AVD) and a
  one-time setup block.
- Add Wear OS to the README UI-targets table and cross-platform compile examples.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/perry/src/commands/run/devices.rs (1)

257-269: 💤 Low value

Consider exact token matching for ro.build.characteristics parsing.

The current substring match .contains("watch") works for typical values like "watch" or "watch,phone", but a more precise approach would split the comma-separated property and match tokens exactly. This would avoid theoretical false positives from substrings (e.g., if a future Android variant used "stopwatch" or similar, though very unlikely for this system property).

♻️ More precise token-based matching
 pub fn is_wear_os_device(serial: &str) -> bool {
     Command::new("adb")
         .args(["-s", serial, "shell", "getprop", "ro.build.characteristics"])
         .output()
         .ok()
         .filter(|o| o.status.success())
-        .map(|o| String::from_utf8_lossy(&o.stdout).contains("watch"))
+        .map(|o| {
+            String::from_utf8_lossy(&o.stdout)
+                .trim()
+                .split(',')
+                .any(|token| token.trim() == "watch")
+        })
         .unwrap_or(false)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/run/devices.rs` around lines 257 - 269, The
is_wear_os_device function currently uses a substring match with
.contains("watch") to check the ro.build.characteristics property, which could
theoretically match false positives if the property contains "watch" as part of
a larger token (e.g., "stopwatch"). Replace the substring matching logic with
exact token matching by splitting the comma-separated property value and
checking if any of the resulting tokens exactly equals "watch" rather than using
contains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/perry/src/commands/run/devices.rs`:
- Around line 257-269: The is_wear_os_device function currently uses a substring
match with .contains("watch") to check the ro.build.characteristics property,
which could theoretically match false positives if the property contains "watch"
as part of a larger token (e.g., "stopwatch"). Replace the substring matching
logic with exact token matching by splitting the comma-separated property value
and checking if any of the resulting tokens exactly equals "watch" rather than
using contains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bddec5c-cb81-4c78-ba82-69902ee558fa

📥 Commits

Reviewing files that changed from the base of the PR and between f1fb6be and 957246e.

📒 Files selected for processing (8)
  • README.md
  • crates/perry/src/commands/run/devices.rs
  • crates/perry/src/commands/run/entry.rs
  • crates/perry/src/commands/run/mod.rs
  • crates/perry/src/main.rs
  • docs/src/platforms/overview.md
  • docs/src/platforms/wearos.md
  • docs/src/widgets/wearos.md
✅ Files skipped from review due to trivial changes (4)
  • docs/src/widgets/wearos.md
  • docs/src/platforms/overview.md
  • README.md
  • docs/src/platforms/wearos.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/perry/src/main.rs
  • crates/perry/src/commands/run/mod.rs
  • crates/perry/src/commands/run/entry.rs

@proggeramlug proggeramlug merged commit 6ad14ef into main Jun 15, 2026
15 checks passed
@proggeramlug proggeramlug deleted the feat/wearos-app-target branch June 15, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant