Skip to content

build: ship the CLI as a standalone native binary#2

Merged
rmcdaniel merged 17 commits intomainfrom
standalone-binary-build
Apr 15, 2026
Merged

build: ship the CLI as a standalone native binary#2
rmcdaniel merged 17 commits intomainfrom
standalone-binary-build

Conversation

@rmcdaniel
Copy link
Copy Markdown
Member

Summary

Adds a build pipeline that turns this CLI into a standalone native executable (no system PHP required on the target machine) in addition to the existing Composer install path.

  • PHAR via Box — one durable-workflow.phar with src/ + vendor/, PHP/JSON compactors, gzip.
  • Native binary via static-php-cli (spc) — the PHAR is embedded into the phpmicro SAPI, producing a single statically-linked executable per OS/arch.
  • Local: make phar or make binary (downloads Box and spc on demand into build/.tools/).
  • CI:
    • .github/workflows/build.yml — PRs run PHPUnit on PHP 8.2/8.3/8.4 and smoke-build the PHAR.
    • .github/workflows/release.yml — on v* tags, builds the PHAR once then fans out a matrix for linux x86_64/aarch64 and macOS x86_64/aarch64, then publishes a GitHub Release with all assets + SHA256SUMS.

Extensions baked into the phpmicro build match what the CLI actually uses today: curl, mbstring, openssl, phar, tokenizer, ctype, filter, fileinfo, iconv, sockets, pcntl, posix. The list is defined once in scripts/build.sh and referenced from the release workflow via SPC_EXTENSIONS.

Test plan

  • make phar locally produces a working build/durable-workflow.phar (requires PHP >= 8.2).
  • make binary locally produces build/durable-workflow-<platform> that runs on a machine without PHP.
  • Push a throwaway v0.0.0-test tag (or use the workflow_dispatch input) and confirm the release workflow produces all four native binaries + the PHAR + SHA256SUMS.
  • Confirm PR CI (build.yml) still runs the full PHPUnit matrix and Box smoke build.

🤖 Generated with Claude Code

durable-workflow-ops and others added 17 commits April 14, 2026 22:29
Add a build pipeline that turns the CLI into (a) a self-contained PHAR
and (b) a static native executable that runs without a system PHP.

- box.json.dist: Box 4 config that compiles src/ + vendor/ into
  build/durable-workflow.phar with PHP + JSON compactors and gzip
  compression.
- scripts/build.sh + Makefile: local "make phar" and "make binary"
  targets. The binary target downloads static-php-cli (spc) on demand,
  compiles phpmicro with the extensions the CLI actually uses
  (curl, mbstring, openssl, phar, tokenizer, ctype, filter, fileinfo,
  iconv, sockets, pcntl, posix), and splices the PHAR onto the micro
  SAPI via "spc micro:combine".
- .github/workflows/build.yml: PR/push CI. Runs PHPUnit across
  PHP 8.2/8.3/8.4 and smoke-builds the PHAR.
- .github/workflows/release.yml: on "v*" tags, builds the PHAR once
  and then a native binary per platform (linux x86_64/aarch64,
  macos x86_64/aarch64) using spc, then publishes a GitHub release
  with SHA256SUMS.
- README: document the three install paths (standalone binary, PHAR,
  Composer) and the "make" targets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a windows-latest job that builds phpmicro + spc on MSVC and
splices the PHAR into a durable-workflow-windows-x86_64.exe. The
Windows extension list drops pcntl/posix (Unix-only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The committed composer.lock pins symfony/string v8.0.8, which requires
PHP >= 8.4. Updates both CI workflows and scripts/build.sh to build
against 8.4 (the lowest resolvable version for the locked deps).

Also drops pcntl and posix from the extension list. They are only used
by symfony/console for graceful signal handling and TTY detection, both
of which degrade cleanly when missing, and removing them gives Unix and
Windows builds the same extension set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Building phpmicro from source per platform takes ~15-30 minutes
(especially on Windows). Running it on every feature-branch commit
is too expensive. Drop the feature-branch trigger so the workflow
only runs on 'v*' tag pushes or explicit workflow_dispatch.

Note: the in-flight branch build will still complete. This change
just prevents future commits from kicking off a new one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --debug to the Windows phpmicro build and always upload the
  build/.tools/log/ directory so MSVC errors are recoverable.
- Retry the spc binary download up to 5 times on Linux/macOS/Windows.
  The bare curl call was hitting transient CDN failures on
  dl.static-php.dev (observed HTTP errors on linux-x86_64 and
  macos-aarch64 in the previous run).
- Drop the macos-x86_64 matrix entry. The macos-13 runner label is
  not available to this org ("macos-13-us-default is not supported"),
  and Apple Silicon is the practical target anyway.
- Temporarily re-enable the branch push trigger while we iterate on
  the Windows fix. Will be removed before merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop ext-openssl from the Windows phpmicro build. PHP 8.4 on Windows
  fails to compile its openssl extension against OpenSSL 3.x because
  php_openssl.h still references the removed ERR_NUM_ERRORS constant
  (upstream php-src issue). The CLI does not need ext-openssl on
  Windows: curl is built with -DCURL_USE_SCHANNEL=ON, so HTTPS via
  Symfony HttpClient -> ext-curl -> Schannel continues to work.

- Export GITHUB_TOKEN as a workflow-level env so every spc step can
  authenticate against the GitHub API when resolving source tarballs
  and pre-built static-php-cli-hosted libs. The unauthenticated calls
  were hitting HTTP 403 and failing the macos-aarch64 download step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Windows phpmicro build is blocked on an upstream bug: PHP 8.4's
Windows ext-openssl references ERR_NUM_ERRORS (removed in OpenSSL 3),
and spc pulls ext-openssl in as a transitive dependency of ext-curl
even though curl on Windows uses Schannel. Fixing this requires an
spc dep-map change or a php-src patch.

Until that is resolved upstream:
  - The Windows job keeps running (so regressions vs. a future fix
    are visible) but uses continue-on-error so it does not block the
    Linux/macOS binary release.
  - The publish-release job only hard-depends on build-phar and
    build-binary (Linux + macOS). If Windows succeeds, its artifact
    is still picked up.
  - README lists only the platforms we actually ship.

Also drops the temporary standalone-binary-build push trigger; the
release workflow is back to tag/dispatch-only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PHP 8.4's Windows ext-openssl fails to compile against OpenSSL 3.x
because php_openssl.h still references the removed ERR_NUM_ERRORS
constant. PHP 8.3's openssl extension doesn't have this reference
and compiles cleanly on spc's Windows pipeline.

Using 8.3 phpmicro to run the PHAR that was built with 8.4 Composer
resolution is acceptable here: the locked runtime code is expected
to be ABI-compatible with 8.3. If any locked dep actually uses
8.4-only syntax, the smoke test at the end of the job will catch it
before the artifact is uploaded.

Also re-adds the temporary feature-branch push trigger and drops
continue-on-error on the Windows job so this iteration reports a
real result.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both PHP 8.3 and 8.4 fail to compile ext-openssl on Windows against
OpenSSL 3.x — php_openssl.h references the removed ERR_NUM_ERRORS
constant. spc's dependency map (config/ext.json) hard-codes openssl
as a Windows-only transitive dep of ext-curl, so even removing
openssl from --for-extensions does not help: spc still pulls it in.

The prebuilt spc binary embeds ext.json, so overriding it externally
is impossible. Switch the Windows job to run spc from a source clone:

  - shivammathur/setup-php installs PHP 8.3 on the runner
  - git clone static-php-cli, then a small Python patch strips
    "openssl" from curl.ext-depends-windows in config/ext.json
  - composer install inside the spc clone
  - php bin\spc download/build/micro:combine

libcurl itself still links libopenssl at the transport layer for TLS,
so HTTPS via Symfony HttpClient -> ext-curl continues to work without
the PHP-side ext-openssl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rmcdaniel rmcdaniel merged commit 28f27b2 into main Apr 15, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants