From 2e025357274768a568413172c05f7076c3e1f073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 19 Jun 2026 20:15:58 +0200 Subject: [PATCH 1/3] build(#5422): dev/release/dist build taxonomy, rlib-only runtime/stdlib + staticlib wrappers, slim CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements all four parts of #5422: 1. perry-dev profile — fast local builds (lto off, cgu=16, opt-level=1, incremental, no strip). 2. Staticlib split — perry-runtime/perry-stdlib are rlib-only; the .a archives are emitted by new perry-runtime-static / perry-stdlib-static wrapper crates ([lib] name keeps the libperry_{runtime,stdlib}.a basenames). Wrappers carry strip=false + cgu=16 so ThinLTO keeps the exported C API (cf. #5140). Wrappers are NOT in default-members, so a plain `cargo build` no longer emits the .a. Auto-optimize (optimized_libs.rs), geisterhand (library_search.rs), and every workflow/script that produced the archives now build the -static crates. 3. Slim CLI — crates/perry/Cargo.toml feature taxonomy (default=full-cli -> dev-cli/publish-cli/mobile-cli/updater-cli/native-cli/audit-cli + backend-{js,swiftui,arkts,glance,wear-tiles,wasm}/all-codegen-backends). `--no-default-features --features dev-cli` builds a slim compiler CLI; gated commands drop from --help and disabled --target backends error cleanly. 4. dist profile — mirrors release exactly (incl. per-package overrides); release-packages.yml builds official artifacts with --profile dist (CARGO_PROFILE_DIST_PANIC; target/.../release -> target/.../dist for cargo output, staging layout + Homebrew build-from-source stay on --release). scripts/cargo_timing_summary.py surfaces the slowest build units. Validated locally: symbol guard (check_runtime_symbols.sh), prebuilt-link, and auto-optimize-rebuild link paths all compile+link+run a hello-world; default / dev-cli / bare --no-default-features all build; slim CLI behaves correctly. The release-packages.yml --profile dist cutover needs a tag/CI run to confirm the full release matrix. --- .github/workflows/container-tests.yml | 2 +- .github/workflows/feature-matrix.yml | 2 +- .github/workflows/node-core-subset.yml | 2 +- .github/workflows/node-suite-guard.yml | 2 +- .github/workflows/npm-package-sweep.yml | 2 +- .github/workflows/release-packages.yml | 90 +++++----- .github/workflows/simctl-tests.yml | 2 +- .github/workflows/test.yml | 12 +- CHANGELOG.md | 49 ++++++ CLAUDE.md | 4 +- CONTRIBUTING.md | 7 + Cargo.lock | 162 ++++++++++-------- Cargo.toml | 88 +++++++++- README.md | 8 + crates/perry-runtime-static/Cargo.toml | 29 ++++ crates/perry-runtime-static/src/lib.rs | 15 ++ crates/perry-runtime/Cargo.toml | 6 +- crates/perry-stdlib-static/Cargo.toml | 21 +++ crates/perry-stdlib-static/src/lib.rs | 7 + crates/perry-stdlib/Cargo.toml | 6 +- crates/perry/Cargo.toml | 82 ++++++++- crates/perry/src/commands/compile.rs | 85 +++++++-- .../perry/src/commands/compile/bootstrap.rs | 1 + .../src/commands/compile/library_search.rs | 9 +- .../src/commands/compile/optimized_libs.rs | 9 +- crates/perry/src/commands/compile/targets.rs | 7 + crates/perry/src/commands/mod.rs | 10 ++ crates/perry/src/main.rs | 19 ++ docs/src/contributing/building.md | 43 +++++ run_parity_tests.sh | 4 +- scripts/bisect_1114.sh | 2 +- scripts/cargo_timing_summary.py | 83 +++++++++ scripts/run_doc_tests.sh | 2 +- scripts/run_memory_stability_tests.sh | 2 +- scripts/run_tty_pty_smoke.sh | 2 +- 35 files changed, 710 insertions(+), 166 deletions(-) create mode 100644 crates/perry-runtime-static/Cargo.toml create mode 100644 crates/perry-runtime-static/src/lib.rs create mode 100644 crates/perry-stdlib-static/Cargo.toml create mode 100644 crates/perry-stdlib-static/src/lib.rs create mode 100755 scripts/cargo_timing_summary.py diff --git a/.github/workflows/container-tests.yml b/.github/workflows/container-tests.yml index 8ff9ead33e..adb1b9cde7 100644 --- a/.github/workflows/container-tests.yml +++ b/.github/workflows/container-tests.yml @@ -267,7 +267,7 @@ jobs: - name: Build Perry CLI (release) run: | cargo build --release \ - -p perry -p perry-runtime -p perry-stdlib + -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static - name: D — redis smoke run: | diff --git a/.github/workflows/feature-matrix.yml b/.github/workflows/feature-matrix.yml index e8a9b8e432..b027ef16ae 100644 --- a/.github/workflows/feature-matrix.yml +++ b/.github/workflows/feature-matrix.yml @@ -46,7 +46,7 @@ jobs: node-version: "26" - name: Build Perry release binary - run: cargo build --release -p perry-runtime -p perry + run: cargo build --release -p perry-runtime -p perry-runtime-static -p perry - name: Check feature matrix run: | diff --git a/.github/workflows/node-core-subset.yml b/.github/workflows/node-core-subset.yml index 6476beebed..ecb01040de 100644 --- a/.github/workflows/node-core-subset.yml +++ b/.github/workflows/node-core-subset.yml @@ -56,7 +56,7 @@ jobs: git -C vendor/nodejs checkout - name: Build Perry release binary - run: cargo build --release -p perry -p perry-runtime -p perry-stdlib + run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static - name: Run Node core subset radar run: | diff --git a/.github/workflows/node-suite-guard.yml b/.github/workflows/node-suite-guard.yml index bdb885437e..f7f2f30352 100644 --- a/.github/workflows/node-suite-guard.yml +++ b/.github/workflows/node-suite-guard.yml @@ -63,7 +63,7 @@ jobs: node-version: "26" - name: Build Perry release binary - run: cargo build --release -p perry -p perry-runtime -p perry-stdlib + run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static - name: Node-suite regression guard (floor baseline, node 26) # Fails (exit 1) if any baselined module drops below its floor; diff --git a/.github/workflows/npm-package-sweep.yml b/.github/workflows/npm-package-sweep.yml index 62d9351207..75a781251a 100644 --- a/.github/workflows/npm-package-sweep.yml +++ b/.github/workflows/npm-package-sweep.yml @@ -52,7 +52,7 @@ jobs: node-version: '22' - name: Build Perry release binary - run: cargo build --release -p perry-runtime -p perry-stdlib -p perry + run: cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry - name: Run npm package sweep env: diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 79917e2179..e1464b37a4 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -206,8 +206,8 @@ jobs: run: | set -euo pipefail while IFS= read -r pkg; do - rm -rf target/release/.fingerprint/"$pkg"-* \ - target/*/release/.fingerprint/"$pkg"-* + rm -rf target/dist/.fingerprint/"$pkg"-* \ + target/*/dist/.fingerprint/"$pkg"-* done < <(cargo metadata --no-deps --format-version 1 | jq -r '.packages[].name') # Closes #394: Homebrew bottle was missing libperry_ui_ios.a so @@ -263,12 +263,12 @@ jobs: # the next step. (Perry is V8-free — the former perry-jsruntime # crate, which downloaded a prebuilt V8 that 404'd on musl, was # removed.) - run: cargo build --release --target ${{ matrix.target }} -p perry + run: cargo build --profile dist --target ${{ matrix.target }} -p perry - name: Build runtime libraries run: | - cargo build --release --target ${{ matrix.target }} -p perry-runtime - cargo build --release --target ${{ matrix.target }} -p perry-stdlib + cargo build --profile dist --target ${{ matrix.target }} -p perry-runtime -p perry-runtime-static + cargo build --profile dist --target ${{ matrix.target }} -p perry-stdlib -p perry-stdlib-static - name: Build panic=abort runtime variant (Unix) # Out-of-tree installs can't rebuild the runtime, so ship the @@ -280,10 +280,10 @@ jobs: # invalidate the main build's incremental cache. if: runner.os != 'Windows' run: | - CARGO_TARGET_DIR=target-abort CARGO_PROFILE_RELEASE_PANIC=abort \ - cargo build --release --target ${{ matrix.target }} -p perry-runtime - cp "target-abort/${{ matrix.target }}/release/libperry_runtime.a" \ - "target/${{ matrix.target }}/release/libperry_runtime_abort.a" + CARGO_TARGET_DIR=target-abort CARGO_PROFILE_DIST_PANIC=abort \ + cargo build --profile dist --target ${{ matrix.target }} -p perry-runtime -p perry-runtime-static + cp "target-abort/${{ matrix.target }}/dist/libperry_runtime.a" \ + "target/${{ matrix.target }}/dist/libperry_runtime_abort.a" - name: Build native ext libraries (Unix) # #2532 — the perry-ext-* wrapper crates ship the host functions for @@ -302,14 +302,14 @@ jobs: [ -d "$d" ] || continue name=$(basename "$d") echo "::group::build $name" - cargo build --release --target ${{ matrix.target }} -p "$name" \ + cargo build --profile dist --target ${{ matrix.target }} -p "$name" \ || echo " (skipped $name — failed to build on this host)" echo "::endgroup::" done - name: Build UI library (macOS) if: runner.os == 'macOS' - run: cargo build --release --target ${{ matrix.target }} -p perry-ui-macos + run: cargo build --profile dist --target ${{ matrix.target }} -p perry-ui-macos # Closes #394: cross-compile the runtime + stdlib + UI for both the # iOS device and simulator triples (both Tier-2 stable). Library @@ -327,9 +327,9 @@ jobs: if: runner.os == 'macOS' && matrix.target == 'aarch64-apple-darwin' run: | for triple in aarch64-apple-ios aarch64-apple-ios-sim; do - cargo build --release --target "$triple" -p perry-runtime - cargo build --release --target "$triple" -p perry-stdlib - cargo build --release --target "$triple" -p perry-ui-ios + cargo build --profile dist --target "$triple" -p perry-runtime -p perry-runtime-static + cargo build --profile dist --target "$triple" -p perry-stdlib -p perry-stdlib-static + cargo build --profile dist --target "$triple" -p perry-ui-ios done # Closes #396 / #397 / #398: cross-compile runtime + stdlib + UI for the @@ -363,21 +363,21 @@ jobs: sim="${rest#*:}" for triple in "$dev" "$sim"; do echo "::group::$plat $triple" - cargo +nightly build --release \ + cargo +nightly build --profile dist \ -Z build-std=core,std,panic_abort \ --target "$triple" \ - -p perry-runtime -p perry-stdlib "-p" "perry-ui-${plat}" + -p perry-runtime -p perry-runtime-static -p perry-stdlib -p perry-stdlib-static "-p" "perry-ui-${plat}" echo "::endgroup::" done done - name: Build UI library (Linux glibc) if: runner.os == 'Linux' && !endsWith(matrix.target, '-musl') - run: cargo build --release --target ${{ matrix.target }} -p perry-ui-gtk4 + run: cargo build --profile dist --target ${{ matrix.target }} -p perry-ui-gtk4 - name: Build UI library (Windows) if: runner.os == 'Windows' - run: cargo build --release --target ${{ matrix.target }} -p perry-ui-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` / @@ -419,8 +419,8 @@ jobs: $env:CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = $linker $env:CC_aarch64_linux_android = $linker $env:AR_aarch64_linux_android = Join-Path $toolchain "llvm-ar.exe" - cargo build --release --target aarch64-linux-android -p perry-runtime - cargo build --release --target aarch64-linux-android -p perry-stdlib + cargo build --profile dist --target aarch64-linux-android -p perry-runtime -p perry-runtime-static + cargo build --profile dist --target aarch64-linux-android -p perry-stdlib -p perry-stdlib-static # #4856 — defense in depth behind the cache eviction above: assert # every runtime archive this leg built (host triple + any Apple/ @@ -431,7 +431,7 @@ jobs: shell: bash run: | set -euo pipefail - libs=$(find target -path "*/release/libperry_runtime.a" -o -path "*/release/perry_runtime.lib") + libs=$(find target -path "*/dist/libperry_runtime.a" -o -path "*/dist/perry_runtime.lib") if [ -z "$libs" ]; then echo "::error::no built runtime archives found under target/ — nothing to verify" exit 1 @@ -443,11 +443,11 @@ jobs: if: runner.os != 'Windows' run: | mkdir -p staging - cp target/${{ matrix.target }}/release/perry staging/ + cp target/${{ matrix.target }}/dist/perry staging/ # Include static libraries for linking (runtime, stdlib, UI) for lib in libperry_runtime.a libperry_runtime_abort.a libperry_stdlib.a libperry_ui_macos.a libperry_ui_gtk4.a; do - if [ -f "target/${{ matrix.target }}/release/$lib" ]; then - cp "target/${{ matrix.target }}/release/$lib" staging/ + if [ -f "target/${{ matrix.target }}/dist/$lib" ]; then + cp "target/${{ matrix.target }}/dist/$lib" staging/ fi done # #2532 — ship the perry-ext-* staticlibs so an out-of-tree install @@ -455,7 +455,7 @@ jobs: # drivers without the workspace source. `perry compile` finds them # via the same exe-dir / PERRY_LIB_DIR search as the runtime/stdlib # libs (optimized_libs.rs::resolve_prebuilt_ext_libs). - for lib in target/${{ matrix.target }}/release/libperry_ext_*.a; do + for lib in target/${{ matrix.target }}/dist/libperry_ext_*.a; do [ -f "$lib" ] && cp "$lib" staging/ done # Closes #394: macOS legs also ship iOS-cross-compiled libs. @@ -464,7 +464,7 @@ jobs: # bottle's flat lib dir. library_search.rs's cross-compile # branch composes the right name based on --target. if [ "${{ runner.os }}" = "macOS" ]; then - dev_dir="target/aarch64-apple-ios/release" + dev_dir="target/aarch64-apple-ios/dist" if [ -f "$dev_dir/libperry_runtime.a" ]; then cp "$dev_dir/libperry_runtime.a" "staging/libperry_runtime_ios.a" fi @@ -474,7 +474,7 @@ jobs: if [ -f "$dev_dir/libperry_ui_ios.a" ]; then cp "$dev_dir/libperry_ui_ios.a" "staging/libperry_ui_ios.a" fi - sim_dir="target/aarch64-apple-ios-sim/release" + sim_dir="target/aarch64-apple-ios-sim/dist" if [ -f "$sim_dir/libperry_runtime.a" ]; then cp "$sim_dir/libperry_runtime.a" "staging/libperry_runtime_ios_sim.a" fi @@ -495,8 +495,8 @@ jobs: rest="${entry#*:}" dev_triple="${rest%%:*}" sim_triple="${rest#*:}" - dev_dir="target/${dev_triple}/release" - sim_dir="target/${sim_triple}/release" + dev_dir="target/${dev_triple}/dist" + sim_dir="target/${sim_triple}/dist" if [ -f "$dev_dir/libperry_runtime.a" ]; then cp "$dev_dir/libperry_runtime.a" "staging/libperry_runtime_${plat}.a" fi @@ -535,12 +535,12 @@ jobs: shell: pwsh run: | New-Item -ItemType Directory -Force -Path staging - Copy-Item "target/${{ matrix.target }}/release/perry.exe" staging/ + Copy-Item "target/${{ matrix.target }}/dist/perry.exe" staging/ Copy-Item "xwin-install/bin/xwin.exe" staging/ # Include static libraries for linking (runtime, stdlib, UI) $libs = @("perry_runtime.lib", "perry_stdlib.lib", "perry_ui_windows.lib") foreach ($lib in $libs) { - $path = "target/${{ matrix.target }}/release/$lib" + $path = "target/${{ matrix.target }}/dist/$lib" if (Test-Path $path) { Copy-Item $path staging/ } @@ -554,7 +554,7 @@ jobs: New-Item -ItemType Directory -Force -Path $androidLibDir | Out-Null $androidLibs = @("libperry_runtime.a", "libperry_stdlib.a") foreach ($lib in $androidLibs) { - $src = "target/aarch64-linux-android/release/$lib" + $src = "target/aarch64-linux-android/dist/$lib" if (Test-Path $src) { Copy-Item $src $androidLibDir } @@ -788,8 +788,8 @@ jobs: run: | set -euo pipefail while IFS= read -r pkg; do - rm -rf target/release/.fingerprint/"$pkg"-* \ - target/*/release/.fingerprint/"$pkg"-* + rm -rf target/dist/.fingerprint/"$pkg"-* \ + target/*/dist/.fingerprint/"$pkg"-* done < <(cargo metadata --no-deps --format-version 1 | jq -r '.packages[].name') # --- Per-runner toolchain wiring -------------------------------------- @@ -853,22 +853,22 @@ jobs: shell: bash run: | set -euo pipefail - cargo build --release --target ${{ matrix.target }} -p perry-runtime - cargo build --release --target ${{ matrix.target }} -p perry-stdlib + cargo build --profile dist --target ${{ matrix.target }} -p perry-runtime -p perry-runtime-static + cargo build --profile dist --target ${{ matrix.target }} -p perry-stdlib -p perry-stdlib-static # UI crate is matrix-driven. perry-ui-android needs the NDK env # we wired above; perry-ui-windows builds against the MSVC SDK # already on windows-latest. Both go through plain `cargo build`. - cargo build --release --target ${{ matrix.target }} -p ${{ matrix.ui_crate }} + cargo build --profile dist --target ${{ matrix.target }} -p ${{ matrix.ui_crate }} - name: Build runtime + stdlib + UI (nightly + build-std, Tier-3) if: matrix.tier3 shell: bash run: | set -euo pipefail - cargo +nightly build --release \ + cargo +nightly build --profile dist \ -Z build-std=core,std,panic_abort \ --target ${{ matrix.target }} \ - -p perry-runtime -p perry-stdlib -p ${{ matrix.ui_crate }} + -p perry-runtime -p perry-runtime-static -p perry-stdlib -p perry-stdlib-static -p ${{ matrix.ui_crate }} # #4856 — defense in depth behind the cache eviction above: a stale # cached runtime archive fails the release here instead of breaking @@ -877,9 +877,9 @@ jobs: shell: bash run: | set -euo pipefail - lib="target/${{ matrix.target }}/release/libperry_runtime.a" + lib="target/${{ matrix.target }}/dist/libperry_runtime.a" # Windows MSVC emits perry_runtime.lib (no `lib` prefix). - [ -f "$lib" ] || lib="target/${{ matrix.target }}/release/perry_runtime.lib" + [ -f "$lib" ] || lib="target/${{ matrix.target }}/dist/perry_runtime.lib" ./scripts/check_runtime_symbols.sh "$lib" # --- Package + manifest ---------------------------------------------- @@ -903,7 +903,7 @@ jobs: mkdir -p "$STAGING" # Required runtime + stdlib (always present on a successful build). for lib in libperry_runtime.a libperry_stdlib.a; do - src="target/${TARGET}/release/${lib}" + src="target/${TARGET}/dist/${lib}" if [ ! -f "$src" ]; then echo "::error::Expected $src after build but it's missing — aborting bundle." exit 1 @@ -914,7 +914,7 @@ jobs: # principle, but we built it above so this should exist; we hard-fail # if it's missing so the worker doesn't see a silently-degraded # bundle). - UI_SRC="target/${TARGET}/release/${UI_LIB}" + UI_SRC="target/${TARGET}/dist/${UI_LIB}" if [ ! -f "$UI_SRC" ]; then echo "::error::UI lib $UI_SRC missing after build — aborting bundle." exit 1 @@ -967,7 +967,7 @@ jobs: # behavior at line ~448 above. $libs = @("perry_runtime.lib", "perry_stdlib.lib", $uiLib) foreach ($lib in $libs) { - $src = "target/$target/release/$lib" + $src = "target/$target/dist/$lib" if (-not (Test-Path $src)) { Write-Error "Expected $src after build but it's missing — aborting bundle." exit 1 @@ -1143,7 +1143,7 @@ jobs: lib.install Dir["libperry_*.a"] else system "cargo", "build", "--release" - system "cargo", "build", "--release", "-p", "perry-runtime", "-p", "perry-stdlib" + system "cargo", "build", "--release", "-p", "perry-runtime", "-p", "perry-stdlib", "-p", "perry-runtime-static", "-p", "perry-stdlib-static" bin.install "target/release/perry" lib.install Dir["target/release/libperry_*.a"] end diff --git a/.github/workflows/simctl-tests.yml b/.github/workflows/simctl-tests.yml index d19a41e111..0eea9e8eab 100644 --- a/.github/workflows/simctl-tests.yml +++ b/.github/workflows/simctl-tests.yml @@ -49,7 +49,7 @@ jobs: - name: Build perry + iOS UI lib run: | - cargo build --release -p perry -p perry-runtime -p perry-stdlib + cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static cargo build --release -p perry-ui-ios --target aarch64-apple-ios-sim # Regression guard for #1311 — the geisterhand glue (perry-ui-ios's diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec810c3208..d4eb551aa5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -348,7 +348,7 @@ jobs: # directly and fail with "Could not find libperry_runtime.a" if the # cached staticlib was invalidated. Build them explicitly. if printf '%s\n' "$scope" | grep -qE '^(perry|perry-stdlib)$'; then - cargo build -p perry-runtime -p perry-stdlib + cargo build -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static fi find target/debug/deps -maxdepth 1 -type f -perm -111 ! -name '*.so' -delete # Large perry / perry-stdlib integration-test binaries: serialize @@ -1223,7 +1223,7 @@ jobs: mysql -h 127.0.0.1 -P 3306 -u root -e "SELECT VERSION();" - name: Build perry compiler - run: cargo build --release -p perry-runtime -p perry-stdlib -p perry + run: cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry - name: Run drizzle-mysql fixture run: | @@ -1268,7 +1268,7 @@ jobs: node-version: '22' - name: Build perry compiler - run: cargo build --release -p perry-runtime -p perry-stdlib -p perry + run: cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry - name: Run Ink link fixture run: | @@ -1317,7 +1317,7 @@ jobs: node-version: '22' - name: Build perry compiler - run: cargo build --release -p perry-runtime -p perry-stdlib -p perry + run: cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry - name: Run Effect fixture run: | @@ -1538,7 +1538,7 @@ jobs: 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 + run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p ${{ matrix.ui_backend }} -p perry-doc-tests - name: Pre-build Apple UI libs for cross-compile (macOS only) if: matrix.os == 'macos-14' @@ -1606,7 +1606,7 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Build release binaries - run: cargo build --release -p perry -p perry-runtime -p perry-stdlib + run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static - name: Report binary sizes run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 664d60ed4a..a7dd3a43c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +## v0.5.1197 — build: #5422 — dev/release/dist build taxonomy, rlib-only runtime/stdlib, slim CLI + +Reworks Perry's build surface so local development is light while official release artifacts +stay explicit and optimized. Four parts (issue #5422): + +**1. Fast `perry-dev` profile.** New `[profile.perry-dev]` inherits `release` but drops the +distribution-grade settings (`lto = false`, `codegen-units = 16`, `opt-level = 1`, +`incremental = true`, no strip). Local loop: `cargo check -p perry` for correctness, +`cargo build --profile perry-dev -p perry` for an optimized dev binary (`target/perry-dev/perry`). + +**2. Staticlib split.** `perry-runtime` and `perry-stdlib` are now `crate-type = ["rlib"]` only, +so a plain `cargo build` no longer emits the heavy `libperry_runtime.a` / `libperry_stdlib.a` as +a side effect. The archives are produced by two new wrapper crates, `perry-runtime-static` and +`perry-stdlib-static` (each `[lib] name = "perry_runtime"` / `"perry_stdlib"`, `crate-type = +["staticlib"]`), so every consumer's `.a` basename is unchanged — only the cargo *package* name +(`-p perry-runtime-static`) changes. The wrappers carry `strip = false` + `codegen-units = 16` +profile overrides (mirroring perry-ext-events #5140) so ThinLTO doesn't internalize and drop the +exported C API. The auto-optimize rebuild path (`optimized_libs.rs`), the geisterhand build +(`library_search.rs`), `stage-npm.sh`'s symbol guard, and every workflow / script that produced +the archives now build the `-static` crates. The wrappers are deliberately **not** in +`default-members`. Validated locally: sentinel symbols present (`check_runtime_symbols.sh`), +prebuilt-link, and auto-optimize-rebuild paths all compile + link + run a hello-world. + +**3. Slim developer CLI.** `crates/perry/Cargo.toml` gains a feature taxonomy: `default = +["full-cli"]` (unchanged shipped CLI) decomposes into `dev-cli` (compile/run/check/types/cache + +watch), `publish-cli`, `mobile-cli`, `updater-cli`, `native-cli`, `audit-cli`, and per-backend +`backend-{js,swiftui,arkts,glance,wear-tiles,wasm}` / `all-codegen-backends`. `cargo build -p +perry --no-default-features --features dev-cli` builds a slimmer compiler CLI: gated commands +drop out of `--help` and disabled `--target` backends return a clear "built without the +`` feature" error. The publish/setup/audit *modules* stay compiled (their config/audit +helpers are used by core paths); only their commands are gated. The 6 codegen-backend crates plus +`notify`, `ed25519-dalek`, `rand` became optional deps. Default / `dev-cli` / bare +`--no-default-features` all compile. + +**4. Explicit `dist` profile.** New `[profile.dist]` mirrors `release` exactly (ThinLTO, +`codegen-units = 1`, `opt-level = 3`, strip) including all per-package overrides, and +`release-packages.yml` now builds official artifacts with `--profile dist` +(`CARGO_PROFILE_DIST_PANIC` for the panic=abort variant; cargo-output paths moved +`target/.../release/` → `target/.../dist/`, while the staging package layout and the +build-from-source Homebrew formula stay on `--release`). `scripts/cargo_timing_summary.py` +parses `cargo --timings` output to surface the slowest build units so regressions are visible. + +Docs: `docs/src/contributing/building.md` documents the three-tier taxonomy, the `-static` +crates, and the slim CLI. + +> **Release validation note:** the `--profile dist` cutover and the `-static` additions across +> `release-packages.yml` could not be exercised by the local build; they need a tag/CI run to +> confirm the full release matrix. + ## v0.5.1196 — fix(codegen): #5431 — cross-module call to a `$`-named exported function returned `undefined` Calling an exported function whose name contains a non-`[A-Za-z0-9_]` character (e.g. diff --git a/CLAUDE.md b/CLAUDE.md index 12a3e2ba46..442c3b0e4d 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.1196 +**Current Version:** 0.5.1197 ## TypeScript Parity Status @@ -49,7 +49,9 @@ PRs from outside contributors should **not** touch `[workspace.package] version` ```bash cargo build --release # Build all crates +cargo build --profile perry-dev -p perry # Fast local dev build (#5422; perry-dev profile) cargo build --release -p perry-runtime -p perry-stdlib # Rebuild runtime (MUST rebuild stdlib too!) +cargo build --release -p perry-runtime-static -p perry-stdlib-static # Emit libperry_{runtime,stdlib}.a (#5422: runtime/stdlib are now rlib-only; the .a comes from these wrapper crates) cargo test --release --workspace \ --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos \ --exclude perry-ui-visionos --exclude perry-ui-android --exclude perry-ui-windows \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a993c856ac..ba1463cad7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,13 @@ cargo test --workspace \ --exclude perry-ui-gtk4 --exclude perry-ui-android --exclude perry-ui-windows ``` +For a faster inner loop, `cargo check -p perry` (correctness only) or +`cargo build --profile perry-dev -p perry` (optimized local dev build). The +`.a` static archives now come from dedicated wrapper crates — build them with +`cargo build --release -p perry-runtime-static -p perry-stdlib-static` when you +need `libperry_{runtime,stdlib}.a`. See `docs/src/contributing/building.md` for +the full dev/release/dist taxonomy and the slim `--features dev-cli` CLI (#5422). + The full README [Development](README.md#development) section has more `cargo run` recipes (HIR dumps, per-crate rebuilds). ## Making changes diff --git a/Cargo.lock b/Cargo.lock index 6b997ac8a6..1504a5e998 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5325,7 +5325,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "base64", @@ -5382,14 +5382,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "cc", "libc", @@ -5397,7 +5397,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "log", @@ -5412,7 +5412,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-hir", @@ -5421,7 +5421,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-hir", @@ -5429,7 +5429,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-dispatch", @@ -5439,7 +5439,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-hir", @@ -5448,7 +5448,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "base64", @@ -5461,7 +5461,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-hir", @@ -5469,7 +5469,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "async-trait", @@ -5498,14 +5498,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "serde", "serde_json", @@ -5513,7 +5513,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1196" +version = "0.5.1197" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5524,7 +5524,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "clap", @@ -5539,14 +5539,14 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-argon2" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "argon2", "perry-ffi", @@ -5554,7 +5554,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "reqwest", @@ -5563,7 +5563,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "bcrypt", "perry-ffi", @@ -5571,7 +5571,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "rusqlite", @@ -5579,7 +5579,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "scraper", @@ -5587,7 +5587,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "perry-runtime", @@ -5595,7 +5595,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "chrono", "cron 0.16.0", @@ -5605,7 +5605,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "chrono", "perry-ffi", @@ -5613,7 +5613,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "rust_decimal", @@ -5621,7 +5621,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "serde_json", @@ -5629,7 +5629,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -5637,7 +5637,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "perry-runtime", @@ -5645,14 +5645,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "bytes", "http-body-util", @@ -5669,7 +5669,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "lazy_static", "perry-ffi", @@ -5681,7 +5681,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "bytes", "lazy_static", @@ -5695,7 +5695,7 @@ dependencies = [ [[package]] name = "perry-ext-http-server" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "bytes", "h2", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "lazy_static", "perry-ffi", @@ -5728,7 +5728,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "jsonwebtoken", @@ -5739,7 +5739,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "lru", "perry-ffi", @@ -5747,7 +5747,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "chrono", "perry-ffi", @@ -5755,7 +5755,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "bson", "futures-util", @@ -5767,7 +5767,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "chrono", "perry-ffi", @@ -5777,7 +5777,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "nanoid", "perry-ffi", @@ -5786,7 +5786,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "perry-runtime", @@ -5798,7 +5798,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "lettre", "perry-ffi", @@ -5808,7 +5808,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "printpdf", @@ -5816,7 +5816,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "sqlx", @@ -5825,7 +5825,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "governor", "perry-ffi", @@ -5833,7 +5833,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "fast_image_resize", "image", @@ -5843,14 +5843,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "lazy_static", "perry-ffi", @@ -5859,7 +5859,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "uuid", @@ -5867,7 +5867,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ffi", "regex", @@ -5877,7 +5877,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "futures-util", "lazy_static", @@ -5889,7 +5889,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "brotli", "flate2", @@ -5898,7 +5898,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "dashmap", "once_cell", @@ -5907,7 +5907,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-api-manifest", @@ -5925,7 +5925,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-diagnostics", @@ -5937,7 +5937,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "base64", @@ -5968,9 +5968,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "perry-runtime-static" +version = "0.5.1197" +dependencies = [ + "perry-runtime", +] + [[package]] name = "perry-stdlib" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "aes 0.8.4", "aes-gcm", @@ -6060,9 +6067,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "perry-stdlib-static" +version = "0.5.1197" +dependencies = [ + "perry-stdlib", +] + [[package]] name = "perry-transform" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "perry-hir", @@ -6072,7 +6086,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -6080,14 +6094,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "itoa", @@ -6104,7 +6118,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "rand 0.8.6", "serde", @@ -6114,7 +6128,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "cairo-rs", @@ -6137,7 +6151,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "block2", @@ -6153,7 +6167,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "block2", @@ -6168,7 +6182,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1196" +version = "0.5.1197" [[package]] name = "perry-ui-test" @@ -6176,11 +6190,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.1196" +version = "0.5.1197" [[package]] name = "perry-ui-tvos" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "block2", @@ -6196,7 +6210,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "block2", @@ -6212,7 +6226,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "block2", "libc", @@ -6225,7 +6239,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "libc", @@ -6242,14 +6256,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "base64", "ed25519-dalek", @@ -6263,7 +6277,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1196" +version = "0.5.1197" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 0ec448c18d..68e62ca97d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,8 @@ members = [ "crates/perry-ui-testkit", "crates/perry-doc-tests", "crates/perry-updater", + "crates/perry-runtime-static", + "crates/perry-stdlib-static", "docs/examples/_fixtures/native-libraries/my-bindings", ] # Only build platform-independent crates by default. @@ -214,8 +216,92 @@ opt-level = "s" # Optimize for size in stdlib strip = false codegen-units = 16 +# Staticlib wrapper crates (#5422). perry-runtime / perry-stdlib are now +# rlib-only; these wrappers re-export their #[no_mangle] C API into +# libperry_runtime.a / libperry_stdlib.a. Thin-LTO would otherwise internalize +# and drop the whole exported C surface (same failure as perry-ext-events, +# #5140), so mirror its settings: more codegen units + no strip keep the C API +# in the archive. +[profile.release.package.perry-runtime-static] +strip = false +codegen-units = 16 +[profile.release.package.perry-stdlib-static] +strip = false +codegen-units = 16 + +# Fast developer profile (#5422). Optimized enough for realistic local runs but +# without the distribution-grade settings that dominate compile time, so the +# edit/build loop stays short. Intended local loop: +# cargo check -p perry # fastest correctness feedback +# cargo build --profile perry-dev -p perry # optimized local development +[profile.perry-dev] +inherits = "release" +lto = false +codegen-units = 16 +incremental = true +strip = false +opt-level = 1 + +# Official distribution profile (#5422). The explicit, named home for the +# release-grade optimization Perry ships. Mirrors [profile.release] exactly — +# including every per-package override below — so official artifacts stay +# equivalent while being selected via `--profile dist`. Keep these blocks in +# sync with their [profile.release.*] counterparts above. +[profile.dist] +inherits = "release" +lto = "thin" +codegen-units = 1 +panic = "unwind" +strip = true +opt-level = 3 + +[profile.dist.package.perry-runtime] +opt-level = 3 +strip = false +[profile.dist.package.perry-ui-macos] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-gtk4] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-windows] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-windows-winui] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-geisterhand] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-ios] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-visionos] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-tvos] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-android] +strip = false +codegen-units = 16 +[profile.dist.package.perry-ui-watchos] +strip = false +codegen-units = 16 +[profile.dist.package.perry-stdlib] +opt-level = "s" +[profile.dist.package.perry-ext-events] +strip = false +codegen-units = 16 +[profile.dist.package.perry-runtime-static] +strip = false +codegen-units = 16 +[profile.dist.package.perry-stdlib-static] +strip = false +codegen-units = 16 + [workspace.package] -version = "0.5.1196" +version = "0.5.1197" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/README.md b/README.md index f9912af233..9e9eeeec22 100644 --- a/README.md +++ b/README.md @@ -877,12 +877,20 @@ compatibility_reports = "off" # #849 compat reports off ```bash cargo build --release # Build everything +cargo build --profile perry-dev -p perry # Fast local dev build (#5422) cargo build --release -p perry-runtime -p perry-stdlib # Rebuild runtime (after changes) +cargo build --release -p perry-runtime-static -p perry-stdlib-static # Emit libperry_*.a (#5422) cargo test --workspace --exclude perry-ui-ios # Run tests cargo run --release -- compile file.ts -o out && ./out # Compile and run cargo run --release -- compile file.ts --print-hir # Debug HIR ``` +The static archives (`libperry_runtime.a` / `libperry_stdlib.a`) are emitted by +the `perry-runtime-static` / `perry-stdlib-static` wrapper crates, so a plain +`cargo build` no longer produces them as a side effect. A slimmer compiler CLI +is available with `cargo build -p perry --no-default-features --features dev-cli`. +See [docs/src/contributing/building.md](docs/src/contributing/building.md). + ### Adding a new feature 1. **HIR** — add node type to `crates/perry-hir/src/ir.rs` diff --git a/crates/perry-runtime-static/Cargo.toml b/crates/perry-runtime-static/Cargo.toml new file mode 100644 index 0000000000..26c96930b1 --- /dev/null +++ b/crates/perry-runtime-static/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "perry-runtime-static" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Staticlib wrapper (#5422): emits libperry_runtime.a from the rlib-only perry-runtime crate" + +# Emits the C-ABI static archive consumed by `perry compile`'s linker. The +# output name stays `perry_runtime` so the produced archive is the canonical +# `libperry_runtime.a` / `perry_runtime.lib` every consumer already looks for — +# only the cargo PACKAGE name (`-p perry-runtime-static`) changes. +[lib] +name = "perry_runtime" +crate-type = ["staticlib"] +path = "src/lib.rs" + +[features] +# A plain `cargo build -p perry-runtime-static` reproduces today's full +# `cargo build -p perry-runtime` archive. The auto-optimize path instead builds +# with `--no-default-features --features perry-runtime/` (cargo accepts +# the `dep/feature` form because perry-runtime is in this crate's dependency +# graph), so no per-feature forwarding is needed here. +default = ["perry-runtime/default"] + +[dependencies] +# default-features = false so `--no-default-features` on this wrapper actually +# strips perry-runtime down to the auto-optimize subset; without it the dep edge +# would drag perry-runtime's defaults back on (cf. the root Cargo.toml note). +perry-runtime = { path = "../perry-runtime", default-features = false } diff --git a/crates/perry-runtime-static/src/lib.rs b/crates/perry-runtime-static/src/lib.rs new file mode 100644 index 0000000000..cec4b52aca --- /dev/null +++ b/crates/perry-runtime-static/src/lib.rs @@ -0,0 +1,15 @@ +//! Staticlib wrapper for `perry-runtime` (#5422). +//! +//! `perry-runtime` is now an rlib-only crate so a plain `cargo build` of the +//! workspace no longer emits the heavy `libperry_runtime.a` as a side effect. +//! This thin wrapper is the only crate with `crate-type = ["staticlib"]`, so +//! the archive is produced exactly when requested (`cargo build -p +//! perry-runtime-static`). Its `[lib] name = "perry_runtime"` keeps the output +//! basename (`libperry_runtime.a` / `perry_runtime.lib`) that every consumer — +//! `library_search.rs`, the auto-optimize linker, `stage-npm.sh`, the symbol +//! guard — already resolves. +//! +//! Pulling `perry-runtime` in as a dependency links all of its object code +//! (including the `#[no_mangle]` / `#[used]` C API surface that generated +//! native code calls) into this archive. +extern crate perry_runtime; diff --git a/crates/perry-runtime/Cargo.toml b/crates/perry-runtime/Cargo.toml index 6846b3ddd9..2ebf20cb9a 100644 --- a/crates/perry-runtime/Cargo.toml +++ b/crates/perry-runtime/Cargo.toml @@ -5,8 +5,12 @@ edition.workspace = true license.workspace = true description = "Runtime library: GC, JSValue, builtins" +# rlib-only (#5422): the staticlib (libperry_runtime.a) is emitted by the +# perry-runtime-static wrapper crate so a plain `cargo build` of the workspace +# no longer produces the heavy archive as a side effect. Build the archive +# explicitly with `cargo build -p perry-runtime-static`. [lib] -crate-type = ["rlib", "staticlib"] +crate-type = ["rlib"] [features] # `default` keeps the shipped prebuilt libperry_runtime.a and plain diff --git a/crates/perry-stdlib-static/Cargo.toml b/crates/perry-stdlib-static/Cargo.toml new file mode 100644 index 0000000000..c361c3c7aa --- /dev/null +++ b/crates/perry-stdlib-static/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "perry-stdlib-static" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Staticlib wrapper (#5422): emits libperry_stdlib.a from the rlib-only perry-stdlib crate" + +# Output name stays `perry_stdlib` so the produced archive is the canonical +# `libperry_stdlib.a` / `perry_stdlib.lib` every consumer already looks for. +[lib] +name = "perry_stdlib" +crate-type = ["staticlib"] +path = "src/lib.rs" + +[features] +# Plain build reproduces today's full `cargo build -p perry-stdlib` archive; +# auto-optimize uses `--no-default-features --features perry-stdlib/`. +default = ["perry-stdlib/default"] + +[dependencies] +perry-stdlib = { path = "../perry-stdlib", default-features = false } diff --git a/crates/perry-stdlib-static/src/lib.rs b/crates/perry-stdlib-static/src/lib.rs new file mode 100644 index 0000000000..92c4059e20 --- /dev/null +++ b/crates/perry-stdlib-static/src/lib.rs @@ -0,0 +1,7 @@ +//! Staticlib wrapper for `perry-stdlib` (#5422). See `perry-runtime-static`'s +//! lib.rs for the rationale. `[lib] name = "perry_stdlib"` keeps the canonical +//! `libperry_stdlib.a` / `perry_stdlib.lib` output basename, and depending on +//! `perry-stdlib` links its full object code (including the runtime symbols it +//! re-bundles via its own `perry-runtime` / `perry-updater` deps) into the +//! archive. +extern crate perry_stdlib; diff --git a/crates/perry-stdlib/Cargo.toml b/crates/perry-stdlib/Cargo.toml index 9739850925..acfcef584b 100644 --- a/crates/perry-stdlib/Cargo.toml +++ b/crates/perry-stdlib/Cargo.toml @@ -5,8 +5,12 @@ edition.workspace = true license.workspace = true description = "Standard library implementations: fs, http, crypto, mysql, postgres" +# rlib-only (#5422): the staticlib (libperry_stdlib.a) is emitted by the +# perry-stdlib-static wrapper crate so a plain `cargo build` of the workspace +# no longer produces the heavy archive as a side effect. Build the archive +# explicitly with `cargo build -p perry-stdlib-static`. [lib] -crate-type = ["rlib", "staticlib"] +crate-type = ["rlib"] [features] # Default: include everything for backwards compatibility diff --git a/crates/perry/Cargo.toml b/crates/perry/Cargo.toml index e47137b609..1bc2dc99f5 100644 --- a/crates/perry/Cargo.toml +++ b/crates/perry/Cargo.toml @@ -18,12 +18,15 @@ perry-transform.workspace = true perry-codegen.workspace = true perry-runtime.workspace = true perry-diagnostics.workspace = true -perry-codegen-js.workspace = true -perry-codegen-swiftui.workspace = true -perry-codegen-arkts.workspace = true -perry-codegen-glance.workspace = true -perry-codegen-wear-tiles.workspace = true -perry-codegen-wasm.workspace = true +# Codegen backends for non-native targets — optional so a slim dev CLI +# (--no-default-features --features dev-cli) drops them. full-cli (default) +# re-enables all via all-codegen-backends, keeping the shipped CLI unchanged. +perry-codegen-js = { workspace = true, optional = true } +perry-codegen-swiftui = { workspace = true, optional = true } +perry-codegen-arkts = { workspace = true, optional = true } +perry-codegen-glance = { workspace = true, optional = true } +perry-codegen-wear-tiles = { workspace = true, optional = true } +perry-codegen-wasm = { workspace = true, optional = true } perry-api-manifest = { workspace = true, features = ["serde"] } clap.workspace = true @@ -37,7 +40,8 @@ console.workspace = true dialoguer.workspace = true toml.workspace = true walkdir.workspace = true -notify.workspace = true +# `perry dev` file-watch loop only (commands/dev.rs); optional under watch-cli. +notify = { workspace = true, optional = true } reqwest.workspace = true tokio.workspace = true tokio-tungstenite.workspace = true @@ -72,15 +76,75 @@ fslock = "0.2" # feature — listing them as direct deps just makes them callable from # the CLI module. ed25519-dalek 2.1 takes any CryptoRng; we use rand 0.9 # for the keygen seed. -ed25519-dalek = { version = "2.1", features = ["rand_core"] } +ed25519-dalek = { version = "2.1", features = ["rand_core"], optional = true } sha2 = "0.11" hex = "0.4" -rand = "0.9" +rand = { version = "0.9", optional = true } # chrono is already a transitive dep through perry-stdlib; listing it # here makes the year accessor available to `perry native init`'s # LICENSE template substitution. chrono = "0.4" +[features] +# The default build is the full official CLI — every command and codegen +# backend. The shipped binary is unchanged from before #5422. +default = ["full-cli"] + +full-cli = [ + "dev-cli", + "publish-cli", + "mobile-cli", + "updater-cli", + "native-cli", + "audit-cli", + "all-codegen-backends", +] + +# Slim compiler-development CLI: compile / run / check / types / cache + the +# `perry dev` watch loop. Build with: +# cargo build -p perry --no-default-features --features dev-cli +dev-cli = ["compile-cli", "check-cli", "types-cli", "cache-cli", "watch-cli"] + +# Command groups. compile/check/types/cache pull no optional deps — their code +# is always compiled — but exist so a build can name exactly the surface it +# wants and so disabled commands drop out of `--help`. +compile-cli = [] +check-cli = [] +types-cli = [] +cache-cli = [] +watch-cli = ["dep:notify"] + +# `perry publish` / `setup` / `audit` keep their modules compiled even when these +# features are off (their config/audit helpers are used by core code paths); the +# features gate the *commands* (so they leave `--help` in a slim build). +publish-cli = [] +mobile-cli = [] +audit-cli = [] +# These commands are fully standalone, so the feature also drops their module +# (and, for updater, its signing crates). +updater-cli = ["dep:ed25519-dalek", "dep:rand"] +native-cli = [] + +# Codegen backends for non-native targets. Each enables one backend crate; +# `all-codegen-backends` (in full-cli) turns them all on so the default build is +# unchanged. A slim CLI without a backend rejects its `--target` with a clear +# "built without the feature" error. `backend-js`/`backend-wasm` gate +# the shared web/wasm path together. +backend-js = ["dep:perry-codegen-js"] +backend-swiftui = ["dep:perry-codegen-swiftui"] +backend-arkts = ["dep:perry-codegen-arkts"] +backend-glance = ["dep:perry-codegen-glance"] +backend-wear-tiles = ["dep:perry-codegen-wear-tiles"] +backend-wasm = ["dep:perry-codegen-wasm"] +all-codegen-backends = [ + "backend-js", + "backend-swiftui", + "backend-arkts", + "backend-glance", + "backend-wear-tiles", + "backend-wasm", +] + [dev-dependencies] tempfile = "3" swc_common.workspace = true diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index b5ba469eed..009793ca9b 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -51,10 +51,12 @@ use app_metadata::rust_target_triple; // compile.rs anymore now that the iOS bundle code moved out). pub(crate) use audit_manifest::allowlist_matches; use bootstrap::{ - apply_i18n_pass, bundle_extensions_into_ctx, dump_hir_for_debug, harvest_harmonyos_index_ets, - maybe_init_type_checker, rerun_collect_with_class_field_types, run_native_instance_fixups, - run_post_collect_preflight, + apply_i18n_pass, bundle_extensions_into_ctx, dump_hir_for_debug, maybe_init_type_checker, + rerun_collect_with_class_field_types, run_native_instance_fixups, run_post_collect_preflight, }; +// HarmonyOS ArkTS harvest only exists when the arkts backend is compiled in. +#[cfg(feature = "backend-arkts")] +use bootstrap::harvest_harmonyos_index_ets; use build_cache::BuildCacheProbe; use bundle_apple::{bundle_for_tvos, bundle_for_visionos, bundle_for_watchos}; use bundle_ios::build_ios_app_bundle; @@ -99,16 +101,38 @@ use strip_dedup::{ strip_duplicate_objects_from_well_known_lib, }; use targets::{ - apple_sdk_version, compile_for_android_widget, compile_for_ios_widget, compile_for_wasm, - compile_for_watchos_widget, compile_for_wearos_tile, find_visionos_swift_runtime, - find_watchos_swift_runtime, generate_embedded_js_object, generate_js_bundle, + apple_sdk_version, find_visionos_swift_runtime, find_watchos_swift_runtime, + generate_embedded_js_object, generate_js_bundle, }; +// Codegen-backend entrypoints (#5422) — only present when their backend feature +// is compiled in. The matching `--target` routing below errors cleanly when a +// backend was built out. +#[cfg(feature = "backend-glance")] +use targets::compile_for_android_widget; +#[cfg(feature = "backend-swiftui")] +use targets::{compile_for_ios_widget, compile_for_watchos_widget}; +#[cfg(feature = "backend-wasm")] +use targets::compile_for_wasm; +#[cfg(feature = "backend-wear-tiles")] +use targets::compile_for_wearos_tile; use super::progress::{ProgressSnapshot, VerboseProgress}; mod types; pub use types::*; +/// Error text for a `--target` whose codegen backend was compiled out (#5422). +/// Always defined so the `#[cfg(not(...))]` routing arms can call it; unused in +/// a full-cli build, hence the allow. +#[allow(dead_code)] +fn backend_disabled_msg(target: &str, feature: &str) -> String { + format!( + "target '{target}' needs the '{feature}' codegen backend, but this perry \ + was built without it. Rebuild with `--features {feature}` (or the default \ + `full-cli` / `all-codegen-backends`)." + ) +} + struct NativeObjectArtifact { path: PathBuf, bytes: Option>, @@ -580,7 +604,17 @@ pub fn run_with_parse_cache( // --- Web/WASM target: emit WASM binary + JS runtime bridge --- if matches!(args.target.as_deref(), Some("web") | Some("wasm")) { - return compile_for_wasm(&ctx, &args, format); + #[cfg(feature = "backend-wasm")] + { + return compile_for_wasm(&ctx, &args, format); + } + #[cfg(not(feature = "backend-wasm"))] + { + anyhow::bail!(backend_disabled_msg( + args.target.as_deref().unwrap_or("wasm"), + "backend-wasm", + )); + } } // --- Widget targets: emit platform-specific source + optional native provider --- @@ -588,22 +622,51 @@ pub fn run_with_parse_cache( args.target.as_deref(), Some("ios-widget") | Some("ios-widget-simulator") ) { - return compile_for_ios_widget(&ctx, &args, format); + #[cfg(feature = "backend-swiftui")] + { + return compile_for_ios_widget(&ctx, &args, format); + } + #[cfg(not(feature = "backend-swiftui"))] + { + anyhow::bail!(backend_disabled_msg("ios-widget", "backend-swiftui")); + } } if matches!( args.target.as_deref(), Some("watchos-widget") | Some("watchos-widget-simulator") ) { - return compile_for_watchos_widget(&ctx, &args, format); + #[cfg(feature = "backend-swiftui")] + { + return compile_for_watchos_widget(&ctx, &args, format); + } + #[cfg(not(feature = "backend-swiftui"))] + { + anyhow::bail!(backend_disabled_msg("watchos-widget", "backend-swiftui")); + } } if args.target.as_deref() == Some("android-widget") { - return compile_for_android_widget(&ctx, &args, format); + #[cfg(feature = "backend-glance")] + { + return compile_for_android_widget(&ctx, &args, format); + } + #[cfg(not(feature = "backend-glance"))] + { + anyhow::bail!(backend_disabled_msg("android-widget", "backend-glance")); + } } if args.target.as_deref() == Some("wearos-tile") { - return compile_for_wearos_tile(&ctx, &args, format); + #[cfg(feature = "backend-wear-tiles")] + { + return compile_for_wearos_tile(&ctx, &args, format); + } + #[cfg(not(feature = "backend-wear-tiles"))] + { + anyhow::bail!(backend_disabled_msg("wearos-tile", "backend-wear-tiles")); + } } run_native_instance_fixups(&mut ctx); + #[cfg(feature = "backend-arkts")] harvest_harmonyos_index_ets(&args, &mut ctx, format); let i18n_table = apply_i18n_pass(&mut ctx, i18n_config.as_ref(), &i18n_translations, format); diff --git a/crates/perry/src/commands/compile/bootstrap.rs b/crates/perry/src/commands/compile/bootstrap.rs index ab16527f6c..a6ea2b447b 100644 --- a/crates/perry/src/commands/compile/bootstrap.rs +++ b/crates/perry/src/commands/compile/bootstrap.rs @@ -1052,6 +1052,7 @@ pub(super) fn run_native_instance_fixups(ctx: &mut CompilationContext) { /// Also flips `ctx.needs_ui` back to false so the link path skips the /// perry-ui-* lib check (which would fail on the OHOS target since no /// such lib exists). +#[cfg(feature = "backend-arkts")] pub(super) fn harvest_harmonyos_index_ets( args: &CompileArgs, ctx: &mut CompilationContext, diff --git a/crates/perry/src/commands/compile/library_search.rs b/crates/perry/src/commands/compile/library_search.rs index 3d8ea9055b..da49f03a8b 100644 --- a/crates/perry/src/commands/compile/library_search.rs +++ b/crates/perry/src/commands/compile/library_search.rs @@ -1353,7 +1353,7 @@ pub(super) fn build_geisterhand_libs(target: Option<&str>, format: OutputFormat) "Cannot auto-build geisterhand libraries: Perry workspace not found.\n\ Build manually from the Perry source directory:\n \ CARGO_TARGET_DIR=target/geisterhand cargo build --release \\\n \ - -p perry-runtime --features geisterhand \\\n \ + -p perry-runtime-static --features perry-runtime/geisterhand \\\n \ -p {} --features geisterhand \\\n \ -p perry-ui-geisterhand", ui_crate @@ -1369,8 +1369,11 @@ pub(super) fn build_geisterhand_libs(target: Option<&str>, format: OutputFormat) ) .arg("build") .arg("--release") + // #5422 — staticlib (.a) now comes from the *-static wrapper crates; + // perry-runtime/perry-stdlib are rlib-only. The `perry-runtime/` + // selector still resolves via the wrapper's dependency graph. .arg("-p") - .arg("perry-runtime") + .arg("perry-runtime-static") .arg("--features") .arg("perry-runtime/geisterhand") .arg("-p") @@ -1388,7 +1391,7 @@ pub(super) fn build_geisterhand_libs(target: Option<&str>, format: OutputFormat) // perry-stdlib's async surface (`perry_ffi_promise_*`) had no // consistent stdlib to link against under --enable-geisterhand (#1383). .arg("-p") - .arg("perry-stdlib"); + .arg("perry-stdlib-static"); // Add cross-compilation target if needed if let Some(triple) = rust_target_triple(target) { diff --git a/crates/perry/src/commands/compile/optimized_libs.rs b/crates/perry/src/commands/compile/optimized_libs.rs index 1b95a448f8..fa07bf4f7e 100644 --- a/crates/perry/src/commands/compile/optimized_libs.rs +++ b/crates/perry/src/commands/compile/optimized_libs.rs @@ -799,10 +799,15 @@ pub(super) fn build_optimized_libs( .env("CARGO_TARGET_DIR", &cargo_env_dir) .arg("build") .arg("--release") + // #5422 — the staticlib (.a) is now emitted by the perry-runtime-static + // / perry-stdlib-static wrapper crates, not perry-runtime/perry-stdlib + // themselves (which are rlib-only). The `perry-runtime/` strings in + // `cross_features` still resolve because perry-runtime is in each + // wrapper's dependency graph (cargo accepts the `dep/feature` form). .arg("-p") - .arg("perry-runtime") + .arg("perry-runtime-static") .arg("-p") - .arg("perry-stdlib") + .arg("perry-stdlib-static") .arg("--no-default-features"); // #507 — rebuild tokio-using ext crates in the same cargo // invocation as perry-stdlib so cargo unifies tokio across them. diff --git a/crates/perry/src/commands/compile/targets.rs b/crates/perry/src/commands/compile/targets.rs index 86d24cf2c2..9e43c89880 100644 --- a/crates/perry/src/commands/compile/targets.rs +++ b/crates/perry/src/commands/compile/targets.rs @@ -219,6 +219,7 @@ fn c_string_literal(s: &str) -> String { /// Compile for iOS widget target: emit SwiftUI source for WidgetKit extension. /// Auto-invokes `swiftc` to produce a built `WidgetExtension.appex/` directory /// unless `--skip-swift-build` is passed. +#[cfg(feature = "backend-swiftui")] pub(super) fn compile_for_ios_widget( ctx: &CompilationContext, args: &CompileArgs, @@ -400,6 +401,7 @@ pub(super) fn compile_for_ios_widget( /// widgets declare a *different* app group we warn rather than silently routing /// them all through the first suite (validation is otherwise out of scope; the /// common case is a single bundle-wide app group). +#[cfg(feature = "backend-swiftui")] fn write_shared_widget_runtime( output_dir: &Path, widgets: &[&perry_hir::ir::WidgetDecl], @@ -534,6 +536,7 @@ fn build_widget_appex( } /// Compile for watchOS widget target: emit SwiftUI + native timeline (accessory families) +#[cfg(feature = "backend-swiftui")] pub(super) fn compile_for_watchos_widget( ctx: &CompilationContext, args: &CompileArgs, @@ -928,6 +931,7 @@ pub(super) fn compile_metallib_for_bundle( } /// Compile for Android widget target: emit Kotlin/Glance source + JNI bridge +#[cfg(feature = "backend-glance")] pub(super) fn compile_for_android_widget( ctx: &CompilationContext, args: &CompileArgs, @@ -1193,6 +1197,7 @@ mod native_shader_tool_tests { } /// Compile for Wear OS tile target: emit Kotlin Tiles source + JNI bridge +#[cfg(feature = "backend-wear-tiles")] pub(super) fn compile_for_wearos_tile( ctx: &CompilationContext, args: &CompileArgs, @@ -1292,6 +1297,7 @@ pub(super) fn compile_for_wearos_tile( // #854: web-target compile path; currently unreferenced from the dispatch // but kept as the JS+HTML emission entry point for `--target web`. #[allow(dead_code)] +#[cfg(feature = "backend-js")] pub(super) fn compile_for_web( ctx: &CompilationContext, args: &CompileArgs, @@ -1449,6 +1455,7 @@ pub(super) fn compile_for_web( } /// Compile for WebAssembly target: emit WASM binary + JS runtime bridge +#[cfg(feature = "backend-wasm")] pub(super) fn compile_for_wasm( ctx: &CompilationContext, args: &CompileArgs, diff --git a/crates/perry/src/commands/mod.rs b/crates/perry/src/commands/mod.rs index 43ead0241f..fb506401c7 100644 --- a/crates/perry/src/commands/mod.rs +++ b/crates/perry/src/commands/mod.rs @@ -1,5 +1,10 @@ //! CLI command implementations +// Feature-gated command modules (#5422). Only modules that nothing in the +// always-compiled core depends on are gated; `audit`, `publish` and `setup` +// stay compiled because their audit/config helpers are used by core paths +// (telemetry, login, run, compile/bundle_ios, publish itself). +#[cfg(feature = "mobile-cli")] pub mod appstore; pub mod attest; pub mod audit; @@ -7,6 +12,7 @@ pub mod cache; pub mod check; pub mod compile; pub mod deps; +#[cfg(feature = "watch-cli")] pub mod dev; pub mod doctor; pub mod explain; @@ -19,6 +25,7 @@ pub mod install; pub mod lock; pub mod login; pub mod lower_diagnostic; +#[cfg(feature = "native-cli")] pub mod native; pub mod perry_lock; pub(crate) mod progress; @@ -31,6 +38,9 @@ pub mod stdlib_features; pub mod typecheck; pub mod types; pub mod update; +#[cfg(feature = "updater-cli")] pub mod updater; +#[cfg(feature = "audit-cli")] pub mod verify; +#[cfg(feature = "mobile-cli")] pub mod widget; diff --git a/crates/perry/src/main.rs b/crates/perry/src/main.rs index 761fc7dd07..86afb64a50 100644 --- a/crates/perry/src/main.rs +++ b/crates/perry/src/main.rs @@ -114,24 +114,29 @@ enum Commands { Explain(commands::explain::ExplainArgs), /// Build, sign, package and publish your app + #[cfg(feature = "publish-cli")] Publish(commands::publish::PublishArgs), /// Set up credentials for App Store or Google Play distribution + #[cfg(feature = "mobile-cli")] Setup(commands::setup::SetupArgs), /// Check for updates and self-update Perry Update(commands::update::UpdateArgs), /// Scan TypeScript source for security vulnerabilities + #[cfg(feature = "audit-cli")] Audit(commands::audit::AuditArgs), /// Submit compiled binary for runtime verification + #[cfg(feature = "audit-cli")] Verify(commands::verify::VerifyArgs), /// Compile and run a TypeScript file in one step Run(commands::run::RunArgs), /// Watch TypeScript source and auto-recompile on changes + #[cfg(feature = "watch-cli")] Dev(commands::dev::DevArgs), /// Internationalization tools (extract strings, manage locales) @@ -141,6 +146,7 @@ enum Commands { Login(commands::login::LoginArgs), /// App Store management (release notes, metadata) + #[cfg(feature = "mobile-cli")] Appstore(commands::appstore::AppStoreArgs), /// Generate TypeScript type stubs for Perry built-in modules @@ -154,6 +160,7 @@ enum Commands { /// `perry updater keygen` — generate Ed25519 keypair. /// `perry updater sign` — sign a binary for a v2 manifest entry. /// `perry updater verify` — sanity-check a v2 signature locally. + #[cfg(feature = "updater-cli")] Updater(commands::updater::UpdaterArgs), /// Native-bindings package tooling (#466 Phase 3). @@ -162,6 +169,7 @@ enum Commands { /// `perry native validate` — diff the manifest vs. the /// staticlib's exported symbols. /// `perry native list` — list bundled well-known bindings. + #[cfg(feature = "native-cli")] Native(commands::native::NativeArgs), /// WidgetKit / Glance build glue (issue #676). @@ -171,6 +179,7 @@ enum Commands { /// to `perry.toml` so the next `perry compile --target ios` builds /// the widget and embeds the produced `.appex` under /// `.app/Frameworks/`. + #[cfg(feature = "mobile-cli")] Widget(commands::widget::WidgetArgs), /// Supply-chain lockfile for `perry.nativeLibrary` archives (#498). @@ -398,6 +407,7 @@ fn main_inner() -> Result<()> { let command_name = match &command { Commands::Compile(_) => Some("compile"), Commands::Init(_) => Some("init"), + #[cfg(feature = "publish-cli")] Commands::Publish(_) => Some("publish"), Commands::Doctor(_) => Some("doctor"), Commands::Update(_) => Some("update"), @@ -424,24 +434,33 @@ fn main_inner() -> Result<()> { r.map(|_| ()) } Commands::Run(args) => commands::run::run(args, cli.format, use_color, cli.verbose), + #[cfg(feature = "watch-cli")] Commands::Dev(args) => commands::dev::run(args, cli.format, use_color, cli.verbose), Commands::Check(args) => commands::check::run(args, cli.format, use_color, cli.verbose), Commands::Init(args) => commands::init::run(args, cli.format, use_color), Commands::Install(args) => commands::install::run(args, cli.format, use_color), Commands::Doctor(args) => commands::doctor::run(args, cli.format, use_color), Commands::Explain(args) => commands::explain::run(args, cli.format, use_color), + #[cfg(feature = "publish-cli")] Commands::Publish(args) => commands::publish::run(args, cli.format, use_color, cli.verbose), + #[cfg(feature = "mobile-cli")] Commands::Setup(args) => commands::setup::run(args), Commands::Update(args) => commands::update::run(args, cli.format, use_color, cli.verbose), + #[cfg(feature = "audit-cli")] Commands::Audit(args) => commands::audit::run(args, cli.format, use_color), + #[cfg(feature = "audit-cli")] Commands::Verify(args) => commands::verify::run(args, cli.format, use_color), Commands::I18n(args) => commands::i18n::run(args, cli.format), Commands::Login(args) => commands::login::run(args, cli.format, use_color), + #[cfg(feature = "mobile-cli")] Commands::Appstore(args) => commands::appstore::run(args), Commands::Types(args) => commands::types::run(args, cli.format, use_color), Commands::Cache(args) => commands::cache::run(args, cli.format), + #[cfg(feature = "updater-cli")] Commands::Updater(args) => commands::updater::run(args), + #[cfg(feature = "native-cli")] Commands::Native(args) => commands::native::run(args, cli.format, use_color), + #[cfg(feature = "mobile-cli")] Commands::Widget(args) => commands::widget::run(args, cli.format, use_color), Commands::Lock(args) => commands::lock::run(args, cli.format, use_color), }; diff --git a/docs/src/contributing/building.md b/docs/src/contributing/building.md index f81470ee3e..d43fad37e5 100644 --- a/docs/src/contributing/building.md +++ b/docs/src/contributing/building.md @@ -17,18 +17,61 @@ cargo build --release The binary is at `target/release/perry`. +## Build taxonomy (dev / release / dist) + +Perry has three build profiles, each tuned for a different job (#5422): + +| Goal | Command | Profile | +|------|---------|---------| +| Fastest correctness feedback | `cargo check -p perry` | — | +| Optimized **local** development | `cargo build --profile perry-dev -p perry` | `perry-dev` | +| Release-compatible build | `cargo build --release` | `release` | +| Official distribution artifacts | `cargo build --profile dist ...` | `dist` | + +- **`perry-dev`** inherits `release` but disables the expensive distribution + settings (`lto = false`, `codegen-units = 16`, `opt-level = 1`, + `incremental = true`, no strip) so the edit/build loop stays short. Output is + at `target/perry-dev/perry`. +- **`dist`** mirrors `release` exactly (ThinLTO, `codegen-units = 1`, + `opt-level = 3`, strip) and is the explicit, named profile the release + workflows use for shipped artifacts. Output is at `target/dist/`. + +After a `--timings` build, `scripts/cargo_timing_summary.py` prints the slowest +units so build-time regressions are visible. + ## Build Specific Crates ```bash # Runtime only (must rebuild stdlib too!) cargo build --release -p perry-runtime -p perry-stdlib +# The .a static archives are emitted by separate wrapper crates (#5422), so a +# plain `cargo build` no longer produces them as a side effect. Build them +# explicitly when you need libperry_runtime.a / libperry_stdlib.a (e.g. to link +# compiled programs without the auto-optimize rebuild): +cargo build --release -p perry-runtime-static -p perry-stdlib-static + # Codegen only cargo build --release -p perry-codegen-llvm ``` > **Important**: When rebuilding `perry-runtime`, you must also rebuild `perry-stdlib` because `libperry_stdlib.a` embeds perry-runtime as a static dependency. +## Slim developer CLI + +The default build is the full official CLI. For compiler work you can build a +slimmer CLI that omits the publish / mobile / updater / native / audit commands +and the non-native codegen backends (#5422): + +```bash +cargo build -p perry --no-default-features --features dev-cli +``` + +`dev-cli` keeps `compile` / `run` / `check` / `types` / `cache` / `dev`. Disabled +commands drop out of `--help`, and disabled `--target` backends report a clear +"built without the `` feature" error. See `crates/perry/Cargo.toml` for +the full feature list (`full-cli`, `publish-cli`, `backend-wasm`, …). + ## Run Tests ```bash diff --git a/run_parity_tests.sh b/run_parity_tests.sh index f3bb4d44d1..82951806b1 100755 --- a/run_parity_tests.sh +++ b/run_parity_tests.sh @@ -350,7 +350,7 @@ echo "" TARGET_DIR="${CARGO_TARGET_DIR:-$SCRIPT_DIR/target}" PERRY_BIN="$TARGET_DIR/release/perry" echo "Building compiler (release)..." -BUILD_PACKAGES=(-p perry -p perry-runtime -p perry-stdlib) +BUILD_PACKAGES=(-p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static) BUILD_FEATURES=() needs_wasm_host=0 if [[ -n "${PERRY_NO_AUTO_OPTIMIZE:-}" && "$TEST_SUITE" == "node-suite" ]]; then @@ -418,7 +418,7 @@ if [[ "$needs_wasm_host" -eq 1 ]]; then # feature while building the `perry` binary would make the CLI link against # unresolved perry_wasm_host_* symbols. echo "Building WebAssembly host runtime (release)..." - if ! cargo build --release --quiet -p perry-runtime -p perry-wasm-host --features perry-runtime/wasm-host 2>/dev/null; then + if ! cargo build --release --quiet -p perry-runtime-static -p perry-wasm-host --features perry-runtime/wasm-host 2>/dev/null; then echo -e "${RED}Failed to build WebAssembly host runtime archives${NC}" exit 1 fi diff --git a/scripts/bisect_1114.sh b/scripts/bisect_1114.sh index 5ef882579d..f8691c8c47 100755 --- a/scripts/bisect_1114.sh +++ b/scripts/bisect_1114.sh @@ -73,7 +73,7 @@ FIRST_BAD="" for sha in "${COMMITS[@]}"; do echo "==> checkout $sha" git checkout -q "$sha" - cargo build --release -p perry-runtime -p perry-stdlib -p perry --quiet 2>&1 \ + cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry --quiet 2>&1 \ | tail -3 || { echo "build failed at $sha — skipping"; continue; } echo "==> probing CPU at $sha" if timeout "$TIMEOUT" bash -c "$(declare -f probe_cpu verdict_at_head); verdict_at_head"; then diff --git a/scripts/cargo_timing_summary.py b/scripts/cargo_timing_summary.py new file mode 100755 index 0000000000..c6d9e7032b --- /dev/null +++ b/scripts/cargo_timing_summary.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Summarize Cargo `--timings` output: print the slowest build units (#5422). + +Cargo writes an HTML timing report to `target/cargo-timings/cargo-timing-*.html` +(and a `cargo-timing.html` symlink to the latest) whenever a build runs with +`--timings`. That HTML embeds the per-unit data as a `const UNIT_DATA = [...]` +JSON array. This script extracts it and prints the top-N units by wall-clock +duration so release build regressions are visible without weakening any +optimization settings. + +Usage: + cargo build --profile dist -p perry --timings + scripts/cargo_timing_summary.py # latest report, top 15 + scripts/cargo_timing_summary.py --top 30 + scripts/cargo_timing_summary.py path/to/cargo-timing.html + +Exit codes: 0 on success, 1 if no timing report was found. +""" +from __future__ import annotations + +import argparse +import glob +import json +import os +import re +import sys + +DEFAULT_GLOB = "target/cargo-timings/cargo-timing-*.html" + + +def find_latest_report() -> str | None: + # Prefer the stable `cargo-timing.html` pointer, else the newest timestamped one. + pointer = "target/cargo-timings/cargo-timing.html" + if os.path.exists(pointer): + return pointer + reports = glob.glob(DEFAULT_GLOB) + if not reports: + return None + return max(reports, key=os.path.getmtime) + + +def extract_units(html: str) -> list[dict]: + # Cargo embeds: const UNIT_DATA = [ {...}, ... ]; + m = re.search(r"UNIT_DATA\s*=\s*(\[.*?\]);", html, re.DOTALL) + if not m: + raise ValueError("could not find UNIT_DATA in the timing report") + return json.loads(m.group(1)) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("report", nargs="?", help="path to a cargo-timing*.html report") + ap.add_argument("--top", type=int, default=15, help="how many slow units to show") + args = ap.parse_args() + + report = args.report or find_latest_report() + if not report or not os.path.exists(report): + print(f"error: no cargo timing report found (looked for {DEFAULT_GLOB}).\n" + f" run a build with `--timings` first.", file=sys.stderr) + return 1 + + units = extract_units(open(report, encoding="utf-8").read()) + # Each unit has at least: name, version, duration (seconds), and usually + # `rmeta_time` (time until dependents could start). Sort by total duration. + units.sort(key=lambda u: u.get("duration", 0.0), reverse=True) + + total = sum(u.get("duration", 0.0) for u in units) + print(f"# Cargo build timing summary ({os.path.basename(report)})") + print(f"# {len(units)} units, {total:.1f}s total compile time (sum, not wall-clock)\n") + print(f"{'rank':>4} {'seconds':>8} {'rmeta':>7} unit") + print(f"{'----':>4} {'-------':>8} {'-----':>7} ----") + for i, u in enumerate(units[: args.top], 1): + dur = u.get("duration", 0.0) + rmeta = u.get("rmeta_time") + rmeta_s = f"{rmeta:.1f}" if isinstance(rmeta, (int, float)) else "-" + name = f"{u.get('name', '?')} v{u.get('version', '?')}" + print(f"{i:>4} {dur:>8.1f} {rmeta_s:>7} {name}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/run_doc_tests.sh b/scripts/run_doc_tests.sh index d9e6b2cfac..f4a5bffdf0 100755 --- a/scripts/run_doc_tests.sh +++ b/scripts/run_doc_tests.sh @@ -22,7 +22,7 @@ cd "$REPO_ROOT" # letting PERRY_NO_AUTO_OPTIMIZE=1 below short-circuit the per-test # specialized rebuild (~30-200s saved per test). cargo build --release \ - -p perry -p perry-runtime -p perry-stdlib -p perry-doc-tests + -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry-doc-tests # Disable per-test auto-optimize for HOST runs only. With this set, # `perry compile` short-circuits the cargo-rebuild step in diff --git a/scripts/run_memory_stability_tests.sh b/scripts/run_memory_stability_tests.sh index 033d3737e4..c0485f8dbb 100755 --- a/scripts/run_memory_stability_tests.sh +++ b/scripts/run_memory_stability_tests.sh @@ -322,7 +322,7 @@ print_gc_evidence_artifacts() { init_gc_evidence_outputs -cargo build --release -p perry-runtime -p perry-stdlib -p perry --quiet +cargo build --release -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static -p perry --quiet PERRY=./target/release/perry PYTHON=${PYTHON:-python3} diff --git a/scripts/run_tty_pty_smoke.sh b/scripts/run_tty_pty_smoke.sh index 471bca04e8..db5854f0d8 100755 --- a/scripts/run_tty_pty_smoke.sh +++ b/scripts/run_tty_pty_smoke.sh @@ -16,7 +16,7 @@ fi mkdir -p "$OUT_DIR" -cargo build --release --quiet -p perry -p perry-runtime -p perry-stdlib >"$BUILD_LOG" 2>&1 || { +cargo build --release --quiet -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static >"$BUILD_LOG" 2>&1 || { cat "$BUILD_LOG" exit 1 } From b7ef520ae9a03d82f08a281492481915ea8fac31 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 05:07:03 +0000 Subject: [PATCH 2/3] build: address CodeRabbit review on #5422 build taxonomy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feature-matrix.yml: build the full runtime/stdlib + -static wrapper set (matching node-suite-guard / simctl-tests) so feature-matrix runs have the prebuilt stdlib archives. - building.md: fix codegen build command crate name (perry-codegen, not the nonexistent perry-codegen-llvm). - cargo_timing_summary.py: wrap UNIT_DATA json.loads in try/except, raising a clear ValueError on malformed JSON instead of a bare JSONDecodeError. Note: CodeRabbit's two "cfg only gates the next item" findings on commands/mod.rs are false positives — each module that needs gating already carries its own #[cfg]; the in-between modules are intentionally always compiled (verified: default, dev-cli, and no-default-features all build). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01SQ4RdAWQhxvqyS9vWeLoTB --- .github/workflows/feature-matrix.yml | 2 +- docs/src/contributing/building.md | 2 +- scripts/cargo_timing_summary.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/feature-matrix.yml b/.github/workflows/feature-matrix.yml index b027ef16ae..51c054d59a 100644 --- a/.github/workflows/feature-matrix.yml +++ b/.github/workflows/feature-matrix.yml @@ -46,7 +46,7 @@ jobs: node-version: "26" - name: Build Perry release binary - run: cargo build --release -p perry-runtime -p perry-runtime-static -p perry + run: cargo build --release -p perry -p perry-runtime -p perry-stdlib -p perry-runtime-static -p perry-stdlib-static - name: Check feature matrix run: | diff --git a/docs/src/contributing/building.md b/docs/src/contributing/building.md index d43fad37e5..2b031a32d4 100644 --- a/docs/src/contributing/building.md +++ b/docs/src/contributing/building.md @@ -52,7 +52,7 @@ cargo build --release -p perry-runtime -p perry-stdlib cargo build --release -p perry-runtime-static -p perry-stdlib-static # Codegen only -cargo build --release -p perry-codegen-llvm +cargo build --release -p perry-codegen ``` > **Important**: When rebuilding `perry-runtime`, you must also rebuild `perry-stdlib` because `libperry_stdlib.a` embeds perry-runtime as a static dependency. diff --git a/scripts/cargo_timing_summary.py b/scripts/cargo_timing_summary.py index c6d9e7032b..b18c4f7d3f 100755 --- a/scripts/cargo_timing_summary.py +++ b/scripts/cargo_timing_summary.py @@ -44,7 +44,10 @@ def extract_units(html: str) -> list[dict]: m = re.search(r"UNIT_DATA\s*=\s*(\[.*?\]);", html, re.DOTALL) if not m: raise ValueError("could not find UNIT_DATA in the timing report") - return json.loads(m.group(1)) + try: + return json.loads(m.group(1)) + except json.JSONDecodeError as e: + raise ValueError(f"malformed UNIT_DATA in timing report: {e}") from e def main() -> int: From 31c12aea2a46d12cc6d145db59ca01f9977144fc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 05:29:50 +0000 Subject: [PATCH 3/3] style: cargo fmt compile.rs backend import ordering Reorder the feature-gated `targets` imports to satisfy `cargo fmt --all --check` (single imports precede grouped imports). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01SQ4RdAWQhxvqyS9vWeLoTB --- crates/perry/src/commands/compile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index 009793ca9b..c12ba11b19 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -109,12 +109,12 @@ use targets::{ // backend was built out. #[cfg(feature = "backend-glance")] use targets::compile_for_android_widget; -#[cfg(feature = "backend-swiftui")] -use targets::{compile_for_ios_widget, compile_for_watchos_widget}; #[cfg(feature = "backend-wasm")] use targets::compile_for_wasm; #[cfg(feature = "backend-wear-tiles")] use targets::compile_for_wearos_tile; +#[cfg(feature = "backend-swiftui")] +use targets::{compile_for_ios_widget, compile_for_watchos_widget}; use super::progress::{ProgressSnapshot, VerboseProgress};