NAME

PAX - Perl Adaptive eXecution compiler and standalone binary packager

VERSION

Current release version is kept in our $VERSION in this module and mirrored to every lib/PAX/*.pm module before release.

SYNOPSIS

perl bin/pax help
perl bin/pax build
perl bin/pax build -o ./build/my-app bin/my-app
perl bin/pax run -- version
perl bin/pax run bin/my-app -- version

DESCRIPTION

PAX turns a Perl entrypoint plus its repeatable build inputs into a standalone executable. The executable can include compiled code units, native artifacts where supported, asset payloads, dependency payloads, and a runtime launcher.

The project is deliberately neutral. Core compiler, packaging, loader, runtime, and dispatch code must not embed assumptions about one application, company, or module namespace.

INTRODUCTION

PAX exists to change the deployment shape of a Perl application.

Without PAX, a Perl application commonly depends on some mix of the original source tree, a host Perl installation, host CPAN modules, asset directories next to the app, and local bootstrap scripts or container images that carry the whole working tree.

PAX aims to turn that into one executable that can carry compiled code units, runtime payloads, embedded assets, and native artifacts where a region can be proven safe to specialize.

In bundled-runtime mode that includes the packaged helper programs and the linked shared libraries and SONAME aliases required by bundled XS modules, so helper commands and query/runtime helpers still work after the original source tree and CPAN installation are gone.

The goal is not to pretend every Perl feature can become a native binary with no trade-offs. The real goal is:

  • keep Perl correctness

  • keep fallback execution explicit

  • package applications into one binary

  • move eligible hot paths toward native speed

  • stay neutral across arbitrary Perl projects

WHAT YOU GET

  • one public command surface with pax build and pax run

  • a repeatable build contract through paxfile.yml

  • one standalone executable output

  • embedded asset packaging for web applications and static payloads

  • runtime payload packaging for source-tree-free execution

  • packaged helper commands plus linked XS shared libraries and SONAME aliases for source-tree-free execution

  • self-hosted build capability, including building bin/pax itself

  • Docker-friendly multi-stage packaging

MAIN CONCEPTS

Public Facade

PAX::CLI owns the public operator contract behind pax build and pax run.

Manifest Loading

PAX::Paxfile loads repeatable build inputs from paxfile.yml.

Standalone Image Builder

PAX::StandaloneImage collects dependencies, packages runtime payloads, embeds assets, and writes the standalone launcher.

Code Unit Compilation

PAX::CodeUnitCompiler lowers supported Perl source shapes into PAX code unit records. Unsupported regions remain on explicit fallback paths instead of being silently miscompiled.

Packaged Runtime

PAX::StandaloneRuntime provides the packaged helper runtime used after the standalone executable starts.

Native Dispatch

PAX::StandaloneDispatch and related runtime pieces execute packaged native regions and deopt fallback behavior under the standalone model.

SOW-03 PUBLIC COMMAND SURFACE

PAX exposes only two public commands through bin/pax:

  • build

    Compile and package the source tree behind an entrypoint into one standalone executable.

  • run

    Run the same build flow and then execute the resulting binary with arguments after --.

The canonical usage is:

pax build ...
pax run ...

Common CLI switches include:

  • --paxfile, --no-paxfile

  • -I, to prepend Perl library directories for inline builds/runs

  • -M, to load and import Perl modules for inline builds/runs

  • -e, to synthesize an entrypoint from inline Perl code

  • --lib, --source-root, --cpanfile, --asset, --asset-dir

  • --output / -o

  • --runtime-mode

  • --compact

Internal diagnostics and validation modules remain available as Perl APIs for the test suite and release gates. They are not public bin/pax subcommands.

PAXFILE CONTRACT

When no positional entrypoint is supplied, build and run read paxfile.yml by default. --paxfile selects a different manifest and --no-paxfile disables manifest loading. When a positional entrypoint is supplied on the CLI, PAX treats that target as an isolated build and does not silently inherit libs, source_roots, assets, asset_dirs, cpanfiles, or app metadata from the ambient paxfile.yml. An explicit --paxfile still applies its manifest defaults.

Supported manifest keys:

  • name

  • entrypoint

  • libs

  • source_roots

  • assets

  • asset_dirs

  • cpanfiles

  • output

  • runtime_mode

  • app_name, app_namespace, app_entrypoint_env, app_entrypoint_fallback, app_command

CLI flags override file values. Output path precedence is:

1. --output / -o
2. paxfile.yml output
3. .pax/standalone/<name/<name>>

MANUAL

Installation

For development from a repository checkout:

cpanm --installdeps .
perl bin/pax help

For release packaging:

cpanm Dist::Zilla

First Build

The simplest workflow is a local paxfile.yml:

name: example-app
entrypoint: bin/example-app
output: build/example-app
libs:
  - lib
cpanfiles:
  - cpanfile
runtime_mode: bundled_perl

Then build:

perl bin/pax build

Long builds print a DD-style task rundown on stderr by default. On a real terminal the board redraws live; in non-interactive runs it prints a static rundown while the machine-readable build payload stays on stdout. The build path is broken into concrete checkpoints such as code-unit compilation, application metadata inference, dependency analysis, native artifact analysis, manifest writing, and launcher compilation. The code-unit phase is further split into source discovery, entrypoint compilation, application unit compilation, and dependency unit compilation so long builds keep moving visibly. Application unit compilation includes the current file name, so a slow module no longer looks like a frozen counter. Set PAX_PROGRESS=0 to suppress the rundown.

And run the result directly:

./build/example-app

Build Without paxfile.yml

When the CLI provides the required shape, paxfile.yml is optional:

perl bin/pax build -o ./build/example-app bin/example-app

That keeps one-off builds and self-hosting neutral even inside repositories that ship their own paxfile.yml. Extra roots, assets, and CPAN policy files must be declared explicitly on the CLI in that mode.

Inline entrypoints use the same public surface. -I adds Perl library roots, -M loads/imports modules before execution, and -e supplies the entrypoint code directly:

perl bin/pax build \
  -I lib \
  -MDateTime \
  -e 'print DateTime->now'

pax run accepts the same switches:

perl bin/pax run \
  -I lib \
  -MDateTime \
  -e 'print DateTime->now'

Self Compile

PAX can build itself:

perl bin/pax build -o /tmp/pax bin/pax
/tmp/pax help

That self-built binary can then build another standalone application from its own paxfile.yml. A self-built standalone pax binary can also rebuild from another standalone pax binary input after the original source tree has been removed, because the rebuild path carries an embedded source snapshot for the application units it needs to rebuild.

ARCHITECTURE

Entrypoint and Build Configuration

PAX::CLI is a small public facade. It resolves build and run inputs from CLI arguments plus PAX::Paxfile, then delegates to the standalone image builder.

Compilation and Code Units

PAX::CodeUnitCompiler compiles supported Perl source shapes into PCU records. Unsupported or partially supported module shapes use hybrid or fallback payloads so correctness is preserved while reusable compiler support expands.

Dependency Discovery

PAX::StandaloneImage follows entrypoints, library directories, source roots, and cpanfile inputs to collect application modules and dependency payloads. The mechanism is structural and path/module based, not tied to a project name.

Asset Embedding

Individual assets and asset directories are embedded into the executable payload. The generated runtime extracts them into a private runtime directory and exposes that location to the packaged program.

Native and Fallback Dispatch

PAX can package native artifacts for supported hot regions. Runtime dispatch uses native execution when assumptions hold and falls back to bundled Perl payloads when they do not.

Standalone Launcher

The final output is an executable launcher containing package metadata, code units, dependency payloads, optional native artifacts, assets, and runtime helper code.

EXAMPLES

Build from paxfile.yml:

perl bin/pax build

Build a specific entrypoint:

perl bin/pax build -o ./build/example bin/example

Run after building:

perl bin/pax run -- status

Embed application assets:

perl bin/pax build \
  --name webapp \
  --lib lib \
  --source-root lib \
  --asset-dir share \
  --cpanfile cpanfile \
  --runtime-mode bundled_perl \
  --output ./build/webapp \
  bin/webapp

Build PAX itself:

perl bin/pax build -o /tmp/pax bin/pax
/tmp/pax help

Web Applications

PAX supports the single-binary packaging shape for framework applications that combine Perl modules, PSGI or web framework code, templates, CSS, JavaScript, and other static assets.

The validated SOW-03 proof includes a Dancer2 + Plack/Starman + Template Toolkit web application packaged as one executable.

DOCKER DEPLOYMENT MODEL

PAX supports a minimal multi-stage image pattern:

FROM perl:5.42 AS builder
WORKDIR /workspace
COPY . /workspace
RUN cpanm --installdeps .
RUN perl bin/pax build --output /out/app

FROM debian:bookworm-slim
COPY --from=builder /out/app /usr/local/bin/app
CMD ["/usr/local/bin/app"]

The final image contains only the executable. The source tree, assets, cpanfile, and framework installation are builder-stage inputs.

For an external application, the validated packaging pattern is:

  1. build a standalone pax binary

  2. copy that pax binary into the application build stage

  3. compile the application into its own standalone binary

  4. copy only that final binary into the runtime stage

ADAPTIVE COMPILATION RULE

When a module or framework fails under PAX, fixes should improve a reusable compiler, packaging, loader, or runtime path for arbitrary modules of the same class. A project-specific branch is not complete when a neutral generalized implementation is locally actionable.

RELEASE GATES

Release readiness requires:

  • Changes, README.md, cpanfile, dist.ini, and lib/PAX.pm.

  • canonical version synchronization across all PAX modules.

  • POD and README parity for public behavior.

  • make test.

  • make release-gate.

  • a deliberate version-bump step such as make cpan-bump-version VERSION=<next-version> followed by a meaningful top entry in Changes.

  • make cpan-build and make cpan-gate.

cpan-gate also verifies that release tarballs and the git index exclude temporary probes, generated workspaces, planning artifacts, and other non-release paths.

make cpan-release follows the DD-style PAUSE flow: run the repo gates, locate the built tarball in the repository root, and upload it with cpan-upload using the local uploader configuration.

The version bump happens before dzil build, for example with make cpan-bump-version VERSION=<next-version> or make cpan-auto-bump. After the bump, the operator must write a meaningful top Changes entry for that version and commit the release-preparation changes. make cpan-dist and make cpan-build then enforce the version gate, the Changes gate, and the documentation gate for README.md plus this module POD without mutating tracked source files during the packaging step.

TESTING AND COVERAGE

Primary validation from a repository checkout is:

make tdd-gate
make bdd-gate
make atdd-gate
make qa-gate
make test
make release-gate
make cpan-build
make cpan-gate

Completion requires the full chain, not a partial subset. release-gate, cpan-gate, or git-gate alone are not sufficient. In project rules, "all gates" means the full closure sequence from TDD through git gate. make all-gates is only the convenience target for replaying the final committed-tree verification set. Treat the change set as complete only when the full gate chain has closed and the committed tree passes git gate.

KNOWN LIMITATIONS

  • Dynamic loading and runtime mutation can require fallback paths.

  • Native speed depends on region selection and guard validity.

  • Bundled runtime executables are larger than wrappers because they carry runtime payloads needed to run without the source tree.

  • Bundled-perl binaries are validated for builder and runtime environments from the same libc family; arbitrary host-built cross-distro portability is not a release guarantee, so multi-stage Docker deployment should build inside the target container family.

  • Docker validation requires local Docker access.

FAQ

Is PAX tied to one specific project?

No. Example applications are validation corpora. Core compiler and runtime logic are expected to stay neutral and reusable.

Does PAX guarantee Rust-like speed for all Perl code?

No. PAX packages the whole application correctly and accelerates hot paths that it can safely specialize. Dynamic regions continue to use fallback execution.

Does pax run require a separate app server?

No. Under SOW-03, pax run builds the standalone executable and then executes that binary directly.

Can PAX package web applications with embedded static assets?

Yes. The validated packaging path includes templates, CSS, JavaScript, and framework code embedded into one standalone executable.

FILES

  • bin/pax - public command entrypoint.

  • lib/PAX/ - compiler, packaging, runtime, and validation modules.

  • paxfile.yml - neutral build manifest.

  • README.md - operator documentation.

  • Changes, cpanfile, dist.ini - release metadata.

SEE ALSO

The repository README.md mirrors the public command contract and operator workflow documented here.

The internal documentation rule for DD-style parity is recorded in docs/pax-doc-parity.md.