From 541937368c5eeea637e3372cdf32b3c9a29c7b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 18 May 2026 02:58:58 +0200 Subject: [PATCH] feat(perry/system): getOSVersion() bug-report-flow utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a tiny perry/system entry point that returns the OS version string for the running platform — a frequent need for crash-report flows / telemetry payloads that want to send (app version, OS version, device model) tuples. import { getOSVersion } from "perry/system"; const ver = getOSVersion(); // e.g. "Version 14.5 (Build 23F79)" | Platform | Status | |------------|-----------------------------------------------------------------| | **macOS** | Real impl: [[NSProcessInfo processInfo] operatingSystemVersionString] | | iOS | Stub + follow-up warning (UIDevice.systemVersion) | | tvOS | Stub + follow-up (UIDevice.systemVersion is available) | | watchOS | Stub + follow-up | | visionOS | Stub + follow-up (UIKit UserDefaults path) | | Android | Stub + follow-up (Build.VERSION.RELEASE via JNI) | | Windows | Stub + follow-up (RtlGetVersion) | | Linux/GTK4 | Stub + follow-up (/etc/os-release or uname -r) | | WASM | Stub via dispatch table fallback | | HarmonyOS | Stub auto-generated by perry-runtime/build.rs | Pairs naturally with the existing getDeviceModel / getAppVersion / getBundleId surfaces so bug-report flows can build a complete diagnostic tuple in one place. [[NSProcessInfo processInfo] operatingSystemVersionString] returns the human-readable form like "Version 14.5 (Build 23F79)" — kept as-is so triage can grep the build number too. NSString → UTF-8 bytes via UTF8String + lengthOfBytesUsingEncoding:NSUTF8StringEncoding (= 4). - perry-api-manifest/src/entries.rs — method row. - perry-dispatch/src/lib.rs — MethodRow with no args, ReturnKind::F64 (matches getDeviceModel's string-as-i64 convention). WASM falls through to ui_method_to_runtime() — symbol mapping for free. - Per-platform exports across all UI crates (real on macOS, stub elsewhere). - perry-ui-test/src/lib.rs — Feature row. - HarmonyOS auto-stubbed via perry-runtime/build.rs. - docs/api/perry.d.ts + docs/src/api/reference.md regenerated via ./scripts/regen_api_docs.sh. App({body: VStack([Text("os: " + getOSVersion())])}) + console.log against release perry; symbol resolves end-to-end, link succeeds, binary writes cleanly. --- crates/perry-api-manifest/src/entries.rs | 5 ++++ crates/perry-dispatch/src/lib.rs | 10 +++++++ crates/perry-ui-android/src/lib.rs | 14 ++++++++++ crates/perry-ui-gtk4/src/lib.rs | 15 +++++++++++ crates/perry-ui-ios/src/lib.rs | 14 ++++++++++ crates/perry-ui-macos/src/lib.rs | 33 ++++++++++++++++++++++++ crates/perry-ui-test/src/lib.rs | 14 ++++++++++ crates/perry-ui-tvos/src/lib.rs | 15 +++++++++++ crates/perry-ui-visionos/src/lib.rs | 13 ++++++++++ crates/perry-ui-watchos/src/lib.rs | 13 ++++++++++ crates/perry-ui-windows/src/lib.rs | 14 ++++++++++ docs/api/perry.d.ts | 4 ++- docs/src/api/reference.md | 3 ++- 13 files changed, 165 insertions(+), 2 deletions(-) diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index f3d558a9d..3d43c8c47 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -2087,6 +2087,11 @@ pub static API_MANIFEST: &[ApiEntry] = &[ method("perry/system", "isDarkMode", false, None), method("perry/system", "getDeviceIdiom", false, None), method("perry/system", "getDeviceModel", false, None), + // Bug-report-flow utility: stable OS-version string per + // platform (e.g. `"15.2"`, `"macOS 14.5"`, `"Android 14"`). + // Common need for crash reports and telemetry; pairs with + // getDeviceModel / getAppVersion. + method("perry/system", "getOSVersion", false, None), method("perry/system", "getLocale", false, None), method("perry/system", "getAppVersion", false, None), method("perry/system", "getAppBuildNumber", false, None), diff --git a/crates/perry-dispatch/src/lib.rs b/crates/perry-dispatch/src/lib.rs index 3109b54f3..a85be1e92 100644 --- a/crates/perry-dispatch/src/lib.rs +++ b/crates/perry-dispatch/src/lib.rs @@ -2204,6 +2204,16 @@ pub static PERRY_SYSTEM_TABLE: &[MethodRow] = &[ args: &[], ret: ReturnKind::F64, }, + // Bug-report-flow utility: stable OS-version string per + // platform. Returns a NaN-boxed JS string the user can splice + // into crash reports / telemetry. Same dispatch shape as + // getDeviceModel. + MethodRow { + method: "getOSVersion", + runtime: "perry_system_get_os_version", + args: &[], + ret: ReturnKind::F64, + }, MethodRow { method: "audioSetOutputFilename", runtime: "perry_system_audio_set_output_filename", diff --git a/crates/perry-ui-android/src/lib.rs b/crates/perry-ui-android/src/lib.rs index 60da71f8e..cfbdeb138 100644 --- a/crates/perry-ui-android/src/lib.rs +++ b/crates/perry-ui-android/src/lib.rs @@ -2169,6 +2169,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Android stub — +/// native impl will read `Build.VERSION.RELEASE` via JNI. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Android getOSVersion (Build.VERSION.RELEASE) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-gtk4/src/lib.rs b/crates/perry-ui-gtk4/src/lib.rs index 88aa94f99..57fc80a82 100644 --- a/crates/perry-ui-gtk4/src/lib.rs +++ b/crates/perry-ui-gtk4/src/lib.rs @@ -1949,6 +1949,21 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. GTK4/Linux +/// stub — native impl would read `/etc/os-release` or +/// `uname -r`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Linux getOSVersion (/etc/os-release) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-ios/src/lib.rs b/crates/perry-ui-ios/src/lib.rs index 30644fd91..5e90ccecd 100644 --- a/crates/perry-ui-ios/src/lib.rs +++ b/crates/perry-ui-ios/src/lib.rs @@ -1541,6 +1541,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. MVP stub on +/// iOS; native impl will use `[[UIDevice currentDevice] systemVersion]`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "iOS getOSVersion not yet implemented (UIDevice.systemVersion follow-up)", + Some("#918"), + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-macos/src/lib.rs b/crates/perry-ui-macos/src/lib.rs index 9330238f9..2d5772e32 100644 --- a/crates/perry-ui-macos/src/lib.rs +++ b/crates/perry-ui-macos/src/lib.rs @@ -2157,6 +2157,39 @@ pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Uses +/// `[[NSProcessInfo processInfo] operatingSystemVersionString]` +/// which returns a human-readable form like `"Version 14.5 +/// (Build 23F79)"` — preserved as-is so triage can grep the build +/// number too. Returned as a Perry-managed string. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { + let cls = objc2::runtime::AnyClass::get(c"NSProcessInfo").unwrap(); + let info: *mut objc2::runtime::AnyObject = objc2::msg_send![cls, processInfo]; + if info.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let s: *mut objc2::runtime::AnyObject = + objc2::msg_send![info, operatingSystemVersionString]; + if s.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let utf8_ptr: *const u8 = objc2::msg_send![s, UTF8String]; + if utf8_ptr.is_null() { + return js_string_from_bytes(std::ptr::null(), 0); + } + let utf8_len: usize = objc2::msg_send![s, lengthOfBytesUsingEncoding: 4u64]; + if utf8_len == 0 { + return js_string_from_bytes(std::ptr::null(), 0); + } + js_string_from_bytes(utf8_ptr, utf8_len as i32) + } +} + /// Set output filename for audio recording. #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { diff --git a/crates/perry-ui-test/src/lib.rs b/crates/perry-ui-test/src/lib.rs index c66e8465c..d1519165d 100644 --- a/crates/perry-ui-test/src/lib.rs +++ b/crates/perry-ui-test/src/lib.rs @@ -1463,6 +1463,20 @@ pub const FEATURES: &[Feature] = &[ web: S, web_name: None, }, + // Bug-report-flow utility: stable OS-version string. macOS has + // the real impl via NSProcessInfo; every other platform is a + // first-call-warning stub awaiting its native implementation. + Feature { + name: "perry_system_get_os_version", + category: SystemApi, + macos: S, + ios: S, + android: S, + gtk4: S, + windows: S, + web: S, + web_name: None, + }, Feature { name: "perry_system_preferences_set", category: SystemApi, diff --git a/crates/perry-ui-tvos/src/lib.rs b/crates/perry-ui-tvos/src/lib.rs index 212c6bf69..98be0c151 100644 --- a/crates/perry-ui-tvos/src/lib.rs +++ b/crates/perry-ui-tvos/src/lib.rs @@ -1327,6 +1327,21 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. tvOS stub — +/// native impl can mirror iOS's `[[UIDevice currentDevice] systemVersion]` +/// (UIDevice is available on tvOS). +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "tvOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-visionos/src/lib.rs b/crates/perry-ui-visionos/src/lib.rs index 9d8fbc7e3..bdb8511f8 100644 --- a/crates/perry-ui-visionos/src/lib.rs +++ b/crates/perry-ui-visionos/src/lib.rs @@ -1448,6 +1448,19 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. visionOS stub. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "visionOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/crates/perry-ui-watchos/src/lib.rs b/crates/perry-ui-watchos/src/lib.rs index 67edbb6b8..5bf320244 100644 --- a/crates/perry-ui-watchos/src/lib.rs +++ b/crates/perry-ui-watchos/src/lib.rs @@ -1200,6 +1200,19 @@ pub extern "C" fn perry_system_audio_get_waveform(_count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { 0 } +/// Bug-report-flow utility: stable OS-version string. watchOS stub. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "watchOS getOSVersion not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { let filename = str_from_header(filename_ptr as *const u8); diff --git a/crates/perry-ui-windows/src/lib.rs b/crates/perry-ui-windows/src/lib.rs index cd4d76382..21887f737 100644 --- a/crates/perry-ui-windows/src/lib.rs +++ b/crates/perry-ui-windows/src/lib.rs @@ -1994,6 +1994,20 @@ pub extern "C" fn perry_system_audio_get_waveform(count: f64) -> f64 { pub extern "C" fn perry_system_get_device_model() -> i64 { audio::get_device_model() } +/// Bug-report-flow utility: stable OS-version string. Windows stub — +/// native impl will use `GetVersionEx` / `RtlGetVersion`. +#[no_mangle] +pub extern "C" fn perry_system_get_os_version() -> i64 { + perry_runtime::stub_diag::perry_stub_warn( + "perry_system_get_os_version", + "Windows getOSVersion (RtlGetVersion) not yet implemented", + None, + ); + extern "C" { + fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; + } + unsafe { js_string_from_bytes(std::ptr::null(), 0) } +} #[no_mangle] pub extern "C" fn perry_system_audio_set_output_filename(filename_ptr: i64) { fn str_from_header(ptr: *const u8) -> &'static str { diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index d6f8e8e40..75ed849d2 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 900 entries across 71 modules +// Coverage: 901 entries across 71 modules declare module "argon2" { /** stdlib */ @@ -736,6 +736,8 @@ declare module "perry/system" { /** stdlib */ export function getLocale(...args: any[]): any; /** stdlib */ + export function getOSVersion(...args: any[]): any; + /** stdlib */ export function imagePickerPick(...args: any[]): any; /** stdlib */ export function isDarkMode(...args: any[]): any; diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 6dbc5f150..3f089f414 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 900 entries across 71 modules. +Total: 901 entries across 71 modules. ## Modules @@ -922,6 +922,7 @@ Total: 900 entries across 71 modules. - `getDeviceIdiom` — module - `getDeviceModel` — module - `getLocale` — module +- `getOSVersion` — module - `imagePickerPick` — module - `isDarkMode` — module - `keychainDelete` — module