Zurück zum Blog
updaterdevtoolsrefactorcommunitymilestone

Auto-Update, ein Live-Inspector und der Compiler, der sich selbst halbiert hat

This dated article records the Perry version and measurements available when it was published. Use the current limitations, platform guide, and benchmark artifact for decisions today.

Der letzte Beitrag endete bei v0.5.306 mit der Gen-GC + JSON + Benchmarks-Story. Vier Tage später ist Perry bei v0.5.359 — das sind 53 Patch-Releases — und die Geschichte ist wieder eine andere. Keines dieser Releases ist eine Schlagzeile mit Benchmark-Zahlen. Fast alle sind Issues aus dem Tracker, die geschlossen werden.

  • perry/updater ist da — Auto-Update für Desktop-Apps in Sparkle/Tauri-Manier (Ed25519 über einen SHA-256-Digest, Sentinel-Rollback, Detached Relaunch). Community-PR von TheHypnoo (#224).
  • Geisterhand Phase D — ein Live-Inspector unter http://localhost:7676 mit Widget-Baum, Per-Widget-Detail, Click-Dispatch und Live-Style-Edit über POST /style/:h.
  • Das Compiler-Refactoring. Über v0.5.329 → v0.5.343 wurden die vier am häufigsten genannten Dateien zerlegt: lower::lower_expr 6.687 → 624 LOC (−91 %), compile.rs 9.391 → 3.783 LOC (−60 %), lower.rs 13.591 → 7.554 LOC (−44 %), lower_call.rs 7.000+ → 4.681 LOC (−33 %). Neuer walker.rs verwandelt die _ => -Catch-all-Bug-Klasse in einen Compile-Error.
  • UI-Styling Phase C abgeschlossen — Inline-style: { ... }-Props auf jedem Widget über Apple, Android, GTK4, Windows und Web. Windows bekommt 4 von 5 Stubs verdrahtet (Decoration / Opacity / Borders); nur widget.shadow bleibt offen (DirectComposition-Follow-up).
  • Ein Scoop-Bucket für Windows: scoop install perry-ts/perry. SHA-256-Sidecars im Release-Workflow.
  • Welle von Community-Issue-Fixes — etwa 30 Issues geschlossen über Runtime, Codegen, fetch, GTK4, Windows-Linker, Async und Stdlib.

1. perry/updater — Auto-Update für Desktop-Apps

Vor dem Fix hatte Perry keinen Update-Pfad. Apps wurden ausgeliefert, und das war's. TheHypnoo öffnete #224 mit der ganzen Story:

import { initUpdater, checkForUpdate, markHealthy } from "@perry/updater";

initUpdater(); // Sentinel-Rollback, falls der vorherige Start gecrasht ist

const update = await checkForUpdate({
  manifestUrl: "https://example.com/updates/manifest.json",
  publicKey: "<ed25519 raw 32-byte hex>",
  currentVersion: "1.4.0",
});

if (update) {
  await update.download((pct) => console.log(`${pct}%`));
  await update.installAndRelaunch();
}

markHealthy(); // aufrufen, nachdem der neue Build erfolgreich gestartet ist

Trust-Modell: Ed25519 über den SHA-256-Digest der Datei (nicht über die Datei-Bytes — das hält die Verifikation bei großen Binärdateien günstig). Das Manifest ist JSON, schema-versioniert, ein Eintrag pro <os>-<arch>-Tripel. Atomare Installation mit <exe>.prev-Backup, Detached Relaunch (setsid auf Unix, DETACHED_PROCESS auf Windows). Mobile ist bewusst ausgeschlossen — App Store / Play Store kontrollieren die Installation auf OS-Ebene.

Beim Schreiben des Smoke-Tests tauchten zwei Perry-Runtime-Eigenheiten auf, die direkt mitgefixt wurden:

  • response.arrayBuffer() lieferte einen Metadaten-Stub. Gefixt in #232 (auch TheHypnoo) — js_response_array_buffer alloziert jetzt einen echten BufferHeader und memcpyt resp.body hinein.
  • fs.appendFileSync schrieb 0 Bytes. Gefixt in #226 — der Namespace-Import-Lowering-Pfad (import * as fs from "fs") hatte keinen Arm für appendFileSync, und der LLVM-Codegen hatte ebenfalls keinen Arm für die HIR-Variante. Beide jetzt verdrahtet.

Die Dokumentation lebt unter docs/src/updater/overview.md.

2. Geisterhand: Live-Inspector unter localhost:7676

Geisterhand ist Perrys In-Process-UI-Test-Harness — eine HTTP-API auf Port 7676 zum Snapshotten von Widget-State und Dispatchen von Klicks. Phase D macht daraus einen Devtools-artigen Inspector, den du in jedem Browser öffnen kannst.

  • Schritt 1 (v0.5.349)GET / liefert eine Single-Page-Vanilla-JS-UI mit Widget-Baum, Per-Widget-Detail (Frame, Value, Raw JSON), 1,5 s Auto-Refresh mit Pause/Resume und einem „fire onClick“-Action-Button. Der Codegen pinnt INSPECTOR_HTML gegen das macOS-Lazy-Load--dead_strip, damit es Release-Builds übersteht.
  • Schritt 2 (v0.5.350)POST /style/:h nimmt einen JSON-Prop-Bag und wendet ihn live an. 9 Props (backgroundColor, color, borderColor, borderWidth, borderRadius, opacity, padding, hidden, enabled) fließen vom HTTP-Thread → Main-Thread über die existierende Pump-Queue. Schlechtes JSON → 400; schlechter Handle → 400; unbekannte Props werden serverseitig gefiltert und die Response listet, welche durchgekommen sind.
perry compile main.ts -o app --enable-geisterhand
./app &
open http://localhost:7676
curl -X POST localhost:7676/style/3 \
  -H 'content-type: application/json' \
  -d '{"backgroundColor":"#1a1a1e","opacity":0.8}'
# => {"ok":true,"applied":["backgroundColor","opacity"]}

Der macOS-Dispatcher ist verdrahtet; Linux / Windows / iOS / tvOS / visionOS / Android folgen dem gleichen Muster und sind als Nächstes dran.

3. Das Compiler-Refactoring — die vier größten Dateien zerlegen

Fünf Issues im Tracker (#167, #169, #212, #214, plus ein langer Schwanz) hatten dieselbe Form: Eine neue Expr-Variante wurde zu ir.rs hinzugefügt, aber einer von vier Ad-hoc-Walkern in lower.rs hatte einen _ => -Catch-all und kompilierte die neue Variante stillschweigend falsch. Sowas zur Laufzeit zu fangen ist teuer — manchmal unsichtbar, manchmal ein SIGSEGV unter SSO.

v0.5.329 brachte crates/perry-hir/src/walker.rs mit walk_expr_children / walk_expr_children_mut — exhaustive Matches über alle 178 Expr-Varianten, kein Catch-all. Eine neue Variante hinzuzufügen, ohne sie hier zu listen, ist jetzt ein Compile-Error. Die vier Konsumenten (substitute_locals, find_max_local_id::check_expr, collect_local_refs_expr, remap_local_ids_in_expr) kollabierten:

FunktionVorherNachherΔ
find_max_local_id::check_expr22557−75 %
substitute_locals55380−86 %
collect_local_refs_expr72070−90 %
remap_local_ids_in_expr54285−84 %

Summe: −1.830 Zeilen duplizierter Descent, ersetzt durch +1.840 Zeilen eines zentralisierten Walkers — netto flach, aber die Bug-Klasse ist weg.

Das schaltete den Rest frei. v0.5.331 → v0.5.343 zerlegten die vier Monolithen über 14 Commits. Die Headline-Zahlen:

DateiVorherNachherΔ
lower::lower_expr6.687624−91 %
compile.rs9.3913.783−60 %
lower.rs13.5917.554−44 %
lower_call.rs7.000+4.681−33 %

Der Split landete als 19 neue, fokussierte Sub-Module: compile/{parse_cache, strip_dedup, library_search, object_cache, resolve, collect_modules, optimized_libs, targets, link}.rs, lower/{expr_misc, expr_function, expr_object, expr_call, expr_member, expr_assign, expr_new}.rs, lower_call/{ui_styling, builtin, native}.rs, plus eine neue crates/perry-dispatch-Crate, die zur Single Source of Truth für UI- / System- / i18n-Method-Tables wurde (das _ => "perry_ui_unknown"-Fan-out, das die „kompiliert auf macOS, kaputt im Web“-Überraschungen aus Issue #191 getrieben hatte, ist jetzt ein einziges Lookup).

Tier-4-Perf-Wins kamen mit (v0.5.335–v0.5.336):

  • Zwei Passes in inline_functions und drei rayon-Passes in compile.rs fusioniert — spart 5 Modul-Scans + 3 Scheduler-Round-Trips pro Compile.
  • Den perry dev-Parse-Cache bei 500 Einträgen begrenzt, FIFO-Eviction. Vor dem Fix konnte eine Session, die node_modules durchläuft, 100+ MB SWC-AST halten.
  • Die Post-Codegen-.ll-Write-Schleife parallelisiert — 2–4× schnellere Wall-Time auf SSDs mit 50+ Modulen.
  • Arc<I18nTable> statt die Locale-Tabelle pro Worker zu klonen.

Workspace-Tests blieben bei 434 passed / 0 failed / 5 ignored über jeden Commit; Gap-Tests bei 25/28 Baseline; Doc-Tests bei 80/82 Baseline.

4. UI-Styling Phase C, abgeschlossen

Phase C war der Inline-style: { ... }-Rollout. Schritte 1–7 schlossen in diesem Fenster:

  • v0.5.305 → v0.5.306StyleProps-Type-Surface + Inline-style: auf Button.
  • v0.5.307 → v0.5.309 — Color/Padding/Shadow-Inline-Destructure auf jedem Tabellen-Widget, dann VStack / HStack.
  • v0.5.310 → v0.5.311 — Hex-Strings + Gradient + Runtime-parseColor für dynamische Werte.
  • v0.5.312 — Styling-Docs + Windows-Tracking-Issue.

Dann der Cross-Platform-Sweep:

  • GTK4 (#202, #206) — 4 Styling-FFIs verdrahtet, plus 7 fehlende FFIs, die das Linux-Doc-Tests-Gate blockierten (v0.5.322).
  • macOS (v0.5.324) — CALayer-Shadow-Plumbing für widget.shadow + visual_test-Infrastruktur; set_color-Class-Probe für Nicht-NSTextField-Widgets.
  • iOS / tvOS / visionOS (v0.5.346) — Button-color: ... traf setTextColor: auf UIButton, der diesen Selektor nicht implementiert; objc2-Panic überquerte eine extern "C"-Grenze und der Prozess brach ab. Über das gleiche Class-Probe-Muster wie macOS gefixt — UIButton routet jetzt durch setTitleColor:forState:UIControlStateNormal.
  • Windows (v0.5.347) — 4 von 5 Styling-Stubs verdrahtet (text.decoration via LOGFONT-Round-Trip, widget.opacity via WS_EX_LAYERED + SetLayeredWindowAttributes, Borders via SetWindowSubclass + WM_PAINT). Nur widget.shadow bleibt offen (braucht DirectComposition).

Die Styling-Matrix in docs/src/ui/styling-matrix.md beendet das Fenster mit Web bei 43/43 Wired, Windows bei 42/43 Wired, der Rest bei voller Abdeckung.

5. Der Runtime-Korrektheits-Pass — Issue für Issue

Ein Thema des Zeitraums: Jeder Miscompile, der über den Tracker reinkam, wurde entweder zu einem Fix oder zu einem Compile-Time-Error. Highlights:

  • #212 (v0.5.323) — Klassen-Methoden innerhalb von fn konnten umschließende Fn-Locals nicht capturen. Multi-Module-Repros matchen jetzt Node Byte-für-Byte.
  • #214 (v0.5.321 + v0.5.330) — SSO-sicheres String-Handle-Unboxing über 7 String-Operand-Sites: arr.join, arr.toString, obj[stringKey] get/set/delete, string.match(re), process.env[dynKey], Crypto-Digest-Input. Vor dem Fix lieferte jede dieser Stellen entweder stumm Müll oder SIGSEGV bei Inline-String-Operanden.
  • #221 (v0.5.351) — Modul-Level leere const-Arrays verloren arr[i]=-Writes aus Funktionen heraus. Aufgetaucht, als Bloom-Engine/jumps discoverLevels() LEVEL_FILES auf Modul-Level via Index-Assign befüllte und der Level-Select-Screen leer rauskam.
  • #233 (v0.5.357)Array.push aus einer Async-Funktion war stumm bei 16 Elementen gedeckelt, wenn das Array als Parameter übergeben wurde. Async-Funktionen werden nicht inlined; Reallocation gab einen neuen Pointer zurück, den der Caller nie sah. Fix: bei jedem Wachstum einen Forwarding-Pointer an der alten Position installieren, wiederverwendet wird der GC-eigene GC_FLAG_FORWARDED-Mechanismus.
  • #235 (v0.5.358) — Method-Default-Param-Dispatch lieferte Müll, wenn Caller nachgestellte Args wegließen. Zwei Anteile: Cross-Module-Method-Declares hardcodierten 6 Doubles statt arity + 1, und lower_class_method hatte überhaupt keinen build_default_param_stmts-Aufruf. Aufgetaucht in mongodbs findOne(filter, options = {}), das stumm hing; Fix ist einheitlich über lokalen + Cross-Module-Dispatch.
  • #236 (v0.5.355) — drei unabhängige fetch- + Promise-Bugs aus einem Repro: api.github.com 403'te anonym (Default-User-Agent jetzt gesetzt), .then(console.log) hing ewig (Null-Callbacks pushten keine TASK_QUEUE-Einträge), jede fetch-Rejection druckte Uncaught exception: [object Object] (NaN-geboxter blanker *StringHeader statt eines echten ErrorHeader).
  • #234 (v0.5.359) — echte Blob mit arrayBuffer / text / bytes / slice-Instance-Methoden. Vor dem Fix gab await response.blob() einen Metadaten-Stub {size, type} zurück. Dreiteiliger Fix landete über Runtime + HIR + Codegen.

Plus die kleinen Catch-ups:

  • #181 — Strip-Dedup über-prunte generische Monomorphisierungen unter Linux + GTK4-Link-Silent-Fallback. Fix: Name-Pattern-Filter durch Symbol-Set-Vergleich via llvm-nm ersetzen. Members mit auch nur einem unique Symbol bleiben drin. libperry_ui_macos.a getrimmt 196 → 35 Objekte ohne Link-Errors.
  • #220secur32.lib zur Windows-Link-Line hinzugefügt.
  • #198 — i18n-FormatNumber-FP-Round-Trip via Ryū.
  • #188 — Codegen-Dispatch für perry/i18n-Format-Wrapper verdrahtet.
  • #189 / #203perry/plugin-Codegen-Dispatch.
  • #190 — Canvas-Widget durch LLVM-Codegen.
  • #191 — CameraView durch Codegen.
  • #192 — Table-Widget durch Codegen.
  • #193 (teilweise) — 11 Stdlib-Helper-Dispatch-Arms.
  • #98 — Background-Receive-Notifications auf iOS + Android (Warm-Path).
  • #106 — watchOS-Weak-Fallbacks für Game-Loop-FFI-Hooks.
  • #154using / await using-Dispose-Hooks.
  • #167js_native_call_method-Args-Alloca in den Entry-Block gehoist.
  • #169substitute_locals-Uint8Array-Arms.
  • #226fs.appendFileSync Ende-zu-Ende verdrahtet (Community-PR).

6. Windows + Scoop

Die Windows-Toolchain-Story vereinfacht sich weiter. v0.5.353 pinnte clang -target bei Host-Builds — Nicht-MSVC-clang im PATH (MinGW / MSYS2 / Anaconda / Rust-GNU-Bundles) schrieb Perrys x86_64-pc-windows-msvc-IR stillschweigend zu windows-gnu um, und lld-link konnte die __main-Referenz, die LLVMs mingw32-Emitter eingefügt hatte, nicht auflösen. Neuer probe_clang_default_triple ruft einmal pro Prozess clang --version auf und druckt einen einzigen Info-Hinweis, wenn der Host-Default GNU ist, wir aber MSVC targeten. Mit PERRY_NO_CLANG_PROBE=1 unterdrücken.

v0.5.345 brachte das Win64-perry-ui-ABI mit perry-dispatch in Einklang — drei Runtime-Extern-Signaturen waren auseinandergedriftet (perry_ui_navstack_create, perry_ui_menu_add_item_with_shortcut, perry_ui_app_set_timer). Auf Win64-ABI teilen sich Integer- und Float-Positions-Args dieselben Slot-Indices, also liest ein Mismatch Müll aus uninitialisierten Registern. SysV (macOS / Linux) nutzt separate Int/Float-Register-Pools und landete zufällig valide Bits — Windows-only-Crash, gefixt über alle 8 perry-ui-*-Plattform-Crates.

Dann: scoop install perry-ts/perry. Manifest gepinnt auf v0.5.345 (mit depends: main/llvm, um offizielles MSVC-Default-LLVM automatisch zu ziehen). Der Release-Workflow emittiert jetzt <artifact>.sha256-Sidecars neben jedem Archiv, im sha256sum-kompatiblen Format für jeden Downstream-Package-Manager-Bumper.

# Windows-Host
scoop bucket add perry-ts https://github.com/PerryTS/perry
scoop install perry-ts/perry
perry compile src\main.ts --target windows -o myapp.exe

7. Abschluss

Das Muster dieser Phase ist Community-Engagement plus interne Hygiene. TheHypnoo lieferte drei substanzielle PRs (#224 perry/updater, #231 fs.appendFileSync-Wiring, #232 response.arrayBuffer-Body-Bytes). Der Tracker leerte sich um etwa 30 Issues. Der Compiler wurde auf seiner größten Datei 60 % kleiner und bekam einen exhaustive Walker, der „hab vergessen, einen von vier Ad-hoc-Walkern zu updaten“ von einem Runtime-Miscompile in einen cargo build-Error verwandelt. UI-Styling erreichte Parität über jede Desktop-Plattform außer Shadows auf Windows. Geisterhand bekam eine Browser-basierte Devtools-Oberfläche. Der Windows-Install-Pfad wurde einen Befehl kürzer.

Probier's aus:

# npm (jede Plattform)
npm install @perryts/perry
npx perry compile src/main.ts -o myapp && ./myapp

# Homebrew (macOS)
brew install PerryTS/perry/perry

# Scoop (Windows)
scoop bucket add perry-ts https://github.com/PerryTS/perry
scoop install perry-ts/perry

# Auto-Update für Desktop-Apps
npm install @perry/updater

# Live-Inspector
perry compile main.ts -o app --enable-geisterhand
./app &  # dann http://localhost:7676 öffnen

Source: github.com/PerryTS/perry — Issues: github.com/PerryTS/perry/issues — Changelog: CHANGELOG.md

— Ralph

Gefällt dir dieser Beitrag? Hol dir den nächsten.

Kurze Notizen zu Perry-Releases und woran wir als Nächstes arbeiten.

Your email is sent to Resend for the Perry newsletter. You can request unsubscribe or deletion at any time. Privacy notice