diff --git a/homebrew/kandelo-homebrew/Formula/perl.rb b/homebrew/kandelo-homebrew/Formula/perl.rb new file mode 100644 index 000000000..d6f1ca037 --- /dev/null +++ b/homebrew/kandelo-homebrew/Formula/perl.rb @@ -0,0 +1,60 @@ +require_relative "../Kandelo/formula_support/kandelo_package" + +class Perl < Formula + include KandeloPackageFormula + SOURCE_URL = "https://www.cpan.org/src/5.0/perl-5.40.3.tar.gz" + SOURCE_SHA256 = "4c155b4e6160682b38919b55ac319081b898db11857cf18a7d9ffed2648ccaff" + PERL_PRIVLIB = "5.40.3" + + desc "Perl interpreter for Kandelo (with generated core-module runtime)" + homepage "https://www.perl.org/" + url SOURCE_URL + sha256 SOURCE_SHA256 + license any_of: ["Artistic-1.0-Perl", "GPL-1.0-or-later"] + + skip_clean "bin" + skip_clean "lib/perl5" + + def install + out_dir = kandelo_build_package("perl", "build-perl.sh", SOURCE_URL, SOURCE_SHA256, + script_env: { "PERL_VERSION" => version.to_s }) + kandelo_install_bin(out_dir, "perl.wasm", "perl") + + # Ship the generated core-module runtime library (XSLoader.pm, Config*.pm, + # File::Spec and the rest of the pure-perl core tree) so the runtime can + # load File::Spec (-> Cwd -> XSLoader); the bare perl.wasm carries none. + # build-perl.sh stages perl-src/lib via `make all` and zips it as + # perl-runtime.zip. The test's PERL5LIB (below) points at the installed + # lib/perl5/#{PERL_PRIVLIB}. + runtime_stage = buildpath/"perl-runtime-stage" + system "unzip", "-q", out_dir/"perl-runtime.zip", "-d", runtime_stage + (prefix/"lib").install Dir["#{runtime_stage}/lib/*"] + end + + test do + # LC_ALL=C: perl 5.40 panics at startup parsing the composite default + # locale Kandelo's musl setlocale returns ('C.UTF-8;C;C;C;C;C') -- a + # separate platform boundary (kd-dvph), not this package's gap. + env = { "PERL5LIB" => (lib/"perl5/#{PERL_PRIVLIB}").to_s, "LC_ALL" => "C" } + + # Interpreter smoke (the minimal strict/warnings arithmetic check). + assert_match "5", kandelo_run_wasm(bin/"perl", + ["-e", "use strict; use warnings; print 2 + 3"], env: env) + + # Regression guard for the reported gap (kd-k7zy): File::Spec must load and + # build the expected path, XSLoader.pm (the missing generated file) must + # load, and an XS core module must bootstrap through XSLoader::load. + prog = <<~PERL + use strict; use warnings; + use File::Spec; + use XSLoader; + use POSIX (); + my $p = File::Spec->catfile("a", "b", "c.txt"); + die "File::Spec catfile: $p" unless $p eq "a/b/c.txt"; + die "POSIX floor" unless POSIX::floor(3.7) == 3; + print "perl-runtime-ok xsloader=$XSLoader::VERSION"; + PERL + assert_match "perl-runtime-ok xsloader=", + kandelo_run_wasm(bin/"perl", ["-e", prog], env: env) + end +end diff --git a/packages/registry/perl/build-perl.sh b/packages/registry/perl/build-perl.sh index f42f31588..21ba8c91d 100755 --- a/packages/registry/perl/build-perl.sh +++ b/packages/registry/perl/build-perl.sh @@ -13,17 +13,24 @@ set -euo pipefail # # Output: packages/registry/perl/bin/perl.wasm -PERL_VERSION="${PERL_VERSION:-5.40.3}" +PERL_VERSION="${WASM_POSIX_DEP_VERSION:-${PERL_VERSION:-5.40.3}}" PERL_CROSS_VERSION="${PERL_CROSS_VERSION:-1.6.4}" +SOURCE_URL="${WASM_POSIX_DEP_SOURCE_URL:-https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz}" +SOURCE_SHA256="${WASM_POSIX_DEP_SOURCE_SHA256:-}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" -SRC_DIR="$SCRIPT_DIR/perl-src" -BIN_DIR="$SCRIPT_DIR/bin" -SYSROOT="$REPO_ROOT/sysroot" +WORK_DIR="${WASM_POSIX_DEP_WORK_DIR:-$SCRIPT_DIR}" +SRC_DIR="$WORK_DIR/perl-src" +BIN_DIR="${WASM_POSIX_DEP_OUT_DIR:-$SCRIPT_DIR/bin}" +# Worktree-local SDK on PATH (no global npm link required). +# shellcheck source=/dev/null +source "$REPO_ROOT/sdk/activate.sh" +SYSROOT="${WASM_POSIX_SYSROOT:-$REPO_ROOT/sysroot}" +export WASM_POSIX_SYSROOT="$SYSROOT" # --- Prerequisites --- if ! command -v wasm32posix-cc &>/dev/null; then - echo "ERROR: wasm32posix-cc not found. Run 'npm link' in sdk/ first." >&2 + echo "ERROR: wasm32posix-cc not found after sourcing sdk/activate.sh." >&2 exit 1 fi @@ -32,8 +39,6 @@ if [ ! -f "$SYSROOT/lib/libc.a" ]; then exit 1 fi -export WASM_POSIX_SYSROOT="$SYSROOT" - # perl-cross's configure scripts require GNU tools (sed -r, readelf, objdump). # scripts/dev-shell.sh provides those tools in the pure build environment. # LLVM provides readelf and objdump that perl-cross needs @@ -47,7 +52,7 @@ if [ -z "${LLVM_BIN:-}" ]; then fi if [ -d "$LLVM_BIN" ]; then # Create temp dir with readelf/objdump symlinks for perl-cross - TOOL_DIR="$SCRIPT_DIR/.host-tools" + TOOL_DIR="$WORK_DIR/.host-tools" mkdir -p "$TOOL_DIR" ln -sf "$LLVM_BIN/llvm-readelf" "$TOOL_DIR/readelf" ln -sf "$LLVM_BIN/llvm-objdump" "$TOOL_DIR/objdump" @@ -57,11 +62,14 @@ fi # --- Download Perl source + perl-cross overlay --- if [ ! -d "$SRC_DIR" ]; then echo "==> Downloading Perl $PERL_VERSION..." - TARBALL="perl-${PERL_VERSION}.tar.gz" - URL="https://www.cpan.org/src/5.0/${TARBALL}" - curl --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors -fsSL "$URL" -o "/tmp/$TARBALL" + TARBALL="$(basename "$SOURCE_URL")" + curl --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors -fsSL "$SOURCE_URL" -o "/tmp/$TARBALL" + if [ -n "$SOURCE_SHA256" ]; then + echo "==> Verifying source sha256..." + echo "$SOURCE_SHA256 /tmp/$TARBALL" | shasum -a 256 -c - + fi mkdir -p "$SRC_DIR" - tar xzf "/tmp/$TARBALL" -C "$SRC_DIR" --strip-components=1 + tar xf "/tmp/$TARBALL" -C "$SRC_DIR" --strip-components=1 rm "/tmp/$TARBALL" echo "==> Downloading perl-cross $PERL_CROSS_VERSION..." @@ -242,6 +250,32 @@ if [ ! -f config.sh ]; then # platform that builds perl with clang. (Adding it to HOSTCFLAGS # ensures the buildmini sub-configure inherits it — perl-cross # propagates HOSTCFLAGS into the host CC invocation.) + # + # The SAME UB miscompile hits the TARGET perl.wasm, not just the host + # miniperl: with `-Doptimize=-O2` and no `-fno-strict-aliasing` in the + # target `-Dccflags`, perl.wasm panics `magic_killbackrefs` / + # `del_backref` the first time a loaded module traverses weak refs (e.g. + # `use File::Spec`, `use Config`, `use Data::Dumper`). That is why the + # earlier port only passed a trivial arithmetic smoke. `-Dccflags` below + # carries `-fno-strict-aliasing` so the shipped interpreter is correct. + # + # `-Dosname=linux` (below): the wasm32-unknown-none target left osname + # empty, so ExtUtils::MakeMaker's `$Config{osname} eq ...` probes hit + # undef and every core module's Makefile.PL/pm_to_blib staging failed + # (no generated runtime files). Kandelo presents a POSIX/linux-like + # syscall surface, so a linux osname routes MakeMaker through MM_Unix + # correctly and lets the core-module runtime tree generate + stage. + # + # `-Uusedl` (static extensions): Kandelo wasm has no working dlopen + # (dlerror() is a stub -> "Can't load Cwd.so ... dlerror() not + # implemented"). The default usedl=define builds each XS core module as a + # .so that the runtime can never load, so File::Spec (-> File::Spec::Unix + # -> Cwd, an XS module), POSIX, Fcntl, List::Util, etc. all fail. Building + # extensions statically links their XS into perl.wasm with a boot table so + # XSLoader::load resolves them without dlopen. The set of statically-linked + # extensions is curated after configure by editing Makefile.config's + # fullpath_static_ext (perl-cross has no -Dnoextensions handler); see that + # patch below for which extensions are dropped and why. export HOSTCFLAGS="-Wno-format -fno-strict-aliasing" # perl-cross's `--mode=cross` spawns two sub-configures: one for @@ -266,7 +300,8 @@ if [ ! -f config.sh ]; then -Dranlib=wasm32posix-ranlib \ -Dnm=wasm32posix-nm \ -Doptimize="-O2" \ - -Dccflags="-D_GNU_SOURCE -DNO_ENV_ARRAY_IN_MAIN -fvisibility=default" \ + -Dosname=linux \ + -Dccflags="-D_GNU_SOURCE -DNO_ENV_ARRAY_IN_MAIN -fvisibility=default -fno-strict-aliasing" \ -Dldflags="" \ -Dlddlflags="" \ -Dccdlflags="" \ @@ -279,6 +314,7 @@ if [ ! -f config.sh ]; then -Uuselargefiles \ -Duse64bitint \ -Duseperlio \ + -Uusedl \ \ -Dcharsize=1 \ -Dshortsize=2 \ @@ -463,7 +499,7 @@ if [ ! -f config.sh ]; then -Dd_crypt=undef \ -Dd_times=undef \ -Dd_system=undef \ - 2>&1 | tee "$SCRIPT_DIR/configure.log" | tail -50 + 2>&1 | tee "$WORK_DIR/configure.log" | tail -50 echo "==> Configure complete." @@ -489,6 +525,21 @@ if [ ! -f config.sh ]; then -e "/^CFLAGS = /s/$/ -fvisibility=default -DNO_ENV_ARRAY_IN_MAIN/" \ Makefile.config + # Curate the static extension set (perl-cross ignores -Dnoextensions, so we + # edit fullpath_static_ext directly). Two reasons: + # - ext/re recompiles regcomp.c with -DPERL_EXT_RE_BUILD, whose symbols + # (Perl_reg_add_data, ...) collide with core regcomp.o at the static + # perl link ("duplicate symbol"). re is a debug pragma; drop it. + # - drop extensions whose XS needs libraries absent from the wasm sysroot + # (Compress::Raw::Zlib/Bzip2, Encode, Sys::Syslog, I18N::Langinfo, + # NDBM_File, Unicode::Collate/Normalize, PerlIO::encoding) or threads + # (we build -Uusethreads); with --allow-undefined those would link but + # add wasm imports the host cannot satisfy. Kept set uses standard + # libc/syscalls the kernel provides. Excluded modules -> follow-up. + KEEP_STATIC_EXT="ext/B ext/Devel-Peek ext/Fcntl ext/File-DosGlob ext/File-Glob ext/Hash-Util ext/Hash-Util-FieldHash ext/Opcode ext/POSIX ext/PerlIO-mmap ext/PerlIO-via ext/SDBM_File ext/Sys-Hostname ext/attributes ext/mro cpan/Digest-MD5 cpan/Digest-SHA cpan/Filter-Util-Call cpan/MIME-Base64 cpan/Math-BigInt-FastCalc cpan/Scalar-List-Utils cpan/Socket cpan/Time-Piece dist/Data-Dumper dist/Devel-PPPort dist/IO dist/PathTools dist/Storable dist/Time-HiRes" + sed -i.bak3 "s|^fullpath_static_ext = .*|fullpath_static_ext = ${KEEP_STATIC_EXT}|" Makefile.config + echo "==> Curated fullpath_static_ext to $(echo "$KEEP_STATIC_EXT" | wc -w | tr -d ' ') extensions (dropped re + external-lib/threads)." + # Patch Makefile for wasm32 linking: # Our toolchain uses --allow-undefined which creates env.* imports for unresolved # symbols instead of linking to definitions from other .o files. Combined with @@ -505,7 +556,7 @@ fi # --- Build --- echo "==> Building Perl (this takes a while)..." -make -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" perl 2>&1 | tee "$SCRIPT_DIR/build.log" | tail -80 +make -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" perl 2>&1 | tee "$WORK_DIR/build.log" | tail -80 echo "==> Collecting binary..." mkdir -p "$BIN_DIR" @@ -517,7 +568,7 @@ if [ -f "$SRC_DIR/perl" ]; then else echo "ERROR: perl binary not found after build" >&2 echo "==> Last 100 lines of build.log:" - tail -100 "$SCRIPT_DIR/build.log" + tail -100 "$WORK_DIR/build.log" exit 1 fi @@ -525,7 +576,97 @@ echo "" echo "==> Perl $PERL_VERSION built successfully!" echo "Binary: $BIN_DIR/perl.wasm" +# --- Build + package the generated core-module runtime library --- +# `make perl` links the interpreter but STOPS before perl-cross's +# `nonxs_ext extensions pods` sub-targets. Those sub-targets are what +# GENERATE core runtime files (e.g. lib/XSLoader.pm from +# dist/XSLoader/XSLoader_pm.PL) and stage the pure-perl core-module tree +# into perl-src/lib/ via pm_to_blib. Without them the shipped bottle has no +# XSLoader.pm, so loading File::Spec (-> Cwd -> XSLoader) fails at runtime -- +# the reported gap. These steps run under miniperl (the host build perl), so +# they need no wasm-target execution. +# +# We run `make -k` (keep-going) rather than a plain `make` because two known +# wasm boundaries make the FULL build return non-zero, and neither blocks the +# runtime this package ships (the curated static extensions above still build +# and their .pm still stage): +# 1. ext/Errno: Errno_pm.PL's errno-constant extraction finds none under the +# wasm sysroot ("No error definitions found") -> follow-up kd-gtxa. +# 2. The extensions dropped from fullpath_static_ext (external-lib/threads/re) +# have their Makefile.PL/pm_to_blib skipped -> follow-up kd-14n8. The +# curated static set (POSIX/Fcntl/Cwd/List::Util/...) is linked into +# perl.wasm and their pure-perl .pm stage via pm_to_blib, so File::Spec +# (-> Cwd XS), POSIX, etc. load at runtime without dlopen. +# We then verify the required generated files exist and package perl-src/lib/ +# (the staged privlib) as the perl-runtime.zip output that the Homebrew +# formula installs + points PERL5LIB at, and that the resolver ships alongside +# perl.wasm. +echo "==> Building + staging Perl runtime modules (make -k)..." +set +e +make -k -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" 2>&1 | tee "$WORK_DIR/build-all.log" | tail -40 +ALL_RC=${PIPESTATUS[0]} +set -e +if [ "$ALL_RC" -ne 0 ]; then + echo "==> 'make -k' exited $ALL_RC (expected: Errno + dropped external-lib exts);" >&2 + echo " verifying the required generated runtime files were still staged below." >&2 +fi + +# Stage the statically-linked extensions' .pm into lib/. perl-cross's static +# recipe runs `make -C ... static` (builds the .a) but never runs the +# module's pm_to_blib, and it touches a `/pm_to_blib` stamp so a later +# `make pm_to_blib` no-ops -- so File::Spec/POSIX/Cwd .pm never reach lib/ even +# though their XS is linked into perl.wasm. Remove the stamp and run each +# curated static ext's pm_to_blib (uses miniperl, no wasm-target execution). +echo "==> Staging static-extension .pm (pm_to_blib)..." +STATIC_EXT_DIRS="$(sed -n 's/^fullpath_static_ext = //p' Makefile.config)" +for d in $STATIC_EXT_DIRS; do + [ -d "$d" ] || continue + rm -f "$d/pm_to_blib" + make -C "$d" PERL_CORE=1 LIBPERL=libperl.a pm_to_blib >/dev/null 2>&1 || \ + echo " WARN: pm_to_blib failed for $d (checked by the post-check below)" >&2 +done + +PRIVLIB_SRC="$SRC_DIR/lib" +# Fail loudly if the generated core runtime files this package exists to ship +# are still absent -- do not publish a silently-incomplete runtime. Cwd.pm is +# the file whose absence made File::Spec fail in the original report. +missing="" +for f in XSLoader.pm Config.pm File/Spec.pm File/Spec/Unix.pm Cwd.pm; do + [ -f "$PRIVLIB_SRC/$f" ] || missing="$missing $f" +done +if [ -n "$missing" ]; then + echo "ERROR: required generated runtime files missing from $PRIVLIB_SRC:$missing" >&2 + echo " (make -k rc=$ALL_RC) -- see $WORK_DIR/build-all.log" >&2 + exit 1 +fi +echo "==> Generated runtime files present (XSLoader.pm, Config.pm, File::Spec, Cwd.pm)." + +# `make perl` above linked perl.wasm before any extension was built; with +# `-Uusedl` the `make -k` pass compiles the core XS extensions and relinks +# perl.wasm to statically embed them (with the XSLoader boot table). Re-collect +# that interpreter -- it is the one that can load Cwd/POSIX/Fcntl without +# dlopen. (No-op for a usedl=define build where make -k does not relink perl.) +if [ -f "$SRC_DIR/perl" ]; then + cp "$SRC_DIR/perl" "$BIN_DIR/perl.wasm" + echo "==> Re-collected perl.wasm after make -k ($(wc -c < "$BIN_DIR/perl.wasm" | tr -d ' ') bytes)." +fi + +echo "==> Packaging Perl runtime library (lib/perl5/$PERL_VERSION)..." +RUNTIME_STAGE="$WORK_DIR/perl-runtime-stage" +rm -rf "$RUNTIME_STAGE" +mkdir -p "$RUNTIME_STAGE/lib/perl5/$PERL_VERSION" +# Ship the staged privlib. Keep unicore/ (utf8 + regex need it) and the +# generated *.pm/*.pl; drop *.bak left by the configure sed patches and the +# *.orig backups so the runtime zip carries only real library files. +cp -R "$PRIVLIB_SRC/." "$RUNTIME_STAGE/lib/perl5/$PERL_VERSION/" +find "$RUNTIME_STAGE" -type f \( -name '*.bak' -o -name '*.orig' \) -delete 2>/dev/null || true +RUNTIME_ZIP="$BIN_DIR/perl-runtime.zip" +rm -f "$RUNTIME_ZIP" +( cd "$RUNTIME_STAGE" && zip -q -r -X "$RUNTIME_ZIP" lib ) +echo "==> Packaged runtime: $RUNTIME_ZIP ($(du -h "$RUNTIME_ZIP" | cut -f1))" + # Install into local-binaries/ so the resolver picks the freshly-built -# binary over the fetched release. +# binary + runtime over the fetched release. source "$REPO_ROOT/scripts/install-local-binary.sh" -install_local_binary perl "$SCRIPT_DIR/bin/perl.wasm" +install_local_binary perl "$BIN_DIR/perl.wasm" +install_local_binary perl "$RUNTIME_ZIP" diff --git a/packages/registry/perl/build.toml b/packages/registry/perl/build.toml index 5d344dba6..f0f6f36f8 100644 --- a/packages/registry/perl/build.toml +++ b/packages/registry/perl/build.toml @@ -1,7 +1,7 @@ script_path = "packages/registry/perl/build-perl.sh" repo_url = "https://github.com/brandonpayton/kandelo.git" commit = "8c53383229fab78f97b098c3207a655159c03041" -revision = 1 +revision = 2 [binary] index_url = "https://github.com/Automattic/kandelo/releases/download/binaries-abi-v{abi}/index.toml" diff --git a/packages/registry/perl/demo/runtime-smoke.ts b/packages/registry/perl/demo/runtime-smoke.ts new file mode 100644 index 000000000..9a151302d --- /dev/null +++ b/packages/registry/perl/demo/runtime-smoke.ts @@ -0,0 +1,84 @@ +/** + * runtime-smoke.ts — kd-k7zy Perl core-module runtime smoke on Kandelo. + * + * Runs the built perl.wasm under the Node kernel host (host-fs passthrough so + * PERL5LIB resolves) and checks that the generated core-module runtime library + * loads and that XS core modules bootstrap through XSLoader::load. + * + * Guards the reported gap (kd-k7zy): main's build-perl.sh ran `make perl`, + * which stops before perl-cross's `nonxs_ext extensions` targets that generate + * XSLoader.pm and stage the core-module tree, so File::Spec (-> Cwd -> XSLoader) + * failed. build-perl.sh now runs `make -k` and ships that tree as + * perl-runtime.zip, and builds extensions statically (-Uusedl) so their XS + * loads without dlopen (Kandelo wasm has none). + * + * Usage: + * bash build.sh && bash packages/registry/perl/build-perl.sh + * npx tsx packages/registry/perl/demo/runtime-smoke.ts [PERL5LIB_DIR] + * + * PERL5LIB_DIR defaults to the built privlib staged by build-perl.sh; pass an + * arg (e.g. an unzipped perl-runtime.zip) to smoke the shippable layout. + */ +import { existsSync } from "fs"; +import { resolve, dirname } from "path"; +import { runCentralizedProgram } from "../../../../host/test/centralized-test-helper"; +import { NodePlatformIO } from "../../../../host/src/platform/node"; + +const scriptDir = dirname(new URL(import.meta.url).pathname); +const repoRoot = resolve(scriptDir, "../../../.."); + +// Each check prints "=ok" on success. The XS modules (POSIX/Cwd/...) each +// require XSLoader to bootstrap their statically-linked half, so their success +// also proves XSLoader::load works without dlopen. +const PROG = [ + "use strict; use warnings;", + "my @res;", + "sub ck { my ($n,$c)=@_; my $r=eval { $c->() }; push @res, $n.'='.((defined $r && !$@)?'ok':'FAIL('.(($@=~/^(.*?)(?: at |\\n)/)?$1:'err').')'); }", + // Generated files: Config.pm + XSLoader.pm (the reported missing file). + "ck('Config', sub { require Config; $Config::Config{version} eq '5.40.3' or die 'ver'; 1 });", + "ck('XSLoader', sub { require XSLoader; $XSLoader::VERSION or die; 1 });", + // File::Spec: the reported failing module. catfile is path logic; rel2abs + // pulls in Cwd (an XS module) so it also exercises the XS boot path. + "ck('FileSpec_catfile', sub { require File::Spec; File::Spec->catfile('a','b','c.txt') eq 'a/b/c.txt' or die });", + "ck('FileSpec_rel2abs', sub { File::Spec->rel2abs('x','/root') eq '/root/x' or die });", + // XS core modules that bootstrap through XSLoader::load. + "ck('Cwd_xs', sub { require Cwd; Cwd::getcwd(); 1 });", + "ck('POSIX_xs', sub { require POSIX; POSIX::floor(3.7)==3 or die });", + "ck('Fcntl_xs', sub { require Fcntl; defined Fcntl::O_RDONLY() or die });", + "ck('ListUtil_xs',sub { require List::Util; List::Util::sum(1,2,3,4)==10 or die });", + "ck('DataDumper_xs',sub { require Data::Dumper; Data::Dumper::Dumper([1])=~/1/ or die });", + "print 'PERLVER=',$],\"\\n\";", + "print 'RESULTS=',join(',',@res),\"\\n\";", + "print((grep { !/=ok$/ } @res) ? \"PERL_RUNTIME_SMOKE_FAIL\\n\" : \"PERL_RUNTIME_SMOKE_PASS\\n\");", +].join("\n"); + +async function main() { + const perlWasm = resolve(repoRoot, "packages/registry/perl/bin/perl.wasm"); + const perl5lib = process.argv[2] || + resolve(repoRoot, "packages/registry/perl/perl-src/lib"); + if (!existsSync(perlWasm)) { + console.error("perl.wasm not found. Run: bash packages/registry/perl/build-perl.sh"); + process.exit(1); + } + + const result = await runCentralizedProgram({ + programPath: perlWasm, + argv: ["perl", "-e", PROG], + // LC_ALL=C: perl 5.40 panics at startup parsing the composite default + // locale Kandelo's musl setlocale returns ('C.UTF-8;C;C;C;C;C') -- a + // separate platform boundary (kd-dvph), not this package's gap. + env: [`PERL5LIB=${perl5lib}`, `LC_ALL=C`, `HOME=/tmp`, `TMPDIR=/tmp`], + io: new NodePlatformIO(), + timeout: 300_000, + }); + + process.stdout.write(result.stdout); + if (result.stderr) process.stderr.write(result.stderr); + const ok = result.exitCode === 0 && result.stdout.includes("PERL_RUNTIME_SMOKE_PASS"); + process.exit(ok ? 0 : (result.exitCode || 1)); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/packages/registry/perl/package.toml b/packages/registry/perl/package.toml index 074c93423..00dddd399 100644 --- a/packages/registry/perl/package.toml +++ b/packages/registry/perl/package.toml @@ -18,3 +18,12 @@ script_path = "packages/registry/perl/build-perl.sh" [[outputs]] name = "perl" wasm = "perl.wasm" + +# Generated core-module runtime library (lib/perl5/5.40.3/*): XSLoader.pm, +# Config*.pm, File::Spec and the rest of the pure-perl core tree that +# perl-cross generates during `make all` (not `make perl`). The Homebrew +# formula installs this and points PERL5LIB at it; without it the bare +# perl.wasm cannot load File::Spec (-> Cwd -> XSLoader). +[[outputs]] +name = "perl-runtime" +wasm = "perl-runtime.zip"