diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml
index 6bbd28bc..10b125a3 100644
--- a/.github/workflows/build-and-push-docker-image.yml
+++ b/.github/workflows/build-and-push-docker-image.yml
@@ -2,7 +2,7 @@
# in a way to pass those permissions on, e.g.:
#
# build-and-push-docker-image:
-# needs: build-phar
+# needs: build-assets
# permissions:
# contents: read
# id-token: write
diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml
new file mode 100644
index 00000000..f0ef767a
--- /dev/null
+++ b/.github/workflows/build-assets.yml
@@ -0,0 +1,161 @@
+# Invoking this pipeline requires additional permissions, so must be invoked
+# in a way to pass those permissions on, e.g.:
+#
+# build-assets:
+# permissions:
+# contents: read
+# id-token: write
+# attestations: write
+# uses: ./.github/workflows/build-assets.yml
+
+name: "Build the PIE assets"
+
+on:
+ workflow_call:
+
+permissions:
+ contents: read
+
+jobs:
+ build-phar:
+ runs-on: ${{ matrix.operating-system }}
+ strategy:
+ matrix:
+ operating-system:
+ - ubuntu-latest
+ php-versions:
+ - '8.1'
+ permissions:
+ # id-token:write is required for build provenance attestation.
+ id-token: write
+ # attestations:write is required for build provenance attestation.
+ attestations: write
+ steps:
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ coverage: none
+ tools: composer, box
+ php-version: "${{ matrix.php-version }}"
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ # Fixes `git describe` picking the wrong tag - see https://github.com/php/pie/issues/307
+ - run: git fetch --tags --force
+ # Ensure some kind of previous tag exists, otherwise box fails
+ - run: git describe --tags HEAD || git tag 0.0.0
+ - uses: ramsey/composer-install@v3
+ - name: Build PHAR
+ run: box compile
+ - name: Check the PHAR executes
+ run: php pie.phar --version
+ - name: Generate build provenance attestation
+ # It does not make sense to do this for PR builds, nor do contributors
+ # have permission to do. We can't write attestations to `php/pie` in an
+ # unprivileged context, otherwise anyone could send a PR with malicious
+ # code, which would store attestation that `php/pie` built the PHAR, and
+ # it would look genuine. So this should NOT run for PR builds.
+ if: github.event_name != 'pull_request'
+ uses: actions/attest-build-provenance@v3
+ with:
+ subject-path: '${{ github.workspace }}/pie.phar'
+ - uses: actions/upload-artifact@v5
+ with:
+ name: pie-${{ github.sha }}.phar
+ path: pie.phar
+
+ build-executable:
+ needs:
+ - build-phar
+ runs-on: ${{ matrix.operating-system }}
+ strategy:
+ fail-fast: false
+ matrix:
+ operating-system:
+ - ubuntu-24.04
+ - ubuntu-24.04-arm
+ - macos-15-intel
+ - macos-26
+ - windows-2025
+ permissions:
+ # id-token:write is required for build provenance attestation.
+ id-token: write
+ # attestations:write is required for build provenance attestation.
+ attestations: write
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Download SPC (non-Windows)
+ if: runner.os != 'Windows'
+ run: |
+ # @todo find a better way to do this :/
+ # Source URL: https://static-php.dev/en/guide/manual-build.html#build-locally-using-spc-binary-recommended
+ case "${{ matrix.operating-system }}" in
+ ubuntu-24.04)
+ curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+ ;;
+
+ ubuntu-24.04-arm)
+ curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+ ;;
+
+ macos-15-intel)
+ curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+ ;;
+
+ macos-26)
+ curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+ ;;
+
+ *)
+ echo "unsupported operating system: ${{ matrix.operating-system }}"
+ exit 1
+ ;;
+ esac
+ chmod +x spc
+ echo "SPC_BINARY=./spc" >> $GITHUB_ENV
+ echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}" >> $GITHUB_ENV
+ - name: Download SPC (Windows)
+ if: runner.os == 'Windows'
+ run: |
+ curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+ chmod +x spc.exe
+ echo "SPC_BINARY=.\spc.exe" >> $env:GITHUB_ENV
+ echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}.exe" >> $env:GITHUB_ENV
+
+ - name: Grab the pie.phar from artifacts
+ uses: actions/download-artifact@v5
+ with:
+ name: pie-${{ github.sha }}.phar
+
+ - name: Build for ${{ runner.os }} ${{ runner.arch }} on ${{ matrix.operating-system }}
+ run: ${{ env.SPC_BINARY }} craft resources/spc/craft.yml
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Bundle pie.phar into executable PIE binary
+ run: ${{ env.SPC_BINARY }} micro:combine pie.phar --output=${{ env.PIE_BINARY_OUTPUT }}
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ coverage: none
+ tools: composer
+ php-version: "7.4"
+ - name: Quick validation that the binary runs
+ run: ./${{ env.PIE_BINARY_OUTPUT }} show --all
+
+ - name: Generate build provenance attestation
+ # It does not make sense to do this for PR builds, nor do contributors
+ # have permission to do. We can't write attestations to `php/pie` in an
+ # unprivileged context, otherwise anyone could send a PR with malicious
+ # code, which would store attestation that `php/pie` built the binaries,
+ # and it would look genuine. So this should NOT run for PR builds.
+ if: github.event_name != 'pull_request'
+ uses: actions/attest-build-provenance@v3
+ with:
+ subject-path: '${{ github.workspace }}/${{ env.PIE_BINARY_OUTPUT }}'
+
+ - uses: actions/upload-artifact@v5
+ with:
+ name: pie-${{ github.sha }}-${{ runner.os }}-${{ runner.arch }}.bin
+ path: ${{ env.PIE_BINARY_OUTPUT }}
diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml
deleted file mode 100644
index b0b5aad8..00000000
--- a/.github/workflows/build-phar.yml
+++ /dev/null
@@ -1,65 +0,0 @@
-# Invoking this pipeline requires additional permissions, so must be invoked
-# in a way to pass those permissions on, e.g.:
-#
-# build-phar:
-# permissions:
-# contents: read
-# id-token: write
-# attestations: write
-# uses: ./.github/workflows/build-phar.yml
-
-name: "Build the PIE PHAR"
-
-on:
- workflow_call:
-
-permissions:
- contents: read
-
-jobs:
- build-phar:
- runs-on: ${{ matrix.operating-system }}
- strategy:
- matrix:
- operating-system:
- - ubuntu-latest
- php-versions:
- - '8.1'
- permissions:
- # id-token:write is required for build provenance attestation.
- id-token: write
- # attestations:write is required for build provenance attestation.
- attestations: write
- steps:
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- coverage: none
- tools: composer, box
- php-version: "${{ matrix.php-version }}"
- - uses: actions/checkout@v6
- with:
- fetch-depth: 0
- # Fixes `git describe` picking the wrong tag - see https://github.com/php/pie/issues/307
- - run: git fetch --tags --force
- # Ensure some kind of previous tag exists, otherwise box fails
- - run: git describe --tags HEAD || git tag 0.0.0
- - uses: ramsey/composer-install@v3
- - name: Build PHAR
- run: box compile
- - name: Check the PHAR executes
- run: php pie.phar --version
- - name: Generate build provenance attestation
- # It does not make sense to do this for PR builds, nor do contributors
- # have permission to do. We can't write attestations to `php/pie` in an
- # unprivileged context, otherwise anyone could send a PR with malicious
- # code, which would store attestation that `php/pie` built the PHAR, and
- # it would look genuine. So this should NOT run for PR builds.
- if: github.event_name != 'pull_request'
- uses: actions/attest-build-provenance@v3
- with:
- subject-path: '${{ github.workspace }}/pie.phar'
- - uses: actions/upload-artifact@v5
- with:
- name: pie-${{ github.sha }}.phar
- path: pie.phar
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 8bdacbbc..cffed089 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -187,14 +187,14 @@ jobs:
- name: Run phpstan
run: vendor/bin/phpstan
- build-phar:
+ build-assets:
needs:
- unit-tests
- coding-standards
- static-analysis
- # See build-phar.yml for a list of the permissions and why they are needed
+ # See build-assets.yml for a list of the permissions and why they are needed
permissions:
contents: read
id-token: write
attestations: write
- uses: ./.github/workflows/build-phar.yml
+ uses: ./.github/workflows/build-assets.yml
diff --git a/.github/workflows/docker-nightly-image-push.yml b/.github/workflows/docker-nightly-image-push.yml
index 43d52198..064a96bc 100644
--- a/.github/workflows/docker-nightly-image-push.yml
+++ b/.github/workflows/docker-nightly-image-push.yml
@@ -11,18 +11,18 @@ permissions:
contents: read
jobs:
- build-phar:
+ build-assets:
if: github.ref_name == github.event.repository.default_branch
- # See build-phar.yml for a list of the permissions and why they are needed
+ # See build-assets.yml for a list of the permissions and why they are needed
permissions:
contents: read
id-token: write
attestations: write
- uses: ./.github/workflows/build-phar.yml
+ uses: ./.github/workflows/build-assets.yml
build-and-push-docker-image:
if: github.ref_name == github.event.repository.default_branch
- needs: build-phar
+ needs: build-assets
# See build-and-push-docker-image.yml for a list of the permissions and why they are needed
permissions:
contents: read
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 6710df88..f5ab3863 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,20 +17,20 @@ concurrency:
cancel-in-progress: false
jobs:
- build-phar:
+ build-assets:
if: github.ref_name == github.event.repository.default_branch
- # See build-phar.yml for a list of the permissions and why they are needed
+ # See build-assets.yml for a list of the permissions and why they are needed
permissions:
contents: read
id-token: write
attestations: write
- uses: ./.github/workflows/build-phar.yml
+ uses: ./.github/workflows/build-assets.yml
build-docs-package:
if: github.ref_name == github.event.repository.default_branch
runs-on: ubuntu-latest
needs:
- - build-phar
+ - build-assets
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -40,12 +40,22 @@ jobs:
uses: actions/download-artifact@v6
with:
name: pie-${{ github.sha }}.phar
- - name: Verify the PHAR
+ - name: Fetch the executable PIEs from artifacts
+ uses: actions/download-artifact@v5
+ with:
+ path: executable-pie-binaries
+ pattern: pie-${{ github.sha }}-*.bin
+ merge-multiple: true
+ - name: Verify the PHAR and binaries
env:
GH_TOKEN: ${{ github.token }}
- run: gh attestation verify pie.phar --repo ${{ github.repository }}
+ run: |
+ gh attestation verify pie.phar --repo ${{ github.repository }} ;
+ find executable-pie-binaries -type f -exec gh attestation verify {} --repo ${{ github.repository }} \;
- name: Copy PHAR into docs
run: cp pie.phar docs-package/pie-nightly.phar
+ - name: Copy executables into docs
+ run: cp executable-pie-binaries/* docs-package/
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 625fd058..ee5de128 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,4 @@
-name: "Publish a draft release with PHAR attached"
+name: "Publish a draft release with assets attached"
on:
push:
@@ -9,18 +9,18 @@ permissions:
contents: read
jobs:
- build-phar:
- # See build-phar.yml for a list of the permissions and why they are needed
+ build-assets:
+ # See build-assets.yml for a list of the permissions and why they are needed
permissions:
contents: read
id-token: write
attestations: write
- uses: ./.github/workflows/build-phar.yml
+ uses: ./.github/workflows/build-assets.yml
create-draft-release:
runs-on: ubuntu-latest
needs:
- - build-phar
+ - build-assets
permissions:
# contents:write is required to create the draft release
contents: write
@@ -39,7 +39,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: gh release create "${{ github.ref_name }}" --title "${{ github.ref_name }}" --draft --notes-from-tag
- release-phar:
+ attach-release-assets:
runs-on: ubuntu-latest
needs:
- create-draft-release
@@ -52,23 +52,34 @@ jobs:
uses: actions/download-artifact@v6
with:
name: pie-${{ github.sha }}.phar
- - name: Verify the PHAR
+ - name: Fetch the executable PIEs from artifacts
+ uses: actions/download-artifact@v5
+ with:
+ path: executable-pie-binaries
+ pattern: pie-${{ github.sha }}-*.bin
+ merge-multiple: true
+ - name: Verify the PHAR and binaries
env:
GH_TOKEN: ${{ github.token }}
- run: gh attestation verify pie.phar --repo ${{ github.repository }}
- # Once the PHAR has been attached to the release, it is ready for review
- # before publishing it. Note that if immutable releases are enabled,
- # the tag, pre-release/latest release flag, and all assets become
- # immutable, so checking this over is a manual exercise.
+ run: |
+ gh attestation verify pie.phar --repo ${{ github.repository }} ;
+ find executable-pie-binaries -type f -exec gh attestation verify {} --repo ${{ github.repository }} \;
+ # Once the PHAR and binaries have been attached to the release, it is
+ # ready for review before publishing it. Note that if immutable releases
+ # are enabled, the tag, pre-release/latest release flag, and all assets
+ # become immutable, so checking this over is a manual exercise.
# More info: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases
- - name: Attach an asset to the draft release
+ - name: Attach the assets to the draft release
env:
GH_TOKEN: ${{ github.token }}
- run: gh release upload "${{ github.ref_name }}" "pie.phar" --clobber
+ run: |
+ gh release upload "${{ github.ref_name }}" "pie.phar" --clobber ;
+ find executable-pie-binaries -type f -exec gh release upload "${{ github.ref_name }}" {} --clobber \;
build-and-push-docker-image:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
- needs: build-phar
+ needs:
+ - build-assets
# See build-and-push-docker-image.yml for a list of the permissions and why they are needed
permissions:
contents: read
diff --git a/docs/usage.md b/docs/usage.md
index b775a11d..174c2ff0 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -70,6 +70,43 @@ RUN pie install asgrim/example-pie-extension
If the extension you would like to install needs additional libraries or other
dependencies, then these must be installed beforehand too.
+### Executable PIE (Experimental)
+
+As of 1.4.0 an **experimental** executable (binary) version of PIE is included.
+The PIE is built using [Static PHP](https://static-php.dev/), which builds a
+self-contained PHP executable with the extensions that PIE needs to run, and
+bundles the PHAR as a single distributable executable. Please keep in mind that
+this is **experimental** and we do not recommend this for production use for
+the time being. Please also note there are some limitations:
+
+ - [php/pie#459](https://github.com/php/pie/discussions/459) - the OSX version
+ is not signed with an Apple Developer account, which means (at your own risk)
+ you would need to tell your system to trust the downloaded executable.
+ - [php/pie#460](https://github.com/php/pie/discussions/460) - all the binary
+ versions have the `pie self-update` feature disabled for now.
+
+If you find the binary releases useful, please leave feedback or upvote on the
+relevant discussions, so we can gauge interest in improving this functionality.
+
+The "nightly" versions of these can be found here:
+
+| Operating System | Architecture | Download URL |
+|------------------|------------------|-----------------------------------------------|
+| Linux | ARM 64 / aarch64 | https://php.github.io/pie/pie-Linux-ARM64 |
+| Linux | amd64 / x86_64 | https://php.github.io/pie/pie-Linux-X64 |
+| OS X | ARM 64 / aarch64 | https://php.github.io/pie/pie-macOS-ARM64 |
+| OS X | Intel / x86_64 | https://php.github.io/pie/pie-macOS-X64 |
+| Windows | x86_64 | https://php.github.io/pie/pie-Windows-X64.exe |
+
+We *highly* recommend you verify the file came from the PHP GitHub repository
+before running it, for example:
+
+```shell
+$ gh attestation verify --owner php pie-Linux-X64
+$ chmod +x pie-Linux-X64
+$ ./pie-Linux-X64 --version
+```
+
## Prerequisites for PIE
Running PIE requires PHP 8.1 or newer. However, you may still use PIE to install
diff --git a/resources/spc/.gitignore b/resources/spc/.gitignore
new file mode 100644
index 00000000..8c9ae3ad
--- /dev/null
+++ b/resources/spc/.gitignore
@@ -0,0 +1,9 @@
+buildroot
+downloads
+log
+pkgroot
+source
+pie.elf
+pie.phar
+spc
+spc.exe
diff --git a/resources/spc/build.sh b/resources/spc/build.sh
new file mode 100755
index 00000000..ad43f59d
--- /dev/null
+++ b/resources/spc/build.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# A little test build script for manually building a self-contained PIE executable
+# Note; needs `spc` pre-installed:
+# curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# chmod +x spc
+
+set -xeuo pipefail
+
+SPC_ROOT=$(dirname "$0")
+PIE_PROJECT_ROOT="$SPC_ROOT/../.."
+
+# First up, build the regular pie.phar
+cd "$PIE_PROJECT_ROOT"
+php box.phar compile
+mv pie.phar "$SPC_ROOT/pie.phar"
+
+cd "$SPC_ROOT"
+
+# Build the static PHP micro.sfx
+./spc craft
+
+# Combine pie.phar with the micro.sfx
+./spc micro:combine pie.phar --output=pie.elf
+
+# Docker build & run the test with it
+docker build --file spc.Dockerfile --tag pie-spc-test .
+docker run --rm -ti pie-spc-test
diff --git a/resources/spc/craft.yml b/resources/spc/craft.yml
new file mode 100644
index 00000000..0d4d8a13
--- /dev/null
+++ b/resources/spc/craft.yml
@@ -0,0 +1,5 @@
+php-version: 8.4
+extensions: "curl,filter,iconv,openssl,phar,zlib"
+sapi: micro
+download-options:
+ prefer-pre-built: true
diff --git a/resources/spc/spc.Dockerfile b/resources/spc/spc.Dockerfile
new file mode 100644
index 00000000..cbde6484
--- /dev/null
+++ b/resources/spc/spc.Dockerfile
@@ -0,0 +1,11 @@
+# docker build --file spc.Dockerfile --tag pie-spc-test .
+# docker run --rm -ti pie-spc-test
+FROM php:7.4-cli
+
+# need
+RUN apt-get update && \
+ apt-get install -y git
+
+COPY pie.elf /usr/bin/pie
+
+CMD ["pie", "install", "asgrim/example-pie-extension"]
diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php
index c43ff079..f8ac776e 100644
--- a/src/Command/CommandHelper.php
+++ b/src/Command/CommandHelper.php
@@ -197,7 +197,12 @@ public static function determineTargetPlatformFromInputs(InputInterface $input,
$targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath, $makeParallelJobs);
- $io->write(sprintf('You are running PHP %s', PHP_VERSION));
+ if (PiePlatform::isRunningStaticPhp()) {
+ $io->write(sprintf('You are running a PIE Static PHP %s build', PHP_VERSION));
+ } else {
+ $io->write(sprintf('You are running PHP %s', PHP_VERSION));
+ }
+
$io->write(sprintf(
'Target PHP installation: %s %s%s, on %s %s (from %s)',
$phpBinaryPath->version(),
diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php
index ed3a7671..8e371632 100644
--- a/src/Command/SelfUpdateCommand.php
+++ b/src/Command/SelfUpdateCommand.php
@@ -82,7 +82,7 @@ public function configure(): void
public function execute(InputInterface $input, OutputInterface $output): int
{
- if (! PieVersion::isPharBuild()) {
+ if (! PieVersion::isPharBuild() || Platform::isRunningStaticPhp()) {
$this->io->writeError('Aborting! You are not running a PHAR, cannot self-update.');
return Command::FAILURE;
diff --git a/src/Platform.php b/src/Platform.php
index 90d50caf..4abeb709 100644
--- a/src/Platform.php
+++ b/src/Platform.php
@@ -15,10 +15,12 @@
use function implode;
use function md5;
use function rtrim;
+use function str_contains;
use function strpos;
use function strtr;
use const DIRECTORY_SEPARATOR;
+use const PHP_BUILD_PROVIDER;
use const STDIN;
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
@@ -125,4 +127,9 @@ public static function getPieJsonFilename(TargetPlatform $targetPlatform): strin
{
return self::getPieWorkingDirectory($targetPlatform) . '/pie.json';
}
+
+ public static function isRunningStaticPhp(): bool
+ {
+ return defined('PHP_BUILD_PROVIDER') && str_contains(PHP_BUILD_PROVIDER, 'static-php-cli');
+ }
}