Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,14 @@ jobs:
if: runner.os == 'Windows'
run: cargo build --profile dist --target ${{ matrix.target }} -p perry-ui-windows

# Closes #872: cross-compile the Android runtime libs on the Windows
# leg so the WinGet zip includes `libperry_runtime.a` /
# `libperry_stdlib.a` for `aarch64-linux-android`. Without these,
# `perry compile --target android` from a WinGet-installed perry
# bails at link time with "Could not find libperry_runtime.a".
# Closes #872 / #4823: cross-compile the Android runtime libs (and the
# perry-ui-android UI backend) on the Windows leg so the WinGet zip
# includes `libperry_runtime.a` / `libperry_stdlib.a` /
# `libperry_ui_android.a` for `aarch64-linux-android`. Without the
# runtime libs, `perry compile --target android` from a WinGet-installed
# perry bails with "Could not find libperry_runtime.a"; without the UI
# lib, any app importing `perry/ui` bails with "libperry_ui_android.a
# not found" (#4823).
# GitHub-hosted Windows runners pre-install the Android NDK at
# `%ANDROID_NDK_HOME%`; we point cargo at its `aarch64-linux-android-*`
# clang as the cross-linker.
Expand Down Expand Up @@ -416,11 +419,41 @@ jobs:
Write-Error "Android NDK linker not found at $linker — NDK layout drifted; update the version-suffixed path."
exit 1
}
# perry-ui-android pulls in perry-audio-miniaudio (C) and aws-lc-rs
# (via tungstenite/rustls), so the C++ wrapper must be wired too.
$linkerxx = Join-Path $toolchain "aarch64-linux-android$($env:ANDROID_API_LEVEL)-clang++.cmd"
if (-not (Test-Path $linkerxx)) {
$linkerxx = Join-Path $toolchain "aarch64-linux-android$($env:ANDROID_API_LEVEL)-clang++.exe"
}
$env:CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = $linker
$env:CC_aarch64_linux_android = $linker
$env:CXX_aarch64_linux_android = $linkerxx
$env:AR_aarch64_linux_android = Join-Path $toolchain "llvm-ar.exe"
# pwsh has no `set -e`; these two are mandatory, so fail the step on a
# non-zero exit (otherwise the best-effort UI build below, which resets
# $LASTEXITCODE, could mask a runtime/stdlib failure).
cargo build --profile dist --target aarch64-linux-android -p perry-runtime -p perry-runtime-static
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
cargo build --profile dist --target aarch64-linux-android -p perry-stdlib -p perry-stdlib-static
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# #4823 (option 1): the Android UI backend so a WinGet/Scoop-installed
# perry can link `perry/ui` apps for android without a source tree.
# Best-effort: aws-lc-rs cross-from-Windows can be finicky; a failure
# here must not sink the whole release, because the npm path backfills
# libperry_ui_android.a from the ubuntu `perry-cross-*` bundle and the
# zip still ships runtime+stdlib. The UI staging below is guarded on
# the lib actually existing.
# Mirrors the ubuntu cross-bundle leg: `--profile dist` build for the
# staticlib (UI crates are already staticlib — no -static wrapper); the
# global-dynamic TLS rustflag only matters at the consumer's final
# cdylib link, not here.
# A native non-zero exit isn't a terminating error in pwsh, so guard
# on $LASTEXITCODE explicitly and reset it so the step stays green.
cargo build --profile dist --target aarch64-linux-android -p perry-ui-android
if ($LASTEXITCODE -ne 0) {
Write-Warning "perry-ui-android android cross-build failed (exit $LASTEXITCODE) — zip will omit the UI lib; npm backfills it from perry-cross-aarch64-linux-android."
$global:LASTEXITCODE = 0
}

# #4856 — defense in depth behind the cache eviction above: assert
# every runtime archive this leg built (host triple + any Apple/
Expand Down Expand Up @@ -552,7 +585,9 @@ jobs:
# subdir lives alongside `perry.exe`.
$androidLibDir = "staging/aarch64-linux-android/release"
New-Item -ItemType Directory -Force -Path $androidLibDir | Out-Null
$androidLibs = @("libperry_runtime.a", "libperry_stdlib.a")
# #4823: libperry_ui_android.a is best-effort (the cross-build above
# may have skipped it) — Test-Path guards it just like the others.
$androidLibs = @("libperry_runtime.a", "libperry_stdlib.a", "libperry_ui_android.a")
foreach ($lib in $androidLibs) {
$src = "target/aarch64-linux-android/dist/$lib"
if (Test-Path $src) {
Expand Down
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
## v0.5.1198 — fix(packaging): ship `libperry_ui_android.a` to Windows installs so `perry/ui` android apps link (#4823)

A Windows user (discussion #4823) with the NDK/SDK and Rust android targets
installed could not build a `perry/ui` app for android — `perry --target
android` got through the "runtime-only" link step and then failed with:

```
Error: perry/ui imported but libperry_ui_android.a not found. Build with:
RUSTC_BOOTSTRAP=1 RUSTFLAGS="-Z tls-model=global-dynamic" cargo build --release -p perry-ui-android --target aarch64-linux-android
```

Root cause was a packaging gap, not user error. Two distinct holes:

- **WinGet/Scoop zip** — the Windows release leg cross-compiled and staged
only `libperry_runtime.a` / `libperry_stdlib.a` for `aarch64-linux-android`
(#872), never `libperry_ui_android.a`. So the runtime-only link succeeded
but any app importing `perry/ui` couldn't resolve the UI backend. The UI lib
*was* built — but only inside the standalone `perry-cross-aarch64-linux-android`
bundle (#1083), which a binary-install user has no reason to know about.
- **npm** — `stage-npm.sh` flattened libs into `lib/` and dropped the
`aarch64-linux-android/release/` subdir entirely, so npm packages shipped
*no* android cross-libs at all; android builds from an npm install were
wholly unsupported.

Fixes (the two options the maintainer approved):

1. **(`release-packages.yml`)** The Windows `build:` leg now also cross-builds
`perry-ui-android` (`cargo build --profile dist` for the staticlib — the
global-dynamic TLS rustflag only matters at the consumer's final cdylib
link, mirroring the ubuntu cross-bundle leg; the UI crate is already a
staticlib so it needs no `-static` wrapper) and stages
`libperry_ui_android.a` into the zip's `aarch64-linux-android/release/`.
It is **best-effort**: a native non-zero exit is caught via `$LASTEXITCODE`
(pwsh native failures aren't terminating errors) so a finicky aws-lc-rs
cross-build can't sink the release; the staging is `Test-Path`-guarded.
2. **(`stage-npm.sh`)** A new `stage_android_cross_libs` helper stages all
three android archives into the Windows npm package under
`bin/aarch64-linux-android/release/` (the layout `library_search.rs` probes,
already inside the `files` allowlist), compressed to `.a.zst` like the rest.
Each lib is sourced from the Windows build artifact first, falling back to
the `perry-cross-aarch64-linux-android` bundle the npm-publish job already
downloads — so the UI lib is backfilled from the authoritative ubuntu build
even if the best-effort Windows cross-build was skipped. This also enables
npm-installed android builds for the first time.

Also improved the link-time error message (`link/mod.rs`) for android: it now
points binary-install users (no source tree) at dropping the prebuilt
`libperry_ui_android.a` from `perry-cross-aarch64-linux-android.tar.gz` before
falling back to the from-source `cargo build` instruction.

## v0.5.1197 — feat(runtime): #2656 — make WeakMap/WeakSet actually weak

WeakMap/WeakSet previously stored entries as plain `[key, value]` pair arrays that the
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1197
**Current Version:** 0.5.1198


## TypeScript Parity Status
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ strip = false
codegen-units = 16

[workspace.package]
version = "0.5.1197"
version = "0.5.1198"
edition = "2021"
license = "MIT"
repository = "https://github.com/PerryTS/perry"
Expand Down
11 changes: 7 additions & 4 deletions crates/perry/src/commands/compile/link/build_and_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,10 +1084,13 @@ pub(crate) fn build_and_run_link(
} else if is_android {
(
"libperry_ui_android.a",
// #1529 — TLS model must be global-dynamic for the dlopen'd cdylib.
// `tls-model` is `-Z`-gated on the toolchains we ship against, so
// RUSTC_BOOTSTRAP=1 lets the gated flag through on a stable rustc.
"RUSTC_BOOTSTRAP=1 RUSTFLAGS=\"-Z tls-model=global-dynamic\" cargo build --release -p perry-ui-android --target aarch64-linux-android",
// Two audiences here: a binary-install user (WinGet/Scoop/npm,
// no source tree) can't run cargo, so point them at the
// prebuilt cross bundle first; the from-source build follows
// for workspace users. #1529 — the dlopen'd cdylib needs the
// global-dynamic TLS model, and `tls-model` is `-Z`-gated, so
// RUSTC_BOOTSTRAP=1 lets it through on a stable rustc.
"either (a) drop libperry_ui_android.a next to perry under aarch64-linux-android/release/ from the prebuilt perry-cross-aarch64-linux-android.tar.gz release asset, or (b) from a perry checkout: RUSTC_BOOTSTRAP=1 RUSTFLAGS=\"-Z tls-model=global-dynamic\" cargo build --release -p perry-ui-android --target aarch64-linux-android",
)
} else if is_linux {
(
Expand Down
80 changes: 80 additions & 0 deletions scripts/stage-npm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,83 @@ resolve_artifact() {
echo "$workdir"
}

# #4823: Stage Android cross-compile libs into a platform package.
#
# `perry --target android` links three static archives (runtime, stdlib, UI).
# We mirror the layout perry's library_search.rs probes —
# `<bin-dir>/aarch64-linux-android/release/<lib>` and its `.a.zst` sibling —
# under the package's `bin/` dir (already covered by the npm `files`
# allowlist, so no package.json change is needed).
#
# Each lib is sourced from the host package's own build artifact first (the
# Windows release leg cross-builds all three — see release-packages.yml). The
# UI lib is best-effort on that leg, so it falls back to the standalone
# `perry-cross-aarch64-linux-android` bundle the Android cross-bundle job
# always produces. This both enables npm-installed android builds (previously
# the npm packages shipped no android libs at all) and backfills the UI lib
# when the Windows-side cross-build was skipped.
#
# Args: $1 = package dir, $2 = host artifact src dir (may lack the subdir).
stage_android_cross_libs() {
local pkg_dir="$1" host_src="$2"
local triple="aarch64-linux-android"
local host_sub="$host_src/$triple/release"
local dest="$pkg_dir/bin/$triple/release"

# Extract the standalone cross bundle once (fallback source for any lib the
# host artifact is missing). download-artifact nests by artifact name; also
# tolerate a flat drop for local runs.
local cross_dir="" cb="$ARTIFACT_DIR/perry-cross-$triple/perry-cross-$triple.tar.gz"
[ -f "$cb" ] || cb="$ARTIFACT_DIR/perry-cross-$triple.tar.gz"
if [ -f "$cb" ]; then
cross_dir="$ARTIFACT_DIR/.extracted/perry-cross-$triple"
if [ ! -d "$cross_dir" ]; then
mkdir -p "$cross_dir"
tar xzf "$cb" -C "$cross_dir"
fi
fi

mkdir -p "$dest"
local staged=0 lib src
for lib in libperry_runtime.a libperry_stdlib.a libperry_ui_android.a; do
src=""
if [ -f "$host_sub/$lib" ]; then
src="$host_sub/$lib"
elif [ -n "$cross_dir" ] && [ -f "$cross_dir/$lib" ]; then
src="$cross_dir/$lib"
fi
if [ -z "$src" ]; then
echo " (android: $lib unavailable — skipping)"
continue
fi
cp "$src" "$dest/$lib"
staged=$((staged + 1))
done

if [ "$staged" -eq 0 ]; then
rm -rf "$pkg_dir/bin/$triple"
echo " (android: no cross-libs staged)"
return 0
fi

# Compress to match the rest of the package (raw .a's blow npm's upload
# limit; the binary decompresses the .a.zst transparently — see
# compressed_libs.rs). The existing lib/ compression loop only walks lib/,
# so these bin/-subdir archives must be compressed here.
if [ "${PERRY_NPM_NO_COMPRESS:-0}" != "1" ]; then
if ! command -v zstd >/dev/null 2>&1; then
echo " error: zstd not found but archive compression is required" >&2
exit 1
fi
for f in "$dest"/*.a; do
[ -f "$f" ] || continue
zstd -19 -T0 -q -f --rm "$f"
echo " compressed android/$(basename "$f") -> $(basename "$f").zst"
done
fi
echo " android: staged $staged cross-lib(s) under bin/$triple/release/"
}

# -----------------------------------------------------------------------------
# Stage wrapper package
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -166,6 +243,9 @@ for entry in "${PLATFORMS[@]}"; do
for lib in "${WIN_CORE_LIBS[@]}" "$ui_lib"; do
[ -f "$src_dir/$lib" ] && cp "$src_dir/$lib" "$pkg_dir/lib/"
done
# #4823: the Windows package also carries the android cross-libs so
# `perry --target android` works from an npm install (parity with the zip).
stage_android_cross_libs "$pkg_dir" "$src_dir"
else
cp "$src_dir/perry" "$pkg_dir/bin/perry"
chmod +x "$pkg_dir/bin/perry"
Expand Down
Loading