feat(wearos): full Wear OS app target (--target wearos / perry run wearos)#5173
Conversation
…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>
📝 WalkthroughWalkthroughAdds 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 ChangesWear OS Platform Support
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Fixes the `lint` CI job (cargo fmt --all -- --check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
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
📒 Files selected for processing (22)
crates/perry-codegen/src/codegen/helpers.rscrates/perry/src/commands/compile.rscrates/perry/src/commands/compile/app_metadata.rscrates/perry/src/commands/compile/library_search.rscrates/perry/src/commands/compile/link/mod.rscrates/perry/src/commands/compile/lock_scan.rscrates/perry/src/commands/compile/optimized_libs.rscrates/perry/src/commands/compile/post_link.rscrates/perry/src/commands/compile/resolve/native_library.rscrates/perry/src/commands/publish/mod.rscrates/perry/src/commands/publish/preflight.rscrates/perry/src/commands/run/android.rscrates/perry/src/commands/run/entry.rscrates/perry/src/commands/run/launch.rscrates/perry/src/commands/run/mod.rscrates/perry/src/main.rsdocs/examples/platforms/ui/wearos_app.tsdocs/src/SUMMARY.mddocs/src/platforms/android.mddocs/src/platforms/overview.mddocs/src/platforms/wearos.mddocs/src/widgets/wearos.md
| @@ -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 | |||
There was a problem hiding this comment.
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.
| // 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.
…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>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/perry/src/commands/run/devices.rs (1)
257-269: 💤 Low valueConsider exact token matching for
ro.build.characteristicsparsing.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
📒 Files selected for processing (8)
README.mdcrates/perry/src/commands/run/devices.rscrates/perry/src/commands/run/entry.rscrates/perry/src/commands/run/mod.rscrates/perry/src/main.rsdocs/src/platforms/overview.mddocs/src/platforms/wearos.mddocs/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
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
Viewsystem — so this reuses the entireperry-ui-androidbackend andaarch64-linux-androidtoolchain rather than introducing a parallel UI crate. The compiled.sois 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-osaliases) cross-compiles the same.soasandroid, thenbuild_and_run_wearospackages it through a watch form-factor overlay applied to the existing Android Gradle template:android.hardware.type.watchfeaturecom.google.android.wearable.standalonemeta-data +com.google.android.wearableuses-libraryandroidx.wear:weardependencyminSdkraised to 30 (Wear OS 3 — the Google Play floor for watch APKs)Everything else (jniLibs, companion
.sobundling, assets, applicationId, deep links, signing, install, logcat) is shared with the phone path.Bug fixes (also affect the existing
androidpath)Found while bringing the target up on a real Wear OS emulator:
android_cross_envnow setsAR=llvm-ar. NDK r27+ removed the per-targetaarch64-linux-android-arwrapper that cc-rs probes for, so the runtime/stdlib C deps (mimalloc) failed to cross-build.resolve_target_triplemapswearos→aarch64-unknown-linux-androidso codegen emits Android ELF objects (it was falling back to the host Mach-O triple →.o: unknown file typeat link).gradlewinvocation uses an absolute path. A relativeandroid-build/gradlewcombined with.current_dir(&build_dir)resolved against the new cwd (android-build/android-build/gradlew) →perry runfailed with ENOENT.Testing
cargo test -p perry: all green (incl. newapply_wear_overlayregression + idempotency tests).perry run wearoscompiled the Android ELF.so, built a watch APK (verifiedandroid.hardware.type.watch+wearable.standalone+minSdk 30+ bundledlibperry_app.so), installed, and launched.libperry_app.soloaded over JNI without crash and theperry/uiText+Button tree rendered as native Android views on the round watch face.Adds
docs/src/platforms/wearos.md(wired into nav + cross-linked) anddocs/examples/platforms/ui/wearos_app.ts.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
--target wearos) with aliases (wear,wear-os) for build, run, and publishing workflows.Bug Fixes
gradlewpath.Documentation