Add legacy TypeScript decorator metadata support #1023
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| # Run on version tags so `release-packages.yml` can gate publish on a green | |
| # Tests workflow for the exact commit being released. Direct pushes to main | |
| # do NOT trigger tests — the gates that matter are PRs (pre-merge) and tags | |
| # (pre-release). | |
| push: | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - 'docs/src/**' | |
| - '*.md' | |
| - '!CLAUDE.md' | |
| - '!CHANGELOG.md' | |
| concurrency: | |
| group: test-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| MACOSX_DEPLOYMENT_TARGET: "13.0" | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Lint: cargo fmt --check (formatting gate for every PR) | |
| # --------------------------------------------------------------------------- | |
| lint: | |
| # Was macos-14 — moved to ubuntu-latest in v0.5.428 since `cargo fmt | |
| # --check` is portable. The 6 multiplier-min cut is small in absolute | |
| # terms (lint runs in ~30s) but it's free. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| # PRs read from cache; only main writes new entries. | |
| # Avoids cache thrash from short-lived branches. | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| # --------------------------------------------------------------------------- | |
| # API docs drift gate (#465) | |
| # | |
| # Regenerates `docs/src/api/reference.md` and `docs/api/perry.d.ts` from | |
| # the compile-time manifest in `crates/perry-api-manifest/src/entries.rs`, | |
| # then `git diff --exit-code`s the result. Fails when a code change updated | |
| # the manifest without re-committing the artifacts. Closes the "Release | |
| # workflow regenerates docs automatically (no drift from code)" criterion | |
| # — committing the diff is the easiest way to keep the docs in sync, | |
| # since the diff is reviewable in the PR that introduces it. | |
| # --------------------------------------------------------------------------- | |
| api-docs-drift: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Regenerate API docs | |
| run: ./scripts/regen_api_docs.sh | |
| - name: Check for drift | |
| run: | | |
| if ! git diff --quiet -- docs/src/api/reference.md docs/api/perry.d.ts; then | |
| echo "" | |
| echo "::error::API docs drift detected. The compile-time manifest in" | |
| echo "::error::crates/perry-api-manifest/src/entries.rs changed but the" | |
| echo "::error::generated artifacts under docs/ weren't regenerated." | |
| echo "" | |
| echo "Fix by running:" | |
| echo " ./scripts/regen_api_docs.sh" | |
| echo " git add docs/src/api/reference.md docs/api/perry.d.ts" | |
| echo " git commit -m 'docs: regenerate API reference + .d.ts'" | |
| echo "" | |
| echo "Diff:" | |
| git --no-pager diff --stat -- docs/src/api/reference.md docs/api/perry.d.ts | |
| echo "" | |
| git --no-pager diff -- docs/src/api/reference.md docs/api/perry.d.ts | head -200 | |
| exit 1 | |
| fi | |
| echo "✅ API docs match the manifest." | |
| # --------------------------------------------------------------------------- | |
| # Rust unit tests (266+ tests across all crates) | |
| # --------------------------------------------------------------------------- | |
| # Note: a separate `build` job that produced runtime/stdlib/compiler | |
| # artifacts USED to live here. It only fed `binary-size` (which now does | |
| # its own quick build) — every other job did `cargo build --release` | |
| # itself anyway, so `needs: build` was a serializing barrier with no | |
| # cache benefit. Removed in v0.5.387 along with the `actions/cache@v4` | |
| # blocks (replaced by `Swatinem/rust-cache@v2`, which handles target/ | |
| # pruning intelligently and avoids the disk-pressure issue that | |
| # required the manual simulator-runtime wipe + cache=registry-only | |
| # workaround). Each downstream job below builds in parallel directly. | |
| cargo-test: | |
| # Was macos-14 — moved to ubuntu-latest in v0.5.392 to drop the 10× | |
| # billing weight. cargo-test's `--exclude` list already filters out | |
| # every macOS-specific UI crate (perry-ui-{ios,visionos,tvos, | |
| # watchos,gtk4,android,windows} per the comment block below), so | |
| # the platform-independent test set runs identically on Linux. The | |
| # macOS-host coverage we lose here is negligible — these tests | |
| # don't exercise any platform behavior; they're pure logic + | |
| # codegen. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run cargo test | |
| # Exclude UI backends that don't build on the ubuntu-latest CI image: | |
| # - perry-ui-macos / perry-ui-ios / perry-ui-tvos / perry-ui-watchos | |
| # / perry-ui-visionos: depend on `objc2` which only compiles on | |
| # Apple platforms (`compile_error!` in objc2/src/lib.rs:219). | |
| # - perry-ui-gtk4: needs system pango/gtk via pkg-config; runner | |
| # image doesn't have libgtk-4-dev installed by default. | |
| # - perry-ui-android: needs Android NDK. | |
| # - perry-ui-windows: needs win32 headers. | |
| # - perry-jsruntime: the perry-jsruntime test binary links both | |
| # perry-runtime (with strong V8 stubs gated on `cfg(not(target_os | |
| # = "macos"))` in closure.rs) AND perry-jsruntime (with the real | |
| # V8 FFI impls in interop.rs), causing rust-lld to error on 12 | |
| # duplicate symbols on Linux. macOS uses .weak_definition | |
| # global_asm stubs so the duplicate is resolved at link time; | |
| # Linux ELF doesn't have an equivalent weak escape hatch wired | |
| # up in perry-runtime today (would need #[linkage = "weak"] which | |
| # is nightly-only, or hand-rolled .weak in inline asm). v0.5.297 | |
| # noted that perry-jsruntime tests are independently flaky on | |
| # Linux due to V8/deno_core threading, so excluding here is also | |
| # consistent with that earlier finding. | |
| run: | | |
| cargo test --workspace \ | |
| --exclude perry-ui-macos \ | |
| --exclude perry-ui-ios \ | |
| --exclude perry-ui-visionos \ | |
| --exclude perry-ui-tvos \ | |
| --exclude perry-ui-watchos \ | |
| --exclude perry-ui-gtk4 \ | |
| --exclude perry-ui-android \ | |
| --exclude perry-ui-windows \ | |
| --exclude perry-jsruntime | |
| # --------------------------------------------------------------------------- | |
| # Parity tests (Perry output vs Node.js) | |
| # --------------------------------------------------------------------------- | |
| parity: | |
| # Was macos-14 — moved to ubuntu-latest in v0.5.392. Parity tests | |
| # just compare Perry's stdout against `node --experimental-strip-types`'s | |
| # stdout per test file; both run cleanly on Linux. `gtimeout` on | |
| # macOS is `timeout` on Linux (the run_parity_tests.sh wrapper | |
| # detects either). Node 22+ is installed via setup-node@v4 below. | |
| # 10× billing weight cut. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| - name: Build compiler | |
| run: cargo build --release | |
| - name: Run parity tests | |
| run: ./run_parity_tests.sh | |
| - name: Check parity threshold | |
| run: | | |
| REPORT="test-parity/reports/latest.json" | |
| THRESHOLD_FILE="test-parity/threshold.json" | |
| if [[ ! -f "$REPORT" ]]; then | |
| echo "No parity report found" | |
| exit 1 | |
| fi | |
| ACTUAL=$(jq -r '.summary.parity_percentage' "$REPORT") | |
| MIN=$(jq -r '.min_parity_pct' "$THRESHOLD_FILE") | |
| echo "Parity: ${ACTUAL}% (threshold: ${MIN}%)" | |
| awk -v a="$ACTUAL" -v m="$MIN" 'BEGIN { | |
| if (a+0 < m+0) { print "FAIL: Parity " a "% is below threshold " m "%"; exit 1 } | |
| print "OK: Parity " a "% meets threshold " m "%" | |
| }' | |
| - name: Check for new failures | |
| run: | | |
| REPORT="test-parity/reports/latest.json" | |
| KNOWN="test-parity/known_failures.json" | |
| # Filter empty strings — `run_parity_tests.sh` emits `compile: [""]` | |
| # when there are zero compile failures (a printf+sed quirk in the | |
| # JSON generator), and that empty entry would propagate to a | |
| # spurious "NEW FAILURES: - " line and fail this gate. | |
| jq -r '(.failures.parity // []) + (.failures.compile // []) | .[] | select(. != "")' "$REPORT" | sort -u > /tmp/all_fails.txt | |
| if [[ -f "$KNOWN" ]]; then | |
| jq -r 'keys[]' "$KNOWN" | sort -u > /tmp/known.txt | |
| else | |
| : > /tmp/known.txt | |
| fi | |
| TOTAL=$(wc -l < /tmp/all_fails.txt | tr -d ' ') | |
| comm -23 /tmp/all_fails.txt /tmp/known.txt > /tmp/new.txt | |
| if [[ -s /tmp/new.txt ]]; then | |
| echo "NEW FAILURES (not in known_failures.json):" | |
| sed 's/^/ - /' /tmp/new.txt | |
| exit 1 | |
| fi | |
| echo "All ${TOTAL} failures are known/triaged." | |
| - name: Upload parity report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: parity-report | |
| path: test-parity/reports/latest.json | |
| # Capture per-test compile stderr written by run_parity_tests.sh into | |
| # `test-parity/output/*.compile_error.log` so the long-tail | |
| # macOS-14-only compile failures (tracked as `ci-env` in | |
| # known_failures.json) can finally be diagnosed by reading the actual | |
| # error message rather than inferring from the test family. | |
| - name: Upload compile-error logs | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: parity-compile-errors-${{ runner.os }} | |
| path: test-parity/output/*.compile_error.log | |
| if-no-files-found: ignore | |
| # --------------------------------------------------------------------------- | |
| # Compile smoke test (all 130+ test files must compile) | |
| # --------------------------------------------------------------------------- | |
| compile-smoke: | |
| # Was macos-14 — moved to ubuntu-latest in v0.5.392. The smoke | |
| # compiles every `test-files/*.ts` with the bare `perry foo.ts -o | |
| # out` path; the auto-optimize cache + clang link steps work | |
| # identically on Linux. The v0.5.385 sha256 sidecar is portable | |
| # via `command -v sha256sum || shasum -a 256`. `xargs -P` is | |
| # GNU on Linux (the macos-14 BSD xargs we tuned for behaves the | |
| # same for our usage). Linux runners are 4-vCPU vs macos-14's 3, | |
| # so could try NJOBS=4, but keeping NJOBS=3 + retry conservatively | |
| # for the cargo auto-optimize race (issue tracked separately). | |
| # 10× billing weight cut. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Build compiler | |
| run: cargo build --release | |
| - name: Compile all test files | |
| run: | | |
| set -uo pipefail | |
| export PERRY="$PWD/target/release/perry" | |
| export LOGS_DIR="/tmp/perry_smoke_logs" | |
| # Tests that are known to not compile cleanly under the bare | |
| # `perry foo.ts -o out` smoke path. Sources: | |
| # - test_ui_*: need `--target macos` / `ios-simulator` to pull | |
| # in libperry_ui_*; the no-target compile path doesn't link | |
| # the platform widgets. | |
| # - test_timer: hangs on the runtime event loop under the | |
| # no-arg compile path. | |
| # | |
| # The 11-entry Buffer/typed-array `ci-env` skip family that | |
| # previously lived here (test_gap_buffer_ops, test_buffer_*, | |
| # test_inline_uint8array_param, test_issue_167_*, | |
| # test_issue_227_*, test_gap_fetch_response, | |
| # test_issue_234_blob_methods, etc.) has been removed as of | |
| # PR #239: the actual root cause was a missing | |
| # `module.declare_function("llvm.assume", VOID, &[I1])` in | |
| # `crates/perry-codegen/src/runtime_decls.rs`. Apple Clang | |
| # ≥21 (Xcode 26 / local) auto-recognised the intrinsic; | |
| # Apple Clang 15 (macos-14 runner / Xcode 15.x / LLVM 17) | |
| # errored with `error: use of undefined value '@llvm.assume'`. | |
| # Space-separated skip list (associative arrays require bash | |
| # 4+; macOS-14 ships bash 3.2). Word-boundary match keeps the | |
| # substring lookup safe. | |
| # test_phase2v3_3_show_toast_set_text imports `setText` and | |
| # `showToast` from perry/ui. The cross-platform stubs in | |
| # perry-runtime/src/ui_text_registry.rs ARE meant to make the | |
| # bare-target compile path work (no `--target` flag → host | |
| # build), but the macOS doc-tests + this Linux compile-smoke | |
| # both still fail at link time because the runtime registers | |
| # the macOS-side handler in `perry-ui-macos/src/app_run.rs`, | |
| # which is only linked when `needs_ui = true`. Without | |
| # `--target macos`, perry-ui-macos isn't pulled in, and the | |
| # cross-platform stubs route to a NULL handler → undefined | |
| # symbol. The fix is a separate coordination work item with | |
| # the v3.3 toast/setText worker (PR #322 family); skip-listing | |
| # here unblocks the v9 CI smoke landing without papering over | |
| # the underlying gap. | |
| export SKIP_TESTS=" \ | |
| test_ui_comprehensive \ | |
| test_ui_controls \ | |
| test_ui_phase4 \ | |
| test_timer \ | |
| test_phase2v3_3_show_toast_set_text \ | |
| test_issue_351_media_playback \ | |
| test_issue_442_inline_button_bg \ | |
| test_issue_538_background_tasks \ | |
| test_issue_553_mobile_widgets \ | |
| test_issue_556_table_array \ | |
| test_issue_556_table_concat \ | |
| test_issue_610_foreach \ | |
| test_issue_610_smoke \ | |
| test_issue_640_navstack_textfield \ | |
| test_parity_assert \ | |
| test_parity_async_hooks \ | |
| test_parity_buffer \ | |
| test_parity_child_process \ | |
| test_parity_cluster \ | |
| test_parity_console \ | |
| test_parity_crypto \ | |
| test_parity_dgram \ | |
| test_parity_diagnostics_channel \ | |
| test_parity_decimal \ | |
| test_parity_dns \ | |
| test_parity_dns_promises \ | |
| test_parity_dotenv \ | |
| test_parity_events \ | |
| test_parity_fs \ | |
| test_parity_fs_promises \ | |
| test_parity_http \ | |
| test_parity_http2 \ | |
| test_parity_https \ | |
| test_parity_lodash \ | |
| test_parity_module \ | |
| test_parity_moment \ | |
| test_parity_net \ | |
| test_parity_path \ | |
| test_parity_perf_hooks \ | |
| test_parity_process \ | |
| test_parity_querystring \ | |
| test_parity_readline \ | |
| test_parity_readline_promises \ | |
| test_parity_stream \ | |
| test_parity_stream_consumers \ | |
| test_parity_stream_promises \ | |
| test_parity_stream_web \ | |
| test_parity_sys \ | |
| test_parity_test \ | |
| test_parity_timers \ | |
| test_parity_timers_promises \ | |
| test_parity_tls \ | |
| test_parity_url \ | |
| test_parity_util \ | |
| test_parity_validator \ | |
| test_parity_worker_threads \ | |
| test_parity_zlib " | |
| rm -rf "$LOGS_DIR" | |
| mkdir -p "$LOGS_DIR" | |
| # Worker function. Each test owns a unique marker filename | |
| # (.pass / .fail / .skip) under $LOGS_DIR, so concurrent | |
| # workers never race on the same file. Counts + failure list | |
| # are aggregated below AFTER all workers finish — no shared | |
| # bash-counter state crosses subshell boundaries. Per-test | |
| # stderr is captured to $LOGS_DIR/<name>.compile_error.log so | |
| # the artifact upload step preserves the actual error | |
| # messages (long-tail macOS-14 codegen failures are tracked | |
| # as `ci-env` in test-parity/known_failures.json and are | |
| # otherwise diagnosed by inference, not data). | |
| compile_one() { | |
| local f="$1" | |
| [[ -d "$f" ]] && return 0 | |
| local name | |
| name=$(basename "$f" .ts) | |
| if [[ "$SKIP_TESTS" == *" $name "* ]]; then | |
| : > "$LOGS_DIR/${name}.skip" | |
| return 0 | |
| fi | |
| local err_log="$LOGS_DIR/${name}.compile_error.log" | |
| # Try once. If perry compile fails, sleep 2s then retry. | |
| # The xargs -P parallel pass can race when two workers both | |
| # need an auto-optimize rebuild of the same feature combo — | |
| # the loser's clang sees a momentarily-missing | |
| # `target/perry-auto-<hash>/release/libperry_runtime.a` and | |
| # bails with `errno=2`. Race window is sub-second so a | |
| # single retry after a brief delay is the cheapest fix | |
| # without serializing the workers entirely. `||` short- | |
| # circuits on first success — the success path is unchanged. | |
| if "$PERRY" "$f" -o "/tmp/perry_smoke_${name}" 2>"$err_log" \ | |
| || (sleep 2 && "$PERRY" "$f" -o "/tmp/perry_smoke_${name}" 2>"$err_log"); then | |
| : > "$LOGS_DIR/${name}.pass" | |
| rm -f "/tmp/perry_smoke_${name}" "$err_log" | |
| else | |
| : > "$LOGS_DIR/${name}.fail" | |
| fi | |
| } | |
| export -f compile_one | |
| # ubuntu-latest runners have 4 vCPUs; perry compile is | |
| # CPU-bound for HIR/codegen but waits on the linker (clang) | |
| # for a meaningful chunk of each test. v0.5.384 dropped to | |
| # NJOBS=3 after NJOBS=6 hit a cargo auto-optimize file-lock | |
| # race: two workers rebuilding the same `target/perry-auto- | |
| # <hash>/lib*.a` led to one worker's clang seeing `errno=2` | |
| # mid-link. v0.5.429 closed that race at the source via an | |
| # OS file lock in `commands/compile/optimized_libs.rs:: | |
| # build_optimized_libs` (fslock dep — flock on Unix, | |
| # LockFileEx on Windows; serializes per-hash, parallel across | |
| # different hashes). NJOBS=6 is now safe again. Sequential | |
| # baseline was ~26 min; NJOBS=3 → ~10-12 min; NJOBS=6 → ~6-8 | |
| # min on a 4-vCPU runner. The retry-once in compile_one stays | |
| # as a belt-and-suspenders safety net for any remaining race | |
| # corner the lock doesn't catch. | |
| NJOBS="${PERRY_SMOKE_JOBS:-6}" | |
| printf '%s\n' test-files/*.ts \ | |
| | xargs -P "$NJOBS" -n 1 -I{} bash -c 'compile_one "$@"' _ {} | |
| # Count markers via shopt nullglob + bash array length. Pre-fix | |
| # we used `ls -1 "$LOGS_DIR"/*.fail | wc -l` which fails when | |
| # no matches exist (`ls` exits 1 on missing files), and with | |
| # GH Actions' default `bash -eo pipefail` the failed pipe | |
| # propagates errexit and kills the script BEFORE printing the | |
| # summary line — so a clean run with zero failures still made | |
| # compile-smoke exit 1 (PR #285 / v0.5.379 introduced this). | |
| # nullglob makes an empty glob expand to nothing instead of | |
| # the literal pattern, so the array length is correctly 0. | |
| shopt -s nullglob | |
| pass_files=("$LOGS_DIR"/*.pass) | |
| fail_files=("$LOGS_DIR"/*.fail) | |
| skip_files=("$LOGS_DIR"/*.skip) | |
| PASS=${#pass_files[@]} | |
| FAIL=${#fail_files[@]} | |
| SKIP=${#skip_files[@]} | |
| echo "Compile smoke: $PASS passed, $FAIL failed, $SKIP skipped" | |
| if [[ $FAIL -gt 0 ]]; then | |
| echo "Compile failures:" | |
| for marker in "$LOGS_DIR"/*.fail; do | |
| [[ -e "$marker" ]] || continue | |
| name=$(basename "$marker" .fail) | |
| echo " - $name" | |
| # Surface the head of each failure log directly in the job | |
| # output so a quick scan reveals the underlying error | |
| # without downloading the artifact. | |
| err_log="$LOGS_DIR/${name}.compile_error.log" | |
| if [[ -s "$err_log" ]]; then | |
| echo " --- compile stderr (first 30 lines) ---" | |
| head -n 30 "$err_log" | sed 's/^/ /' | |
| echo " --- end ---" | |
| fi | |
| done | |
| exit 1 | |
| fi | |
| - name: Upload compile-smoke error logs | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: compile-smoke-error-logs | |
| path: /tmp/perry_smoke_logs/*.compile_error.log | |
| if-no-files-found: ignore | |
| # Thread-primitive compile-error tests (#146): checks that closures | |
| # passed to perry/thread primitives with outer-variable writes are | |
| # rejected. The runtime thread_primitives.ts example is covered by | |
| # the doc-tests job below. | |
| - name: Thread-primitive compile-error tests | |
| run: ./scripts/run_thread_tests.sh | |
| # perry/ui styling-matrix CI gate (Phase A of issue #185): verifies | |
| # crates/perry-ui/src/styling_matrix.rs is in sync with every backend's | |
| # lib.rs FFI exports, regenerates docs/src/ui/styling-matrix.md, then | |
| # `git diff --exit-code` catches a forgotten-to-commit regeneration. | |
| # Drift fails CI loudly so a future FFI add/remove can't silently | |
| # land without a matrix update. | |
| - name: UI styling matrix | |
| run: | | |
| ./scripts/run_ui_styling_matrix.sh | |
| git diff --exit-code -- docs/src/ui/styling-matrix.md \ | |
| || (echo "docs/src/ui/styling-matrix.md regenerated; commit the diff" && exit 1) | |
| # Visual styling test ↔ spec consistency (#185 follow-up). | |
| # `docs/examples/ui/styling/visual_test.ts` is the canonical | |
| # comprehensive visual test app; `visual_test.spec.md` documents | |
| # each cell's expected visible signature for human/LLM-aided | |
| # screenshot verification. The two files must stay in lockstep | |
| # — adding a row to the .ts without updating the spec silently | |
| # breaks the verification flow. The actual cross-platform | |
| # compile-test of visual_test.ts is handled by the existing | |
| # run_doc_tests.sh loop downstream. | |
| - name: Visual styling test ↔ spec consistency | |
| run: ./scripts/run_visual_test_check.sh | |
| # Fastify end-to-end integration (#174): launches a Perry-compiled | |
| # Fastify server as a background process, curls four routes covering | |
| # simple GET, path params, POST with JSON body + reply.code(), and | |
| # 404 fallback. The docs Fastify example is marked no-test because | |
| # app.listen() blocks forever; this script provides the coverage | |
| # that the no-test tag would otherwise hide. | |
| - name: Fastify integration tests | |
| run: ./scripts/run_fastify_tests.sh | |
| # Memory-stability regression suite. Two failure modes microbenchs | |
| # don't catch: (1) slow RSS accumulation across 100k-200k iterations | |
| # of allocate-and-discard (would catch a future block-pinning / | |
| # cache-leak / tenuring-trap regression in the gen-GC work), and | |
| # (2) crashes when gc() is forced aggressively during JSON parse, | |
| # deep recursion, or closure init. Each test runs under default, | |
| # PERRY_GEN_GC=1, and PERRY_GEN_GC=1 PERRY_WRITE_BARRIERS=1 so a | |
| # regression in any GC mode is caught. Linux-only because /usr/bin/time | |
| # availability + RSS reporting differs on Windows runners. | |
| - name: Memory stability tests | |
| if: runner.os == 'Linux' || runner.os == 'macOS' | |
| run: ./scripts/run_memory_stability_tests.sh | |
| # --------------------------------------------------------------------------- | |
| # HarmonyOS ArkUI codegen smoke (Phase 2 v9). | |
| # | |
| # The harmonyos compile path produces a 3-part output: the .so (LLVM | |
| # codegen, just like every other backend), the ArkUI Index.ets (emitted | |
| # by perry-codegen-arkts from the harvested perry/ui App({...}) call), | |
| # and the NAPI bridge declarations. End-to-end `perry compile --target | |
| # harmonyos` requires the OpenHarmony SDK (clang + musl sysroot, ~600 | |
| # MB) which isn't pre-installed on ubuntu-latest runners and isn't | |
| # worth downloading every CI run. | |
| # | |
| # What CI CAN cover without the SDK is the codegen-side ArkUI emission | |
| # — the part that's most likely to regress as Phase 2 widgets evolve. | |
| # `crates/perry-codegen-arkts/tests/phase2_full_app_smoke.rs` is the | |
| # comprehensive integration test: constructs a single Module that uses | |
| # every Phase 2 widget shape (state<T>, Tabs, Menu, Grid, LazyVStack | |
| # with .map, ForEach via array.map, inline style: { } with animation/ | |
| # shadow/textDecoration, @app.media image, Toggle/TextField/Slider with | |
| # multi-arg invokeCallback1 closures) and asserts the emitted Index.ets | |
| # contains every canonical pattern v2-v13 added. | |
| # | |
| # Discrete job (separate from cargo-test) so a regression in just one | |
| # widget surfaces as one red cell, not buried in the workspace test | |
| # output. cargo-test ALSO runs these tests as part of `cargo test | |
| # --workspace` — this job is the named visibility, not a duplicate run. | |
| # | |
| # Linker-side validation is covered by manual on-device runs against | |
| # DevEco Studio's Pura 90 Pro Max emulator (see CLAUDE.md v0.5.399+ | |
| # entries for the workflow). | |
| # --------------------------------------------------------------------------- | |
| harmonyos-smoke: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run perry-codegen-arkts unit tests | |
| run: cargo test -p perry-codegen-arkts --release --lib | |
| - name: Run Phase 2 full-app integration smoke | |
| run: | | |
| cargo test -p perry-codegen-arkts \ | |
| --release \ | |
| --test phase2_full_app_smoke \ | |
| -- --nocapture | |
| # --------------------------------------------------------------------------- | |
| # Doc-example tests: compile + run every .ts under docs/examples/. | |
| # UI examples launch with PERRY_UI_TEST_MODE=1 so they auto-exit after one | |
| # frame. Gallery screenshots are diffed against per-OS baselines (advisory | |
| # until Linux/Windows baselines stabilize). | |
| # --------------------------------------------------------------------------- | |
| doc-tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| ui_backend: perry-ui-macos | |
| shell: bash | |
| # stdlib/http/snippets.ts excluded since v0.5.886: it links | |
| # against js_axios_response_data_parsed + | |
| # js_node_http2_create_secure_server which live in | |
| # perry-ext-axios / perry-ext-http-server. v0.5.885's | |
| # PERRY_NO_AUTO_OPTIMIZE skips the well-known-binding probe | |
| # that would route those .a files into the link surface. | |
| # Proper fix: hoist well-known-binding lookup out of | |
| # build_optimized_libs into link.rs so it runs even when | |
| # auto-optimize is skipped. Tracked separately. | |
| cmd_exclude_gallery: "./scripts/run_doc_tests.sh --verbose --skip-xcompile --filter-exclude ui/gallery.ts --filter-exclude stdlib/http/snippets.ts" | |
| cmd_gallery: "./scripts/run_doc_tests.sh --verbose --skip-xcompile --filter ui/gallery.ts" | |
| # Repeat `--xcompile-only-target=…` per target rather than a | |
| # single comma-delimited value because PowerShell splits even | |
| # `--foo=a,b` at the comma when unquoted (array literal). | |
| # Repetition sidesteps the whole issue on every shell. | |
| # | |
| # web + wasm cross-compile dropped from the macOS blocking gate | |
| # in v0.5.429 — both targets are portable and the ubuntu-24.04 | |
| # matrix entry below already verifies them at 1× billing | |
| # weight. Keep ios-simulator here because it requires Apple | |
| # SDK that only macos-14 has. | |
| cmd_xcompile_blocking: "./scripts/run_doc_tests.sh --verbose --xcompile-only --xcompile-only-target=ios-simulator" | |
| cmd_xcompile_advisory: "./scripts/run_doc_tests.sh --verbose --xcompile-only" | |
| # Baseline captured from a clean CI run of 24723671119 (900x970). | |
| gallery_advisory: false | |
| # ubuntu-24.04 / perry-ui-gtk4 doc-tests re-disabled v0.5.873: | |
| # 39 of 88 tests TIMEOUT at the 15s execution budget. The gtk4 | |
| # `glib::timeout_add_local_once` → `app.quit()` exit path | |
| # doesn't terminate the main loop cleanly under xvfb-run, so | |
| # PERRY_UI_TEST_MODE-driven self-exit never fires and the | |
| # harness has to SIGKILL each binary. Separate bug from the | |
| # webkit6 / soup / ed25519 fixes that v0.5.864→0.5.871 closed. | |
| # Tracked separately. Re-enable when the gtk4 testkit exit | |
| # path is fixed. | |
| # - os: ubuntu-24.04 | |
| # ui_backend: perry-ui-gtk4 | |
| # shell: bash | |
| # cmd_exclude_gallery: "xvfb-run -a ./scripts/run_doc_tests.sh --verbose --skip-xcompile --filter-exclude ui/gallery.ts" | |
| # cmd_gallery: "xvfb-run -a ./scripts/run_doc_tests.sh --verbose --skip-xcompile --filter ui/gallery.ts" | |
| # cmd_xcompile_blocking: "./scripts/run_doc_tests.sh --verbose --xcompile-only --xcompile-only-target=web --xcompile-only-target=wasm --xcompile-only-target=ios-simulator" | |
| # cmd_xcompile_advisory: "./scripts/run_doc_tests.sh --verbose --xcompile-only" | |
| # gallery_advisory: false | |
| # windows-2022 doc-tests for perry-ui-windows temporarily disabled: | |
| # 30+ doc-test snippets COMPILE_FAIL on the Windows runner with | |
| # perry exit 1 (mix of platform-banner gaps and #463-unimplemented | |
| # node:* surface). Tracked separately; re-enable once the snippet | |
| # set + perry-ui-windows compile paths are reconciled. macOS | |
| # doc-tests still gate the release. | |
| # - os: windows-2022 | |
| # ui_backend: perry-ui-windows | |
| # shell: pwsh | |
| # cmd_exclude_gallery: "./scripts/run_doc_tests.ps1 --verbose --skip-xcompile --filter-exclude ui/gallery.ts" | |
| # cmd_gallery: "./scripts/run_doc_tests.ps1 --verbose --skip-xcompile --filter ui/gallery.ts" | |
| # cmd_xcompile_blocking: "./scripts/run_doc_tests.ps1 --verbose --xcompile-only --xcompile-only-target=web --xcompile-only-target=wasm --xcompile-only-target=ios-simulator" | |
| # cmd_xcompile_advisory: "./scripts/run_doc_tests.ps1 --verbose --xcompile-only" | |
| # gallery_advisory: false | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: ${{ matrix.shell }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # macos-14 ships with ~14 GB free disk after the preinstalled Xcode + | |
| # iOS/tvOS/watchOS simulator runtime images. Several `cargo build | |
| # --release` jobs in this workflow consistently OOM'd at the cache | |
| # restore step (`No space left on device` from the runner's own | |
| # diagnostic writer, before cargo even started). Wiping the simulator | |
| # runtime IMAGES — not the SDKs — reclaims ~15-25 GB without | |
| # affecting cross-compile to `aarch64-apple-ios-sim` (that only needs | |
| # the SDK, which lives inside the active Xcode app). | |
| - name: Free up disk space (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| BEFORE=$(df -h / | tail -1 | awk '{print $4}') | |
| sudo rm -rf /Library/Developer/CoreSimulator/Profiles/Runtimes/*Simulator* || true | |
| sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/* || true | |
| AFTER=$(df -h / | tail -1 | awk '{print $4}') | |
| echo "Disk free: ${BEFORE} -> ${AFTER}" | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Set up MSVC environment (Windows) | |
| # Populates LIB / INCLUDE / PATH so link.exe can find user32.lib | |
| # etc. Without this, runner's MSVC install exists on disk but the | |
| # shell session has no clue where, and perry's LNK1181 fatal | |
| # errors looking for Windows SDK libs. | |
| if: matrix.os == 'windows-2022' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Install GTK4 + GStreamer + Xvfb + PulseAudio headers (Linux) | |
| if: matrix.os == 'ubuntu-24.04' | |
| run: | | |
| sudo apt-get update | |
| # libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev added in | |
| # v0.5.442 for PR #371 (perry/media streaming playback, #351). | |
| # gstreamer-sys's build script needs `gstreamer-1.0.pc` findable | |
| # via pkg-config; without these two packages doc-tests-gtk4 fails | |
| # at the cargo build step with "Package gstreamer-1.0 was not | |
| # found in the pkg-config search path". gstreamer-base is the | |
| # transitive dep gstreamer-base-sys needs for the playbin element | |
| # that perry/media wraps. | |
| # libwebkitgtk-6.0-dev added for the perry/ui-gtk4 WebView | |
| # feature (Phases 1-5 + v2 follow-ups, #658): the `webkit6` crate | |
| # (0.4 series) and its transitive `javascriptcore6-sys` need | |
| # `webkitgtk-6.0.pc` AND `javascriptcoregtk-6.0.pc` findable via | |
| # pkg-config. Ubuntu 24.04 (noble) ships libwebkitgtk-6.0-dev | |
| # which provides BOTH .pc files (the old libwebkit2gtk-4.1-dev | |
| # name only ships the 4.1 .pc and that's not what webkit6 wants). | |
| sudo apt-get install -y \ | |
| libgtk-4-dev libadwaita-1-dev xvfb pkg-config \ | |
| libpulse-dev \ | |
| libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ | |
| libwebkitgtk-6.0-dev libshumate-dev | |
| - name: Surface Android NDK location (for cross-compile) | |
| if: matrix.os == 'macos-14' || matrix.os == 'ubuntu-24.04' | |
| run: | | |
| # 1. Discover NDK location on the runner. | |
| NDK="" | |
| if [ -n "$ANDROID_NDK_HOME" ]; then | |
| NDK="$ANDROID_NDK_HOME" | |
| elif [ -d "$ANDROID_HOME/ndk-bundle" ]; then | |
| NDK="$ANDROID_HOME/ndk-bundle" | |
| elif [ -d "$ANDROID_HOME/ndk" ]; then | |
| NDK=$(ls -1d "$ANDROID_HOME/ndk/"*/ 2>/dev/null | sort -V | tail -1) | |
| NDK="${NDK%/}" | |
| fi | |
| if [ -z "$NDK" ]; then | |
| echo "No Android NDK found on runner — android xcompile will skip" | |
| exit 0 | |
| fi | |
| echo "ANDROID_NDK_HOME=$NDK" >> "$GITHUB_ENV" | |
| # 2. Point cc-rs + cargo at the NDK's clang wrapper so | |
| # `cargo build --target aarch64-linux-android` picks up the | |
| # right linker/CC/AR instead of the host `cc`. | |
| HOST_TAG=$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64 | |
| # macOS NDK uses darwin-x86_64 even on arm64 runners (rosetta). | |
| if [ "$(uname -s)" = "Darwin" ]; then HOST_TAG="darwin-x86_64"; fi | |
| TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/$HOST_TAG/bin" | |
| API=24 | |
| CLANG=$(ls "$TOOLCHAIN"/aarch64-linux-android*-clang 2>/dev/null | sort -V | tail -1) | |
| if [ -z "$CLANG" ]; then | |
| echo "Could not locate NDK clang under $TOOLCHAIN — skipping env setup" | |
| exit 0 | |
| fi | |
| echo "CC_aarch64_linux_android=$CLANG" >> "$GITHUB_ENV" | |
| echo "AR_aarch64_linux_android=$TOOLCHAIN/llvm-ar" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$CLANG" >> "$GITHUB_ENV" | |
| echo "Android NDK wired: CLANG=$CLANG" | |
| - name: Install Apple SDK Rust targets (macOS only) | |
| if: matrix.os == 'macos-14' | |
| run: | | |
| rustup target add aarch64-apple-ios-sim | |
| # tvOS-sim is Rust Tier-3 — perry auto-rebuilds with | |
| # `+nightly -Zbuild-std`, which requires the rust-src | |
| # component on the nightly toolchain. | |
| rustup toolchain install nightly --component rust-src --profile minimal || true | |
| - name: Install Android Rust target (macOS + Ubuntu) | |
| if: matrix.os == 'macos-14' || matrix.os == 'ubuntu-24.04' | |
| run: rustup target add aarch64-linux-android | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Build compiler + UI backend + harness | |
| run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p ${{ matrix.ui_backend }} -p perry-doc-tests | |
| - name: Pre-build Apple UI libs for cross-compile (macOS only) | |
| if: matrix.os == 'macos-14' | |
| run: | | |
| # iOS-sim is Rust Tier-2 — builds with stable. tvOS-sim is Tier-3 | |
| # and needs nightly + -Zbuild-std; perry's auto-optimize handles | |
| # that path itself, so we skip pre-building perry-ui-tvos here. | |
| cargo build --release -p perry-ui-ios --target aarch64-apple-ios-sim | |
| - name: Lint docs/src markdown fences (repo-wide) | |
| if: matrix.os == 'macos-14' | |
| run: cargo run --release --quiet -p perry-doc-tests -- --lint docs/src | |
| - name: Run non-gallery doc-example tests (blocking) | |
| run: ${{ matrix.cmd_exclude_gallery }} | |
| - name: Run gallery screenshot diff | |
| id: gallery | |
| continue-on-error: ${{ matrix.gallery_advisory }} | |
| run: ${{ matrix.cmd_gallery }} | |
| - name: Cross-compile for web + wasm (blocking) | |
| id: xcompile_blocking | |
| run: ${{ matrix.cmd_xcompile_blocking }} | |
| - name: Cross-compile remaining targets (advisory) | |
| # iOS-sim/tvOS-sim/watchos-sim/android still surface real errors | |
| # that the harness logs but that aren't yet tracked issues. Keep | |
| # advisory until each target has a green baseline run. | |
| id: xcompile_advisory | |
| continue-on-error: true | |
| run: ${{ matrix.cmd_xcompile_advisory }} | |
| - name: Upload doc-tests report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: doc-tests-report-${{ matrix.os }} | |
| path: docs/examples/_reports/latest.json | |
| - name: Upload gallery screenshot + diff artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gallery-screenshots-${{ matrix.os }} | |
| path: | | |
| target/perry-doc-tests/gallery_*.png | |
| docs/examples/_baselines/**/gallery.png | |
| # --------------------------------------------------------------------------- | |
| # Binary size tracking (main branch only) | |
| # --------------------------------------------------------------------------- | |
| binary-size: | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: macos-14 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: "${{ runner.os }}-perry" | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Build release binaries | |
| run: cargo build --release -p perry -p perry-runtime -p perry-stdlib | |
| - name: Report binary sizes | |
| run: | | |
| echo "## Binary Sizes" > /tmp/sizes.md | |
| echo '```' >> /tmp/sizes.md | |
| ls -lh target/release/perry target/release/libperry_runtime.a target/release/libperry_stdlib.a 2>/dev/null | awk '{print $5, $9}' >> /tmp/sizes.md | |
| echo '```' >> /tmp/sizes.md | |
| cat /tmp/sizes.md | |
| - name: Upload size report | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: binary-sizes | |
| path: /tmp/sizes.md |