diff --git a/CLAUDE.md b/CLAUDE.md index ec53d6b5e..71e30bef9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 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 LLVM for code generation. -**Current Version:** 0.5.493 +**Current Version:** 0.5.494 ## TypeScript Parity Status @@ -150,6 +150,7 @@ First-resolved directory cached in `compile_package_dirs`; subsequent imports re Keep entries to 1-2 lines max. Full details in CHANGELOG.md. +- **v0.5.494** — Two fixes that together unblock `perry compile` for GTK4 GUI apps on Ubuntu / Debian / Fedora hosts. **(1) perry-hir lib.rs**: commit `66c5e74f` (v0.5.489) added `lower_module_with_class_id_types_and_seed,` to the `pub use lower::{...}` block in `crates/perry-hir/src/lib.rs:29` but the function definition was never pushed to `main` (it lives in an in-progress branch). Result: `cargo build -p perry-hir` on `origin/main` HEAD failed with `error[E0432]: unresolved import lower::lower_module_with_class_id_types_and_seed`, so any source build of perry from main was broken — local devs and CI could only build via the cached optimized libs. Fix: drop the stale export line. No callers on main reference the function yet (the matching `collect_modules.rs` call site is also in the in-progress branch), so the removal is a no-op for behavior. The function will return when the larger feature it belongs to is ready to land. **(2) Closes #423**: `perry compile src/main.ts` for a basic GTK4 GUI app on Ubuntu 25.10 failed at link with `undefined reference to symbol 'gst_message_parse_buffering'` + `/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0: error adding symbols: DSO missing from command line` even with `libgtk-4-dev` + `libgstreamer1.0-dev` installed. Root cause: v0.5.440 (#371) added `gstreamer = "0.23"` to `crates/perry-ui-gtk4/Cargo.toml` for the perry/media playbin backend, so the trimmed `libperry_ui_gtk4.a` retains the gstreamer-rs objects regardless of whether the user's program imports `perry/media`. But the Linux GTK4 link branch in `crates/perry/src/commands/compile/link.rs:1283-1361` only emitted GTK4 libs (`-lgtk-4 -lgio-2.0 ...`) — `pkg-config --libs gtk4` doesn't transitively reference the gstreamer-1.0 sonames, and the user's `RUSTFLAGS=-l gstreamer-1.0 ...` workaround was a dead-end since `perry compile` invokes `cc` directly for the final link (RUSTFLAGS is dropped). **Fix in one place** (link.rs, immediately after the existing `-lpulse-simple -lpulse` line): try `pkg-config --libs gstreamer-1.0 gstreamer-base-1.0 gstreamer-app-1.0 gstreamer-video-1.0 gstreamer-audio-1.0` and append the result; on pkg-config failure fall back to hardcoded `-lgstreamer-1.0 -lgstbase-1.0 -lgstapp-1.0 -lgstvideo-1.0 -lgstaudio-1.0` (the actual library short-names — Debian/Ubuntu ship `libgstbase-1.0.so.0`, not `libgstreamer-base-1.0.so.0`, despite the pkg-config name being `gstreamer-base-1.0`). The 5 libs cover what gstreamer-rs's playbin path touches (the `query_position` / `query_duration` / message-bus iteration / appsink / video-overlay / audio-sink calls in `media_playback.rs`). Same pkg-config → hardcoded-fallback shape as the existing GTK4 block above. Fallback path emits a warning that names the apt + dnf packages (`libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev` / `gstreamer1-devel gstreamer1-plugins-base-devel`). The `[strip-dedup] rlib NOT found` line the user also reported is benign — it's a fallback path that triggers symbol-based dedup instead, exactly what strip-dedup is designed to do when the companion .rlib isn't shipped (and we don't ship it). Verified: `cargo build --release -p perry` clean. End-to-end on Ubuntu 25.10 needs the user's host since GitHub runners are 22.04/24.04 — the fix matches the gstreamer-rs build's link expectations and the pre-existing GTK4 pkg-config probe shape that's been load-bearing on Linux since v0.5.181. - **v0.5.493** — codegen-arkts: button styling fidelity on HarmonyOS to match the macOS Apple-style reference. Three concrete fixes in `crates/perry-codegen-arkts/src/lib.rs`. (1) **`mutator_background_color` now takes `bindings` and uses `numeric_arg_resolved`**: pre-fix it only matched bare `Lit::Num` literals at each of the 4 channel positions, so Mango's `widgetSetBackgroundColor(btn, moR, moG, moB, 1.0)` (where `moR/moG/moB` are const-bindings holding the 0.99/0.62/0.10 mango-orange channels) silently dropped the modifier — Mango's "+ New Connection" button rendered with ArkUI's default blue Capsule. Now resolves through const-bindings + Conditional folding via the existing 16-hop chaser: emits `.backgroundColor('rgba(255, 159, 28, 1)')` correctly. (2) **New `buttonSetTextColor` handler**: emits `.fontColor('rgba(R,G,B,A)')` resolved through bindings. Mango's white-on-orange button text now visible. (3) **New `buttonSetBordered(btn, 0)` handler**: emits `.backgroundColor(Color.Transparent)` when bordered=0; truthy is a no-op (default is the bordered Capsule). Mango's About button now flat instead of the default blue ArkUI Capsule. New no-op `buttonSetTitle` handler so the trace doesn't leak `// perry/ui mutator buttonSetTitle not yet handled` comments — title is set at construction time on harmonyos. **End-to-end on Mango via OHOS Pura emulator** (visual screenshot match against macOS reference): orange "+ New Connection" CTA with white text, flat About button (no blue), feature pills with orange-tinted backgrounds, white welcome card on cream background. The 4 remaining `[unrecognized body]` placeholders are deep inside browser/info screens (only visible after a connection is added — out of scope for the welcome-screen pass). cargo test passing. - **v0.5.492** — codegen-arkts polish that gets Mango's welcome screen to a pixel-perfect render on HarmonyOS. Three concrete defects: (1) **`textSetFontWeight` arg semantics**: Perry's signature is `(widget, size: number, weight: number)` mirroring Apple's `systemFont(ofSize:weight:)` where `weight` is a 0..1 normalized scale (0=Thin/100, 0.5=Regular/400, 1.0=Bold/900). My v0.5.485 impl read arg[1] (the size, e.g. 24) AS the weight and emitted `.fontWeight(24)` which ArkUI's strict 100..900 range clamped to 100 (lightest). Mango's "Welcome to Mango" rendered translucent because of this. Fix: read arg[1] as size + arg[2] as weight scale; map 0..1 → 100..900 rounded to nearest 100; emit `.fontSize(N).fontWeight(M)` together. (2) **`resolve()` chases LocalGet chains**: pre-fix it did one resolution step. Phase B of the inliner introduces aliasing chains — `const disconnectBtn = makeDangerBtn(...)` becomes `const disconnectBtn = LocalGet(remapped_btn)` after the call gets inlined; emit_widget needs to chase past these aliases to find the actual NativeMethodCall(Button, ...). 16-hop cap mirrors the other resolvers. Fixed 6 of the 10 `[unrecognized body]` islands in Mango's emit. (3) **HarmonyOS-stubbed function recognition**: `evaluate_condition` and `numeric_arg_resolved` now recognize calls to `isDarkMode` / `getDeviceIdiom` / `getDeviceModel` / `getDeviceOSVersion` / `isHighContrast` / `isReducedMotion` / `getNotchHeight` (the v0.5.477 build.rs auto-stubs all return zero on HarmonyOS) and treat them as `Lit::Num(0.0)`. Mango's `const dark = isDarkMode()` now folds to `dark = false`, so theme color resolution `txR = dark ? 0.91 : 0.17` picks the light-mode branch instead of the heuristic-pick-then-branch fallback. Was: dark text `rgba(232, 233, 237, 1)` (white-ish) on white background = invisible. Now: light-theme text `rgba(43, 45, 66, 1)` (proper dark gray) clearly readable. **`is_harmonyos_zero_fn` allowlist** + matching arms in `to_lit` (Expr::Call → ExternFuncRef + Expr::NativeMethodCall → perry/system module). **Inline budget bumped 32 → 256** for completeness; Mango uses well under that. **End-to-end on Mango via OHOS Pura emulator**: 10 → 4 unrecognized-body placeholders (the remaining 4 are deep inside browser/info screens that aren't visible from the welcome state — accessed only after a connection is added). Welcome screen visually matches macOS reference: brand toolbar with logo, bold dark "Welcome to Mango" title, description text, all 4 feature pills, "+ New Connection" CTA, analytics notice, About button. One pre-existing test updated (`text_styling_mutators_emit_arkui_modifiers`) for the new textSetFontWeight signature: pass `Number(28.0), Number(1.0)` instead of `Number(700.0)`, assert `.fontWeight(900)` instead of `.fontWeight(700)`. cargo test 95+2=97 passing. - **v0.5.491** — codegen-arkts: four follow-ups to v0.5.489's harvest inliner that complete the welcome-screen rendering on Mango/HarmonyOS. (1) **Dead-branch elim for unfoldable conditions**: `collect_mutations_in_stmt` now treats Stmt::If as folded-true (walks only the then-branch) when the condition can't be evaluated AND won't serialize cleanly via `is_cleanly_serializable_condition`. Pre-fix both branches were recorded as conditional mutations and rendered as `if (true) { ... } else { ... }`, producing duplicate-content emission (Mango's "+ New Connection" appearing twice — once from the welcome card's CTA button in the then-branch, once from the addMoreBtn in the post-if siblings). (2) **i18n `t()` unwrapping**: `resolve_string_arg` now follows `Expr::I18nString { key, ... }` (perry/i18n's namespaced-import shape) and `Expr::NativeMethodCall { module: "perry/i18n", method: "t", args: [I18nString] }` (the destructured-import shape) to their underlying string. For Mango (and most apps using Perry's i18n) the English source text doubles as the key, so this gives readable English text on platforms where dynamic locale switching isn't wired yet. (3) **Recursive expression-level inlining (Phase B of inliner)**: walks every Stmt's expressions and substitutes `Expr::Call { callee: FuncRef(id) }` AND `Expr::Call { callee: LocalGet(id) where bindings[id] = Expr::Closure }` with the function's return value, hoisting the function body's let-and-mutator statements BEFORE the enclosing Stmt. Mango's `function makePill(label) { ... return pill; }` is declared INSIDE refreshConnectionList (so it's a `Stmt::Let { id, init: Closure }` not a module-level function); the four `makePill('Databases & Collections')` etc. calls in the welcome card now inline correctly, surfacing the feature-pill text. Simple-return shape constraint (last `Stmt::Return(Some(LocalGet(id)))`) covers makePill's pattern; more complex returning-fns punt. (4) **Early-return → if/else rewrite**: `rewrite_early_returns` runs on each cloned function body before the local-id remap. Pattern `if (cond) { ...; return; } ` rewrites to `if (cond) { ... } else { }` so the dead-branch elim's then-branch pick correctly drops `` (Mango's refreshConnectionList: the welcome card branch had a trailing `return`, the connection-list build code lived as siblings AFTER the if; without this rewrite the after-if code emitted unconditionally alongside the welcome card content). The rewrite recurses into nested if/while/for/do-while bodies. **`emit_text` now uses bindings**: `Text(t('Welcome to Mango'))` previously fell through to `[non-literal Text arg]` because the resolved string-arg path required a String literal at the leaf. Now it falls through `resolve_string_arg` (with the new i18n unwrap arms). Two pre-existing tests updated to match the new dead-branch-elim semantics: `issue_408_conditional_widget_add_child_emits_if_else` (was pinning both-branches-emit, now asserts then-branch-only) and `issue_413_addchild_inside_unfoldable_runtime_condition_still_emits_if` (renamed to `issue_490_unfoldable_unresolvable_condition_walks_only_then_branch`). End-to-end on Mango via the OHOS Pura emulator: cumulative harvest output 28667 → 25329 bytes (slight shrink from removing duplicate else-branch content), 14 → 10 unrecognized-body placeholders, 8 → 7 closure registrations. Visible welcome screen: brand toolbar with logo + title, "Welcome to Mango" header, "Connect to your MongoDB instance..." hint, all 4 feature pills (Databases & Collections / Query & Filter / Edit & Insert / Index Viewer), "+ New Connection" CTA (single occurrence), analytics notice, About button — visually matches the macOS reference. cargo test 95+2=97 passing. diff --git a/Cargo.lock b/Cargo.lock index f813759cf..00e973fde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4430,7 +4430,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "atty", @@ -4483,7 +4483,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "log", @@ -4495,7 +4495,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-hir", @@ -4504,7 +4504,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-hir", @@ -4512,7 +4512,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-dispatch", @@ -4522,7 +4522,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-hir", @@ -4531,7 +4531,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "base64", @@ -4544,7 +4544,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-hir", @@ -4552,7 +4552,7 @@ dependencies = [ [[package]] name = "perry-diagnostics" -version = "0.5.493" +version = "0.5.494" dependencies = [ "serde", "serde_json", @@ -4560,11 +4560,11 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.493" +version = "0.5.494" [[package]] name = "perry-doc-tests" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "clap", @@ -4579,7 +4579,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-diagnostics", @@ -4592,7 +4592,7 @@ dependencies = [ [[package]] name = "perry-jsruntime" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "deno_core", @@ -4611,7 +4611,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-diagnostics", @@ -4623,7 +4623,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "base64", @@ -4646,7 +4646,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.493" +version = "0.5.494" dependencies = [ "aes", "aes-gcm", @@ -4713,7 +4713,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "perry-hir", @@ -4723,7 +4723,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.493" +version = "0.5.494" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -4731,11 +4731,11 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.493" +version = "0.5.494" [[package]] name = "perry-ui-android" -version = "0.5.493" +version = "0.5.494" dependencies = [ "itoa", "jni", @@ -4749,7 +4749,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.493" +version = "0.5.494" dependencies = [ "rand 0.8.6", "serde", @@ -4759,7 +4759,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.493" +version = "0.5.494" dependencies = [ "cairo-rs", "gstreamer", @@ -4774,7 +4774,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.493" +version = "0.5.494" dependencies = [ "block2", "libc", @@ -4789,7 +4789,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.493" +version = "0.5.494" dependencies = [ "block2", "libc", @@ -4807,11 +4807,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.493" +version = "0.5.494" [[package]] name = "perry-ui-tvos" -version = "0.5.493" +version = "0.5.494" dependencies = [ "block2", "libc", @@ -4826,7 +4826,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.493" +version = "0.5.494" dependencies = [ "block2", "libc", @@ -4841,7 +4841,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.493" +version = "0.5.494" dependencies = [ "block2", "libc", @@ -4854,7 +4854,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.493" +version = "0.5.494" dependencies = [ "libc", "perry-runtime", @@ -4866,7 +4866,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.493" +version = "0.5.494" dependencies = [ "base64", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index dd4d39251..8f2566a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ opt-level = "s" # Optimize for size in stdlib opt-level = 3 [workspace.package] -version = "0.5.493" +version = "0.5.494" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry-hir/src/lib.rs b/crates/perry-hir/src/lib.rs index 73dabbe69..cf156a90a 100644 --- a/crates/perry-hir/src/lib.rs +++ b/crates/perry-hir/src/lib.rs @@ -26,6 +26,5 @@ pub use js_transform::{ }; pub use lower::{ lower_module, lower_module_with_class_id, lower_module_with_class_id_and_types, - lower_module_with_class_id_types_and_seed, }; pub use monomorph::monomorphize_module; diff --git a/crates/perry/src/commands/compile/link.rs b/crates/perry/src/commands/compile/link.rs index 9d6edf87c..8e69d686d 100644 --- a/crates/perry/src/commands/compile/link.rs +++ b/crates/perry/src/commands/compile/link.rs @@ -1359,6 +1359,64 @@ pub(super) fn build_and_run_link( } // PulseAudio for audio capture (only needed with UI) cmd.arg("-lpulse-simple").arg("-lpulse"); + // GStreamer libs — pulled in by perry-ui-gtk4's gstreamer-rs + // dep (added in v0.5.440 for the perry/media playbin backend). + // GTK4's pkg-config doesn't transitively reference the + // gstreamer-1.0 sonames, so the `-lgstreamer-1.0` (and the + // base/app/video/audio sublibs that gstreamer-rs's playbin + // path touches) have to land on the link line explicitly or ld + // fails with `undefined reference to gst_message_parse_buffering` + // + `DSO missing from command line` (#423). Same pkg-config → + // hardcoded-fallback shape as the GTK4 block above. + let mut got_gst_libs = false; + let gst_pc_out = Command::new("pkg-config") + .args([ + "--libs", + "gstreamer-1.0", + "gstreamer-base-1.0", + "gstreamer-app-1.0", + "gstreamer-video-1.0", + "gstreamer-audio-1.0", + ]) + .output(); + if let Ok(ref output) = gst_pc_out { + if output.status.success() { + let libs = String::from_utf8_lossy(&output.stdout); + for flag in libs.trim().split_whitespace() { + cmd.arg(flag); + } + got_gst_libs = true; + } + } + if !got_gst_libs { + eprintln!( + "Warning: `pkg-config --libs gstreamer-1.0 ...` did not \ + return GStreamer linker flags ({}). Falling back to a \ + hardcoded GStreamer link set — install \ + `libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev` \ + (Debian/Ubuntu) or `gstreamer1-devel \ + gstreamer1-plugins-base-devel` (Fedora/RHEL) to silence \ + this warning.", + match &gst_pc_out { + Err(e) => format!("pkg-config not runnable: {e}"), + Ok(o) if !o.status.success() => format!( + "pkg-config exited {}: {}", + o.status.code().unwrap_or(-1), + String::from_utf8_lossy(&o.stderr).trim() + ), + Ok(_) => "no output".to_string(), + } + ); + for lib in [ + "-lgstreamer-1.0", + "-lgstbase-1.0", + "-lgstapp-1.0", + "-lgstvideo-1.0", + "-lgstaudio-1.0", + ] { + cmd.arg(lib); + } + } } else if is_windows { // Win32 system libs already linked above } else {