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'); + } }