From 727637a94b828742cc271ccbf5188ae9506fde71 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 9 Oct 2026 09:34:30 +0200 Subject: [PATCH] Resolve the Windows PHP prerelease from windows.php.net's manifests On Windows, setup-php installs a nightly for an unreleased minor, and a nightly has no devel pack, so the turbo legs replace it with the official prerelease. Its version was pinned in PHP86_WINDOWS_VERSION, which went stale (8.6.0RC2 while RC3 is out), and its download was not verified. The new php-windows-prereleases job reads windows.php.net's stable and QA manifests once per run. For every minor in the workflow's matrices that has no stable Windows build, it outputs the prerelease's version and the file name and SHA-256 of each runtime and devel pack. The install and devel-pack steps use that entry and verify the checksums, and run for whichever minors it lists. Resolving once per run keeps every leg on the same build when a release candidate comes out mid-run. Once a minor has its stable release, the swap stops on its own. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_017h1GfB7vfJPgwcTHD9Fth6 --- .github/scripts/download-php-windows-devel.sh | 33 ++++++--- .../scripts/find-php-windows-prereleases.sh | 40 +++++++++++ .github/scripts/find-turbo-origins.sh | 2 +- .../scripts/install-php-windows-prerelease.sh | 33 +++++++++ .github/scripts/install-php86-windows.sh | 24 ------- .github/workflows/phar.yml | 69 +++++++++++++++---- turbo-ext/README.md | 5 +- 7 files changed, 158 insertions(+), 48 deletions(-) create mode 100755 .github/scripts/find-php-windows-prereleases.sh create mode 100644 .github/scripts/install-php-windows-prerelease.sh delete mode 100644 .github/scripts/install-php86-windows.sh diff --git a/.github/scripts/download-php-windows-devel.sh b/.github/scripts/download-php-windows-devel.sh index 14267a84ae2..24a5988a66c 100644 --- a/.github/scripts/download-php-windows-devel.sh +++ b/.github/scripts/download-php-windows-devel.sh @@ -3,22 +3,35 @@ # (the short SHA of the last commit touching turbo-ext/src, the Makefile's # computation) and fetches the php-devel-pack matching the installed PHP # (MATRIX_TS: nts/zts, MATRIX_VS: its toolset infix) into C:\php-devel and the -# php-sdk-binary-tools at PHP_SDK_COMMIT into C:\php-sdk. +# php-sdk-binary-tools at PHP_SDK_COMMIT into C:\php-sdk. For a prerelease +# (PHP_PRERELEASE, the minor's entry find-php-windows-prereleases.sh resolved +# for this run) the pack and its checksum come from that entry. set -euo pipefail SHA=$(git log -1 --format=%H -- turbo-ext/src | cut -c1-7) echo "extension version: $SHA" echo "PHPSTANTURBO_VERSION=$SHA" >> "$GITHUB_ENV" -FULL=$(php -r 'echo PHP_VERSION;') -# thread-safe devel packs carry no infix, NTS ones carry -nts -INFIX=$([ "$MATRIX_TS" = "zts" ] && echo "" || echo "-nts") -PACK="php-devel-pack-$FULL$INFIX-Win32-$MATRIX_VS-x64.zip" -echo "devel pack: $PACK" -curl -fsSLo devel-pack.zip "https://windows.php.net/downloads/releases/$PACK" \ - || curl -fsSLo devel-pack.zip "https://windows.php.net/downloads/releases/archives/$PACK" \ - || curl -fsSLo devel-pack.zip "https://downloads.php.net/~windows/qa/$PACK" \ - || curl -fsSLo devel-pack.zip "https://downloads.php.net/~windows/qa/archives/$PACK" +if [ -n "${PHP_PRERELEASE:-}" ] && [ "$PHP_PRERELEASE" != "null" ]; then + # shellcheck disable=SC2016 # PHP code, not shell expansions + read -r PACK SHA256 < <(php -r ' + $entry = json_decode(getenv("PHP_PRERELEASE"), true, 512, JSON_THROW_ON_ERROR); + $devel = $entry[getenv("MATRIX_TS") === "zts" ? "ts" : "nts"]["devel"]; + echo $devel["path"], " ", $devel["sha256"], "\n"; + ') + echo "devel pack: $PACK" + curl -fsSLo devel-pack.zip "https://downloads.php.net/~windows/qa/$PACK" \ + || curl -fsSLo devel-pack.zip "https://downloads.php.net/~windows/qa/archives/$PACK" + echo "$SHA256 devel-pack.zip" | sha256sum -c - +else + FULL=$(php -r 'echo PHP_VERSION;') + # thread-safe devel packs carry no infix, NTS ones carry -nts + INFIX=$([ "$MATRIX_TS" = "zts" ] && echo "" || echo "-nts") + PACK="php-devel-pack-$FULL$INFIX-Win32-$MATRIX_VS-x64.zip" + echo "devel pack: $PACK" + curl -fsSLo devel-pack.zip "https://windows.php.net/downloads/releases/$PACK" \ + || curl -fsSLo devel-pack.zip "https://windows.php.net/downloads/releases/archives/$PACK" +fi unzip -q devel-pack.zip -d /c/php-devel git clone -q https://github.com/php/php-sdk-binary-tools.git /c/php-sdk diff --git a/.github/scripts/find-php-windows-prereleases.sh b/.github/scripts/find-php-windows-prereleases.sh new file mode 100755 index 00000000000..a459e865286 --- /dev/null +++ b/.github/scripts/find-php-windows-prereleases.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# For an unreleased PHP minor, setup-php installs a nightly build of its +# development branch on Windows, which has no devel pack to compile an +# extension against. The Windows legs of phar.yml then replace that runtime +# with the official prerelease (install-php-windows-prerelease.sh) and build +# against its devel pack (download-php-windows-devel.sh). +# +# For each minor in MINORS that windows.php.net has no stable build of, this +# writes the current prerelease from its QA manifest to the `prereleases` +# step output: +# {"": {"version": "8.6.0RC3", +# "nts": {"zip": {"path", "sha256"}, "devel": {"path", "sha256"}}, +# "ts": {...}}} +# A minor with a stable build is left out: setup-php installs that release, +# whose devel pack is published next to it. It runs once per workflow run, +# so every job installs the same prerelease even when a new one comes out +# in the meantime. +# +# Usage: MINORS="8.3 8.4 8.5 8.6" find-php-windows-prereleases.sh +set -euo pipefail + +: "${MINORS:?set MINORS to the PHP minors the Windows legs build for}" +fetch() { curl -fsSL --retry 3 --retry-all-errors "https://downloads.php.net/~windows/$1/releases.json"; } +stable=$(fetch releases) +qa=$(fetch qa) + +prereleases=$(jq -cn --argjson stable "$stable" --argjson qa "$qa" --arg minors "$MINORS" ' + def files($builds; $ts): + $builds | to_entries + | map(select(.key | test("^" + $ts + "-vs[0-9]+-x64$"))) + | if length == 1 then .[0].value | {zip: (.zip | {path, sha256}), devel: (.devel_pack | {path, sha256})} + else error("expected one \($ts) x64 build, found \(length)") end; + reduce ($minors | split(" ") | .[] | select(. != "")) as $minor ({}; + if $stable | has($minor) then . + elif $qa | has($minor) | not then error("windows.php.net has neither a stable nor a QA build of PHP \($minor)") + else .[$minor] = {version: $qa[$minor].version, nts: files($qa[$minor]; "nts"), ts: files($qa[$minor]; "ts")} + end)') + +jq -r 'if length == 0 then "every minor has a stable build" else to_entries[] | "PHP \(.key): prerelease \(.value.version)" end' <<< "$prereleases" +echo "prereleases=$prereleases" >> "${GITHUB_OUTPUT:-/dev/stdout}" diff --git a/.github/scripts/find-turbo-origins.sh b/.github/scripts/find-turbo-origins.sh index ae6acd5e04c..4cd324f8d73 100755 --- a/.github/scripts/find-turbo-origins.sh +++ b/.github/scripts/find-turbo-origins.sh @@ -49,7 +49,7 @@ BUILD_INPUT_PATHS=( .github/scripts/install-alpine-php.sh .github/scripts/download-composer.sh .github/scripts/composer-tags.pub - .github/scripts/install-php86-windows.sh + .github/scripts/install-php-windows-prerelease.sh .github/scripts/download-php-windows-devel.sh turbo-ext/bin/shared-core ) diff --git a/.github/scripts/install-php-windows-prerelease.sh b/.github/scripts/install-php-windows-prerelease.sh new file mode 100644 index 00000000000..ef3a4859afc --- /dev/null +++ b/.github/scripts/install-php-windows-prerelease.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +# setup-php supplies Composer, php.ini and CA certificates, but for an +# unreleased minor it installs a nightly build, which has no matching +# development pack. Replace the runtime and bundled extensions with the +# official prerelease find-php-windows-prereleases.sh resolved for this run +# (PHP_PRERELEASE, its JSON entry for the minor), preserving that +# configuration. +PHP_DIR="$(cygpath -u "$(php -r 'echo dirname(PHP_BINARY);')")" +case "$MATRIX_TS" in + zts) TS=ts ;; + nts) TS=nts ;; + *) echo "Unknown thread-safety mode: $MATRIX_TS" >&2; exit 1 ;; +esac +# shellcheck disable=SC2016 # PHP code, not shell expansions +read -r VERSION PACK SHA256 < <(php -r ' + $entry = json_decode(getenv("PHP_PRERELEASE"), true, 512, JSON_THROW_ON_ERROR); + $zip = $entry[$argv[1]]["zip"]; + echo $entry["version"], " ", $zip["path"], " ", $zip["sha256"], "\n"; +' "$TS") +ARCHIVE="$RUNNER_TEMP/$PACK" +curl -fsSLo "$ARCHIVE" "https://downloads.php.net/~windows/qa/$PACK" \ + || curl -fsSLo "$ARCHIVE" "https://downloads.php.net/~windows/qa/archives/$PACK" +echo "$SHA256 $ARCHIVE" | sha256sum -c - +unzip -qo "$ARCHIVE" -d "$PHP_DIR" +EXPECTED_VERSION="$VERSION" php -r ' + if (PHP_VERSION !== getenv("EXPECTED_VERSION") || (bool) PHP_ZTS !== (getenv("MATRIX_TS") === "zts")) { + fwrite(STDERR, "PHP prerelease version or thread-safety mismatch\n"); + exit(1); + } + echo "Using PHP ", PHP_VERSION, PHP_ZTS ? " ZTS\n" : " NTS\n"; +' diff --git a/.github/scripts/install-php86-windows.sh b/.github/scripts/install-php86-windows.sh deleted file mode 100644 index f7eebca091e..00000000000 --- a/.github/scripts/install-php86-windows.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# setup-php supplies Composer, php.ini and CA certificates, but its 8.6 -# nightly has no matching development pack. Replace the runtime and bundled -# extensions with the official prerelease, preserving that configuration. -PHP_DIR="$(cygpath -u "$(php -r 'echo dirname(PHP_BINARY);')")" -case "$MATRIX_TS" in - zts) INFIX="" ;; - nts) INFIX="-nts" ;; - *) echo "Unknown thread-safety mode: $MATRIX_TS" >&2; exit 1 ;; -esac -PACK="php-$PHP86_WINDOWS_VERSION$INFIX-Win32-vs18-x64.zip" -ARCHIVE="$RUNNER_TEMP/$PACK" -curl -fsSLo "$ARCHIVE" "https://downloads.php.net/~windows/qa/$PACK" \ - || curl -fsSLo "$ARCHIVE" "https://downloads.php.net/~windows/qa/archives/$PACK" -unzip -qo "$ARCHIVE" -d "$PHP_DIR" -php -r ' - if (PHP_VERSION !== getenv("PHP86_WINDOWS_VERSION") || (bool) PHP_ZTS !== (getenv("MATRIX_TS") === "zts")) { - fwrite(STDERR, "PHP prerelease version or thread-safety mismatch\n"); - exit(1); - } - echo "Using PHP ", PHP_VERSION, PHP_ZTS ? " ZTS\n" : " NTS\n"; -' diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 71e85748584..b99ffd2822a 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -30,8 +30,6 @@ env: # The php-parser version whose grammar tables and semantic actions the # native parser engine (turbo-ext/src/parser/) was ported against. SUPPORTED_PHP_PARSER_VERSION: "v5.9.0" - # setup-php installs an 8.6 nightly, which has no matching Windows devel pack. - PHP86_WINDOWS_VERSION: "8.6.0RC2" jobs: compiler-tests: @@ -335,6 +333,45 @@ jobs: make -C "$RUNNER_TEMP/turbo-ext" install INSTALL_ROOT="$RUNNER_TEMP/install-root" find "$RUNNER_TEMP/install-root" -name 'phpstan_turbo.so' | grep . + php-windows-prereleases: + name: "Find PHP Prereleases for Windows" + # For an unreleased minor, setup-php installs a nightly build on Windows, + # which has no devel pack to compile against; the Windows legs replace it + # with the official prerelease. This resolves the current one for every + # minor in this workflow's matrices that has no stable Windows build yet + # (.github/scripts/find-php-windows-prereleases.sh), once per run, so all + # legs install the same build even when a new release candidate comes out + # in the meantime. + runs-on: "ubuntu-latest" + timeout-minutes: 5 + + permissions: + contents: read # actions/checkout of this repository + + outputs: + prereleases: ${{ steps.find.outputs.prereleases }} + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: "Checkout" + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + persist-credentials: false + sparse-checkout: | + .github/scripts + .github/workflows + + - name: "Find the prerelease of every PHP minor without a stable Windows build" + id: find + run: | + MINORS="$(yq -r '[.jobs[].strategy.matrix["php-version"] | select(. != null) | .[]] | unique | .[] | select(test("^[0-9]+\\.[0-9]+$"))' .github/workflows/phar.yml | tr '\n' ' ')" + echo "PHP minors in the matrices: $MINORS" + MINORS="$MINORS" bash .github/scripts/find-php-windows-prereleases.sh + turbo-origins: name: "Find Reusable Turbo Extension Builds" # Most pushes and pull requests leave the extension's build inputs @@ -1469,7 +1506,9 @@ jobs: # is the one that ships VS2022. # PHP 8.6's vs18 development packs need the VS2026 toolchain. runs-on: ${{ matrix.php-version == '8.6' && 'windows-2025-vs2026' || 'windows-2022' }} - needs: turbo-compile-core-windows + needs: + - php-windows-prereleases + - turbo-compile-core-windows # see turbo-compile if: ${{ !cancelled() }} timeout-minutes: ${{ matrix.variant == 'pie' && 45 || 30 }} @@ -1518,18 +1557,20 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" - - name: "Install the matching PHP 8.6 prerelease runtime" - if: matrix.php-version == '8.6' + - name: "Install the PHP prerelease runtime" + if: fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version] shell: bash env: MATRIX_TS: ${{ matrix.ts }} - run: bash .github/scripts/install-php86-windows.sh + PHP_PRERELEASE: ${{ toJSON(fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version]) }} + run: bash .github/scripts/install-php-windows-prerelease.sh - name: "Compute the extension version and download the build tools" shell: bash env: MATRIX_TS: ${{ matrix.ts }} MATRIX_VS: ${{ matrix.vs }} + PHP_PRERELEASE: ${{ toJSON(fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version]) }} run: bash .github/scripts/download-php-windows-devel.sh # the DLL and its import library, into the extension directory where @@ -1751,6 +1792,7 @@ jobs: # it at all; the ZTS legs so as not to depend on setup-php's thread-safe # Linux builds. needs: + - php-windows-prereleases - turbo-compile - turbo-compile-windows runs-on: ${{ matrix.runs-on }} @@ -1856,11 +1898,12 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" - - name: "Install the matching PHP 8.6 prerelease runtime" - if: matrix.target == 'windows-x86_64' && matrix.php-version == '8.6' + - name: "Install the PHP prerelease runtime" + if: matrix.target == 'windows-x86_64' && fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version] env: MATRIX_TS: ${{ matrix.ts }} - run: bash .github/scripts/install-php86-windows.sh + PHP_PRERELEASE: ${{ toJSON(fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version]) }} + run: bash .github/scripts/install-php-windows-prerelease.sh - uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0 if: ${{ !matrix.container }} @@ -2038,6 +2081,7 @@ jobs: turbo-run: name: "Run with Turbo Extension" needs: + - php-windows-prereleases - turbo-compile - turbo-compile-windows runs-on: ${{ matrix.operating-system }} @@ -2109,11 +2153,12 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" - - name: "Install the matching PHP 8.6 prerelease runtime" - if: matrix.operating-system == 'windows-latest' && matrix.php-version == '8.6' + - name: "Install the PHP prerelease runtime" + if: matrix.operating-system == 'windows-latest' && fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version] env: MATRIX_TS: ${{ matrix.ts }} - run: bash .github/scripts/install-php86-windows.sh + PHP_PRERELEASE: ${{ toJSON(fromJSON(needs.php-windows-prereleases.outputs.prereleases)[matrix.php-version]) }} + run: bash .github/scripts/install-php-windows-prerelease.sh - name: "Download extension artifact" uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/turbo-ext/README.md b/turbo-ext/README.md index b66fef16ae7..a82b5ee05e5 100644 --- a/turbo-ext/README.md +++ b/turbo-ext/README.md @@ -537,7 +537,10 @@ Windows legs' tests are their check. infix and toolset in `include`) and of the jobs testing them (`turbo-differential`, `turbo-differential-musl`, `turbo-run`, `turbo-docker-run`), and give the new version a `gnu-php` image - for the Linux gnu legs. The cores need no change: they keep compiling + for the Linux gnu legs. Until the version has a stable Windows release, + the Windows legs install its current prerelease by themselves (the + `php-windows-prereleases` job reads windows.php.net's manifests), so + there is no version to pin. The cores need no change: they keep compiling against the version they are pinned to (`php-version` in `turbo-compile-core`'s targets, `["8.3"]` with the vs16 toolset in `turbo-compile-core-windows`). Run the smoke test of every version