Auto-update, un inspector live et le compilateur qui s'est réduit de moitié
Le précédent billet s'est arrêté à v0.5.306 sur l'histoire gen-GC + JSON + benchmarks. Quatre jours plus tard, Perry est en v0.5.359 — soit 53 patch releases — et l'histoire est encore différente. Aucune de ces releases n'est un titre à coups de chiffres de benchmark. Presque toutes sont des issues du tracker qui se ferment.
perry/updaterarrive — auto-update façon Sparkle/Tauri pour les apps desktop (Ed25519 sur un digest SHA-256, sentinel-rollback, relancement détaché). PR communautaire de TheHypnoo (#224).- Geisterhand Phase D — un inspector live à
http://localhost:7676avec arbre des widgets, détail par widget, dispatch de clic et édition de style en direct viaPOST /style/:h. - Le refactor du compilateur. Sur v0.5.329 → v0.5.343, les quatre fichiers les plus cités ont été découpés :
lower::lower_expr6 687 → 624 LOC (−91 %),compile.rs9 391 → 3 783 LOC (−60 %),lower.rs13 591 → 7 554 LOC (−44 %),lower_call.rs7 000+ → 4 681 LOC (−33 %). Le nouveauwalker.rstransforme la classe de bug du catch-all_ =>en erreur de compilation. - Le styling UI Phase C boucle — props inline
style: { ... }sur chaque widget, sur Apple, Android, GTK4, Windows et Web. Windows obtient 4 stubs sur 5 câblés (decoration / opacity / borders) ; il ne reste quewidget.shadow(suite à venir avec DirectComposition). - Un bucket Scoop pour Windows :
scoop install perry-ts/perry. Sidecars SHA-256 dans le workflow de release. - Vague de fixes d'issues communautaires — environ 30 issues fermées sur runtime, codegen, fetch, GTK4, linker Windows, async et stdlib.
1. perry/updater — auto-update pour apps desktop
Avant ce fix, Perry n'avait aucune voie de mise à jour. Les apps étaient publiées, et puis voilà. TheHypnoo a ouvert #224 avec toute l'histoire :
import { initUpdater, checkForUpdate, markHealthy } from "@perry/updater";
initUpdater(); // sentinel-rollback si le précédent lancement a crashé
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(); // à appeler après que le nouveau build a démarré correctementModèle de confiance : Ed25519 sur le digest SHA-256 du fichier (pas sur les octets du fichier — la vérification reste bon marché sur les gros binaires). Le manifest est en JSON, versionné par schéma, une entrée par triplet <os>-<arch>. Installation atomique avec backup <exe>.prev, relancement détaché (setsid sur Unix, DETACHED_PROCESS sur Windows). Le mobile est exclu par design — App Store / Play Store contrôlent l'installation au niveau OS.
Deux particularités du runtime Perry sont apparues en écrivant le smoke test, et ont été fixées dans la foulée :
response.arrayBuffer()renvoyait un stub de métadonnées seules. Fixé dans #232 (encore TheHypnoo) —js_response_array_bufferalloue désormais un vraiBufferHeaderetmemcpyresp.bodydedans.fs.appendFileSyncécrivait 0 octet. Fixé dans #226 — le chemin de lowering namespace-import (import * as fs from "fs") n'avait pas de bras pourappendFileSync, et le codegen LLVM n'avait pas non plus de bras pour la variante HIR. Les deux désormais câblés.
La documentation vit dans docs/src/updater/overview.md.
2. Geisterhand : inspector live à localhost:7676
Geisterhand a été le harnais de tests UI in-process de Perry — une API HTTP sur le port 7676 pour snapshotter l'état des widgets et dispatcher des clics. La Phase D le transforme en inspector style devtools que l'on peut ouvrir depuis n'importe quel navigateur.
- Étape 1 (v0.5.349) —
GET /sert une UI vanilla-JS mono-page avec arbre des widgets, détail par widget (frame, value, raw JSON), auto-refresh de 1,5 s avec pause/reprise, et un bouton « tirer onClick ». Le codegen épingleINSPECTOR_HTMLcontre le lazy-load-dead_stripde macOS pour qu'il survive aux release builds. - Étape 2 (v0.5.350) —
POST /style/:hprend un sac de props JSON et l'applique en direct. 9 props (backgroundColor,color,borderColor,borderWidth,borderRadius,opacity,padding,hidden,enabled) traversent du thread HTTP → thread principal via la pump-queue existante. JSON invalide → 400 ; handle invalide → 400 ; les props inconnues sont filtrées côté serveur et la réponse liste celles qui sont passées.
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"]}Le dispatcher macOS est câblé ; Linux / Windows / iOS / tvOS / visionOS / Android suivent le même schéma et sont les prochains.
3. Le refactor du compilateur — découper les quatre plus gros fichiers
Cinq issues du tracker (#167, #169, #212, #214, plus une longue queue) avaient la même forme : une nouvelle variante d'Expr ajoutée à ir.rs, mais l'un des quatre walkers ad-hoc dans lower.rs avait un catch-all _ => et compilait silencieusement la nouvelle variante de travers. Attraper ça à l'exécution coûte cher — parfois invisible, parfois un SIGSEGV sous SSO.
v0.5.329 a introduit crates/perry-hir/src/walker.rs avec walk_expr_children / walk_expr_children_mut — matches exhaustifs sur les 178 variantes d'Expr, pas de catch-all. Ajouter une nouvelle variante sans la lister ici devient une erreur de compilation. Les quatre consommateurs (substitute_locals, find_max_local_id::check_expr, collect_local_refs_expr, remap_local_ids_in_expr) ont fondu :
| Fonction | Avant | Après | Δ |
|---|---|---|---|
find_max_local_id::check_expr | 225 | 57 | −75 % |
substitute_locals | 553 | 80 | −86 % |
collect_local_refs_expr | 720 | 70 | −90 % |
remap_local_ids_in_expr | 542 | 85 | −84 % |
Total : −1 830 lignes de descente dupliquée, remplacées par +1 840 lignes d'un walker centralisé — net plat, mais la classe de bug disparaît.
Cela a débloqué le reste. v0.5.331 → v0.5.343 ont taillé les quatre monolithes sur 14 commits. Les chiffres de couverture :
| Fichier | Avant | Après | Δ |
|---|---|---|---|
lower::lower_expr | 6 687 | 624 | −91 % |
compile.rs | 9 391 | 3 783 | −60 % |
lower.rs | 13 591 | 7 554 | −44 % |
lower_call.rs | 7 000+ | 4 681 | −33 % |
Le découpage a atterri sous forme de 19 nouveaux sous-modules ciblés : 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 une nouvelle crate crates/perry-dispatch devenue la source unique de vérité pour les tables de méthodes UI / system / i18n (le fan-out _ => "perry_ui_unknown" qui causait les surprises « compile sur macOS, casse sur web » de l'issue #191 est désormais un seul lookup).
Les wins perf de Tier 4 ont accompagné (v0.5.335–v0.5.336) :
- Fusion de deux passes dans
inline_functionset de trois passes rayon danscompile.rs— économise 5 scans de module + 3 allers-retours du scheduler par compilation. - Borné le parse cache de
perry devà 500 entrées, eviction FIFO. Avant le fix, une session traversantnode_modulespouvait retenir plus de 100 Mo d'AST SWC. - Parallélisation de la boucle d'écriture
.llpost-codegen — wall-time 2–4× plus rapide sur SSD avec 50+ modules. Arc<I18nTable>au lieu de cloner la table de locales par worker.
Les tests workspace sont restés à 434 passed / 0 failed / 5 ignored à chaque commit ; gap tests à la baseline 25/28 ; doc-tests à la baseline 80/82.
4. UI styling Phase C, terminée
La Phase C était le rollout de style: { ... } inline. Les étapes 1–7 ont fermé dans cette fenêtre :
- v0.5.305 → v0.5.306 — surface de type
StyleProps+style:inline sur Button. - v0.5.307 → v0.5.309 — destructure inline color/padding/shadow sur chaque widget de table, puis VStack / HStack.
- v0.5.310 → v0.5.311 — chaînes hex + dégradé +
parseColorruntime pour les valeurs dynamiques. - v0.5.312 — docs de styling + issue de tracking Windows.
Puis la passe cross-platform :
- GTK4 (#202, #206) — 4 FFIs de styling câblés, plus 7 FFIs manquants qui bloquaient la porte des doc-tests Linux (v0.5.322).
- macOS (v0.5.324) — plomberie d'ombre
CALayerpourwidget.shadow+ infrastructure visual_test ; sondage de classeset_colorpour les widgets non-NSTextField. - iOS / tvOS / visionOS (v0.5.346) — Button
color: ...tapaitsetTextColor:surUIButton, qui n'implémente pas ce sélecteur ; le panicobjc2traversait une frontièreextern "C"et le processus s'abortait. Fixé via le même pattern de sondage de classe que macOS — UIButton route maintenant viasetTitleColor:forState:UIControlStateNormal. - Windows (v0.5.347) — 4 stubs de styling sur 5 câblés (
text.decorationvia round-tripLOGFONT,widget.opacityviaWS_EX_LAYERED+SetLayeredWindowAttributes, borders viaSetWindowSubclass+WM_PAINT). Il ne reste quewidget.shadow(nécessite DirectComposition).
La matrice de styling dans docs/src/ui/styling-matrix.md termine la fenêtre avec Web à 43/43 Wired, Windows à 42/43 Wired, le reste en couverture complète.
5. La passe de correction du runtime — issue par issue
Un thème de la période : chaque miscompile entré par le tracker s'est terminé en fix ou en erreur de compilation. Les points saillants :
- #212 (v0.5.323) — les méthodes de classe à l'intérieur de
fnne pouvaient pas capturer les locals de la fn englobante. Les repros multi-modules correspondent maintenant à Node octet par octet. - #214 (v0.5.321 + v0.5.330) — unboxing de string-handle SSO-safe sur 7 sites à opérande string :
arr.join,arr.toString,obj[stringKey]get/set/delete,string.match(re),process.env[dynKey], input de digest crypto. Avant le fix, chacun rendait silencieusement du garbage ou faisait SIGSEGV sur des opérandes string inline. - #221 (v0.5.351) — les arrays vides
constau niveau module perdaient les écrituresarr[i]=depuis l'intérieur des fonctions. Apparu quanddiscoverLevels()de Bloom-Engine/jump remplissaitLEVEL_FILESau niveau module via index-assign et l'écran de sélection de niveau s'affichait vide. - #233 (v0.5.357) —
Array.pushdepuis l'intérieur d'une fonction async était silencieusement plafonné à 16 éléments quand l'array entrait en paramètre. Les fonctions async ne sont pas inlinées ; la réallocation rendait un nouveau pointeur que l'appelant ne voyait jamais. Fix : installer un pointeur de forwarding à l'ancienne position à chaque croissance, en réutilisant le mécanismeGC_FLAG_FORWARDEDexistant du GC. - #235 (v0.5.358) — le dispatch des paramètres par défaut de méthode passait du garbage quand les appelants omettaient des args en queue. Deux contributeurs : les declares de méthode cross-module hardcodaient 6 doubles au lieu d'
arity + 1, etlower_class_methodn'appelait pas du toutbuild_default_param_stmts. Apparu dansfindOne(filter, options = {})de mongodb se bloquant en silence ; le fix est uniforme entre dispatch local et cross-module. - #236 (v0.5.355) — trois bugs indépendants fetch + promise depuis un seul repro : api.github.com renvoyait 403 en anonyme (User-Agent par défaut désormais positionné),
.then(console.log)se bloquait pour toujours (les callbacks null ne poussaient pas d'entrées dans la TASK_QUEUE), chaque rejet de fetch imprimaitUncaught exception: [object Object](*StringHeadernu NaN-boxé au lieu d'un vraiErrorHeader). - #234 (v0.5.359) — vrai
Blobavec méthodes d'instancearrayBuffer/text/bytes/slice. Avant le fix,await response.blob()rendait un stub de métadonnées seules{size, type}. Fix en trois parties atterrissant sur runtime + HIR + codegen.
Plus les petits rattrapages :
- #181 — strip-dedup sur-élaguait les monomorphisations génériques sous Linux + silent-fallback du link GTK4. Fix : remplacer le filtrage par pattern de nom par une comparaison d'ensemble de symboles via
llvm-nm. Les membres avec ne serait-ce qu'un symbole unique sont conservés.libperry_ui_macos.aélagué de 196 → 35 objets sans erreurs de link. - #220 —
secur32.libajouté à la ligne de link Windows. - #198 — i18n
FormatNumberround-trip FP via Ryū. - #188 — codegen dispatch câblé pour les wrappers de format
perry/i18n. - #189 / #203 — codegen dispatch
perry/plugin. - #190 — widget Canvas via le codegen LLVM.
- #191 — CameraView via le codegen.
- #192 — widget Table via le codegen.
- #193 (partiel) — 11 bras de dispatch d'helpers stdlib.
- #98 — réception en arrière-plan des notifications sur iOS + Android (warm-path).
- #106 — fallbacks faibles pour les hooks FFI de game-loop watchOS.
- #154 — hooks de dispose
using/await using. - #167 — alloca des args de
js_native_call_methodremontée au bloc d'entrée. - #169 — bras Uint8Array de
substitute_locals. - #226 —
fs.appendFileSynccâblé de bout en bout (PR communautaire).
6. Windows + Scoop
L'histoire de la toolchain Windows continue de se simplifier. v0.5.353 a épinglé clang -target sur les builds host — clang non-MSVC dans le PATH (MinGW / MSYS2 / Anaconda / bundles GNU de Rust) réécrivait silencieusement l'IR x86_64-pc-windows-msvc de Perry vers windows-gnu, et lld-link ne pouvait pas résoudre la référence __main que l'émetteur mingw32 de LLVM insérait. Le nouveau probe_clang_default_triple exécute clang --version une fois par processus et imprime une seule note informative quand le default du host est GNU mais qu'on cible MSVC. Supprimer avec PERRY_NO_CLANG_PROBE=1.
v0.5.345 a aligné l'ABI perry-ui Win64 avec perry-dispatch — trois signatures extern runtime avaient dérivé (perry_ui_navstack_create, perry_ui_menu_add_item_with_shortcut, perry_ui_app_set_timer). Sur ABI Win64, les args positionnels entiers et flottants partagent les indices de slot, donc un mismatch lit du garbage depuis des registres non-initialisés. SysV (macOS / Linux) utilise des pools de registres int/float séparés et faisait par hasard atterrir des bits valides — crash uniquement Windows, fixé sur les 8 crates de plateforme perry-ui-*.
Puis : scoop install perry-ts/perry. Manifest épinglé à v0.5.345 (avec depends: main/llvm pour tirer automatiquement le LLVM officiel default-MSVC). Le workflow de release émet désormais des sidecars <artifact>.sha256 à côté de chaque archive, au format compatible sha256sum pour tout bumper de package manager downstream.
# Host Windows
scoop bucket add perry-ts https://github.com/PerryTS/perry
scoop install perry-ts/perry
perry compile src\main.ts --target windows -o myapp.exe7. Pour conclure
Le motif de cette période est l'engagement communautaire plus l'hygiène interne. TheHypnoo a livré trois PRs significatifs (#224 perry/updater, #231 câblage de fs.appendFileSync, #232 octets de body de response.arrayBuffer). Le tracker s'est vidé d'environ 30 issues. Le compilateur a perdu 60 % sur son plus gros fichier et a poussé un walker exhaustif qui transforme « j'ai oublié de mettre à jour l'un des quatre walkers ad-hoc » d'un miscompile runtime en erreur cargo build. Le styling UI a atteint la parité sur chaque plateforme desktop sauf les ombres sous Windows. Geisterhand a obtenu une surface devtools navigateur. Le chemin d'installation Windows s'est raccourci d'une commande.
Essayez :
# npm (toute plateforme)
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 pour apps desktop
npm install @perry/updater
# Inspector live
perry compile main.ts -o app --enable-geisterhand
./app & # puis ouvrez http://localhost:7676Source : github.com/PerryTS/perry — Issues : github.com/PerryTS/perry/issues — Changelog : CHANGELOG.md
— Ralph