Skip to content

Test distribution of turbo-ext#6047

Open
ondrejmirtes wants to merge 10 commits into
2.2.xfrom
turbo-dist
Open

Test distribution of turbo-ext#6047
ondrejmirtes wants to merge 10 commits into
2.2.xfrom
turbo-dist

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

No description provided.

ondrejmirtes and others added 6 commits July 13, 2026 23:26
…x and macOS

The turbo-ext workflow moves into phar.yml so the extension is built,
tested, and committed to phpstan/phpstan alongside phpstan.phar. The
matrix covers PHP 8.4 + 8.5 across linux-gnu (x86_64/arm64, built in
ubuntu:22.04 containers via ppa:ondrej/php for a glibc 2.34 symbol
floor), linux-musl (alpine:3.23 containers), and a lipo-merged macOS
universal binary that is codesigned and notarized on push builds.
Every leg runs the differential battery (smoke, signature parity,
parser corpus). The commit job stages the binaries into
turbo-ext/<target>/phpstan_turbo-<minor>.so gated by a .version file
carrying EXPECTED_EXTENSION_VERSION, since native builds are not
byte-reproducible; phar staging is now conditional on its checksum so
a turbo-only update does not churn the phar.

Portability fixes the wider matrix required:

- php-parser assigns compat tokens ids counting down from -1 on hosts
  whose tokenizer lacks them (T_PIPE on 8.4); the dense phpTokenToSymbol
  conversion sized the map as (zend_ulong) -1 + 1 = 0, failing every
  parse on PHP 8.4, and the emulative lexer can emit those negative ids
  into the actual token stream where the LALR loop indexed dropTokens/
  phpTokenToSymbol out of bounds. The map is now bias-indexed and the
  parser corpus gained emulative-lexer snippets that pin the behavior.
- zend_weakrefs_hash_destroy() only exists since PHP 8.5; 8.4 builds
  use an equivalent spelled with the 8.4-available API.
- PHP-CPP needs an explicit <cstdint> include on musl, carried as a
  second local patch; the Makefile phpcpp target applies all patches.
- Alpine's gcc warns -Wattributes on zend_vm_opcodes.h's preserve_none;
  added to the third-party-header pragma guard.
- gcc 11 (the glibc-floor compiler) predates P1825 implicit
  derived-to-base moves, so returning a zv::Arr local from a
  zv::Val-returning function now spells the move explicitly.
- Linux links -static-libstdc++ -static-libgcc so the shipped .so
  depends only on libc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
The macos-x86_64/macos-arm64 compile legs, the lipo + notarization job,
the macOS run leg, and the macOS staging in the commit job need the
Developer ID certificate and App Store Connect API key secrets to be
useful. Revert this commit to bring the macOS build back once they are
configured; the family-conditional steps in turbo-compile stay in place
so the revert applies cleanly. The worker extension selector already
treats a missing turbo-ext/macos directory as "no extension available".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
TurboExtensionEnabler require_onces turbo-ext/stubs/*.php relative to
its own location, so the stub shells must travel with the phar; and the
PHPStanTurbo namespace must survive scoping untouched — the stubs extend
the native extension's classes by exact name (php-scoper's namespace
matching is segment-aware, so the existing PHPStan entry does not cover
PHPStanTurbo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
TurboExtensionSelector locates the binary committed next to
phpstan.phar (turbo-ext/<platform>/phpstan_turbo-<minor>.so) that
matches the current runtime — PHP minor version, OS family, machine
architecture, and libc (no constant exists for libc, so musl is
detected by the same filesystem heuristic datadog-setup.php uses).
ProcessHelper appends -d extension=<path> to the worker command when a
matching binary exists, so phar installations get the extension in the
analysis workers without any php.ini setup; a source checkout keeps
loading its locally built extension through php.ini, which the selector
detects and stays out of. Workers run the regular entrypoint, so
TurboExtensionEnabler still gates activation on the expected extension
version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
…ther tests

A turbo-artifact job aggregates the compile matrix outputs into the
distribution layout (<target>/phpstan_turbo-<minor>.so, derived from
artifact names so matrix changes need no edit here) and uploads them as
one turbo-ext-files artifact. The reusable test workflows in
phpstan/phpstan download it next to the phar under test, so those runs
exercise the freshly built extension through the worker selector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed

- name: "Cache PHP-CPP"
id: "cache-php-cpp"
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
echo "built extension reports: $REPORTED, enabler expects: $EXPECTED"
[ "$REPORTED" = "$EXPECTED" ]

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
sudo bash -c "echo 'extension=$GITHUB_WORKSPACE/turbo-ext/phpstan_turbo.so' >> $(php -r 'echo php_ini_loaded_file();')"
php -m | grep phpstan_turbo

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
Comment on lines +147 to +197
turbo-version:
name: "Turbo Extension Version Check"
runs-on: "ubuntu-latest"
timeout-minutes: 5

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # git log over turbo-ext/src needs full history

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
with:
coverage: "none"
php-version: "8.5"

- name: "Check the shadowed PHP and C++ implementations are in sync (method parity)"
run: "php turbo-ext/bin/side-by-side.php --check"

- name: "Check the vendored php-parser matches the version the native parser engine was ported against"
run: |
INSTALLED="$(jq -r '.packages[] | select(.name == "nikic/php-parser") | .version' composer.lock)"
echo "composer.lock: $INSTALLED, native engine ported against: $SUPPORTED_PHP_PARSER_VERSION"
if [ "$INSTALLED" != "$SUPPORTED_PHP_PARSER_VERSION" ]; then
echo "::error::nikic/php-parser was updated to $INSTALLED but turbo-ext/src/parser/ was ported against $SUPPORTED_PHP_PARSER_VERSION."
echo "::error::Update procedure: diff ParserAbstract.php helpers between the two versions (grammar tables need nothing — they are read at run time), run turbo-ext/bin/generate-parser-actions.php and port any flagged closure bodies via action-overrides/, run turbo-ext/tests/parser-corpus.php until byte-identical, run the full suite, then bump SUPPORTED_PHP_PARSER_VERSION here and the extension version pin."
exit 1
fi

- name: "Check TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION against the last commit touching turbo-ext/src or a shadowed PHP class"
run: |
# A change to either side of a shadowed pair must bump the version:
# watch turbo-ext/src plus the repo-local PHP files from the manifest
# (vendored entries are governed by composer.lock instead).
mapfile -t MAPPED_PHP < <(jq -r '.[] | select(.vendored != true) | .php' turbo-ext/shadowed-classes.json)
EXPECTED_SHA="$(git log -1 --format=%H -- turbo-ext/src "${MAPPED_PHP[@]}" | cut -c1-7)"
ENABLER="$(sed -n "s/.*EXPECTED_EXTENSION_VERSION = '\([^']*\)'.*/\1/p" src/Turbo/TurboExtensionEnabler.php)"
echo "last commit touching turbo-ext/src or a shadowed PHP class: $EXPECTED_SHA"
echo "TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION: $ENABLER"
if [ "$ENABLER" != "$EXPECTED_SHA" ]; then
echo "::error::TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION must be the short SHA of the last commit touching turbo-ext/src/ or a PHP class listed in turbo-ext/shadowed-classes.json."
echo "::error::After changing either side, verify the implementations still match and add a follow-up commit setting the constant to that commit's short SHA (the binary bakes its version from git at build time)."
exit 1
fi
Comment thread .github/workflows/phar.yml Fixed
Comment on lines +355 to +392
turbo-artifact:
name: "Aggregate Turbo Extension Artifact"
needs: "turbo-compile"
runs-on: "ubuntu-latest"
timeout-minutes: 5

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Download extension artifacts"
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "phpstan_turbo-*"
path: "turbo-artifacts"

- name: "Arrange the distribution layout"
# phpstan_turbo-<target>-php<minor> → <target>/phpstan_turbo-<minor>.so,
# derived from the artifact names so new targets need no change here.
# The integration/extension/other test workflows in phpstan/phpstan
# download this artifact next to the phar under test.
run: |
for dir in turbo-artifacts/phpstan_turbo-*; do
name="${dir#turbo-artifacts/phpstan_turbo-}"
target="${name%-php*}"
minor="${name##*-php}"
install -D -m 644 "$dir/phpstan_turbo.so" "turbo-ext-dist/$target/phpstan_turbo-$minor.so"
done
find turbo-ext-dist -type f | sort

- name: "Upload aggregated artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: "turbo-ext-files"
path: "turbo-ext-dist"
if-no-files-found: "error"
Comment on lines +394 to +457
turbo-run:
name: "Run with Turbo Extension"
needs: "turbo-compile"
runs-on: ${{ matrix.setup.operating-system }}
timeout-minutes: 60

strategy:
fail-fast: false
matrix:
setup:
- operating-system: "ubuntu-latest"
artifact: "phpstan_turbo-linux-gnu-x86_64-php8.5"
script: ["make tests", "make phpstan"]

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
with:
coverage: "none"
php-version: "8.5"

- name: "Download extension artifact"
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: "${{ matrix.setup.artifact }}"
path: "turbo-ext"

- name: "Install extension"
run: |
sudo bash -c "echo 'extension=$GITHUB_WORKSPACE/turbo-ext/phpstan_turbo.so' >> $(php -r 'echo php_ini_loaded_file();')"
php -m | grep phpstan_turbo

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0

- name: "Verify the extension is active"
run: |
php -r '
require "src/Turbo/TurboExtensionEnabler.php";
PHPStan\Turbo\TurboExtensionEnabler::enableIfLoaded();
require "vendor/autoload.php";
if (!(new ReflectionClass("PHPStan\\Analyser\\ScopeOps"))->getParentClass() || !(new ReflectionClass("PHPStan\\Analyser\\ScopeOps"))->getParentClass()->isInternal()) {
fwrite(STDERR, "turbo extension is not shadowing ScopeOps" . PHP_EOL);
exit(1);
}
'

- name: "Run"
# The memo's per-worker footprint grows with files-per-worker; macOS
# runners have few cores, so each of their few workers analyses enough
# files to tip the default 450M limit that a many-worker Linux runner
# stays under.
env:
PHPSTAN_MEMORY_LIMIT: ${{ matrix.setup.operating-system == 'macos-latest' && '1G' || '450M' }}
run: ${{ matrix.script }}
# stays under.
env:
PHPSTAN_MEMORY_LIMIT: ${{ matrix.setup.operating-system == 'macos-latest' && '1G' || '450M' }}
run: ${{ matrix.script }}
Comment thread .github/workflows/phar.yml Fixed
turbo-run:
name: "Run with Turbo Extension"
needs: "turbo-compile"
runs-on: ${{ matrix.setup.operating-system }}
CURRENT="$(cat phpstan-dist/turbo-ext/.version 2>/dev/null || echo none)"
echo "enabler expects: $EXPECTED, dist carries: $CURRENT"
if [[ "$EXPECTED" != "$CURRENT" ]]; then
echo "result=different" >> "$GITHUB_OUTPUT"
if [[ "$EXPECTED" != "$CURRENT" ]]; then
echo "result=different" >> "$GITHUB_OUTPUT"
else
echo "result=same" >> "$GITHUB_OUTPUT"
else
echo "result=same" >> "$GITHUB_OUTPUT"
fi
echo "version=$EXPECTED" >> "$GITHUB_OUTPUT"
The turbo compile legs run in containers where the workspace volume is
owned by the host runner user while steps run as the container user;
git then refuses the repository with "detected dubious ownership" and
the Makefile's $(shell git log ...) version baking silently degrades to
"dev", failing the expected-version verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed
Comment thread .github/workflows/phar.yml Fixed

turbo-compile:
name: "Compile Turbo Extension"
runs-on: ${{ matrix.target.runs-on }}
Comment on lines +199 to +361
turbo-compile:
name: "Compile Turbo Extension"
runs-on: ${{ matrix.target.runs-on }}
# Container legs pin the built binaries' userland floor independently of
# the runner image: ubuntu:24.04 sets the glibc symbol floor (2.39 —
# Ubuntu 24.04+, Debian 13+; swap in an older container to lower it),
# alpine:3.24 builds the musl variants.
container: ${{ matrix.target.container }}
timeout-minutes: 30

env:
PHP_MINOR: ${{ matrix.php-version }}

strategy:
fail-fast: false
matrix:
php-version: ["8.4", "8.5"]
target:
- name: "linux-gnu-x86_64"
runs-on: "ubuntu-latest"
container: "ubuntu:24.04"
family: "gnu"
artifact: "phpstan_turbo"
- name: "linux-gnu-arm64"
runs-on: "ubuntu-24.04-arm"
container: "ubuntu:24.04"
family: "gnu"
artifact: "phpstan_turbo"
- name: "linux-musl-x86_64"
runs-on: "ubuntu-latest"
container: "alpine:3.24"
family: "musl"
artifact: "phpstan_turbo"
- name: "linux-musl-arm64"
runs-on: "ubuntu-24.04-arm"
container: "alpine:3.24"
family: "musl"
artifact: "phpstan_turbo"

steps:
# Container legs install PHP and tooling before checkout: actions/checkout
# needs git inside the container, and the build needs php/php-config
# symlinks pointing at the matrix PHP version.
- name: "Install PHP and build tools (Ubuntu container)"
if: matrix.target.family == 'gnu'
run: |
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y software-properties-common gnupg ca-certificates git make g++ patch zstd
add-apt-repository -y ppa:ondrej/php
apt-get install -y "php$PHP_MINOR-cli" "php$PHP_MINOR-dev" "php$PHP_MINOR-mbstring" "php$PHP_MINOR-xml" composer
ln -sf "/usr/bin/php$PHP_MINOR" /usr/local/bin/php
ln -sf "/usr/bin/php-config$PHP_MINOR" /usr/local/bin/php-config

- name: "Install PHP and build tools (Alpine container)"
if: matrix.target.family == 'musl'
run: |
V="$(echo "$PHP_MINOR" | tr -d .)"
apk add --no-cache bash git make g++ musl-dev linux-headers patch tar zstd composer \
"php$V" "php$V-dev" "php$V-ctype" "php$V-mbstring" "php$V-tokenizer" \
"php$V-iconv" "php$V-openssl" "php$V-phar" "php$V-dom" "php$V-xml" \
"php$V-xmlwriter" "php$V-simplexml"
ln -sf "/usr/bin/php$V" /usr/local/bin/php
ln -sf "/usr/bin/php-config$V" /usr/local/bin/php-config

- name: "Checkout"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# The Makefile bakes the extension version via git log over the
# watched paths; a shallow clone would resolve every build to the
# checked-out commit itself — on pull requests the synthetic merge
# commit, which matches nothing.
fetch-depth: 0

# In container legs the workspace volume is owned by the host runner
# user while steps run as the container user, so git refuses the repo
# ("dubious ownership") — which would silently bake the version as
# "dev" via the Makefile's $(shell git log ...) fallback.
- name: "Trust the checkout despite the container/host uid mismatch"
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: "Install PHP (macOS)"
if: matrix.target.family == 'macos'
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"

- name: "Cache PHP-CPP"
id: "cache-php-cpp"
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: "turbo-ext/PHP-CPP"
key: "php-cpp-${{ env.PHP_CPP_COMMIT }}-php-${{ matrix.php-version }}-${{ matrix.target.name }}-${{ matrix.target.container }}-${{ hashFiles('turbo-ext/patches/*.patch') }}"

- name: "Clone and build PHP-CPP"
if: steps.cache-php-cpp.outputs.cache-hit != 'true'
working-directory: "turbo-ext"
# `make phpcpp` applies the local patches from turbo-ext/patches/
# before building.
run: |
git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git PHP-CPP
git -C PHP-CPP checkout "$PHP_CPP_COMMIT"
ln -sfn include PHP-CPP/phpcpp
make phpcpp -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"

- name: "Compile phpstan_turbo with strict warnings"
working-directory: "turbo-ext"
# Exemptions, each caused by third-party macro expansions, not our code:
# -Wno-assume: zend's parameter-parsing macros expand __builtin_assume
# with (potential) side effects
# -Wno-unused-parameter: PHP_METHOD's fixed signature
# (execute_data/return_value are not used by every method)
# -Wno-unicode: zend arginfo macros stringify namespaced class names;
# clang lexes the \N in "PhpParser\NodeVisitor" as a universal
# character name (GCC ignores the unknown -Wno- flag)
run: |
ln -sfn include PHP-CPP/phpcpp
make WARN_FLAGS="-Wall -Wextra -Werror -Wno-assume -Wno-unused-parameter -Wno-unicode" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"

- name: "Verify the built extension reports the expected version"
run: |
REPORTED="$(php -d extension="$PWD/turbo-ext/phpstan_turbo.so" -r 'echo phpversion("phpstan_turbo");')"
EXPECTED="$(sed -n "s/.*EXPECTED_EXTENSION_VERSION = '\([^']*\)'.*/\1/p" src/Turbo/TurboExtensionEnabler.php)"
echo "built extension reports: $REPORTED, enabler expects: $EXPECTED"
[ "$REPORTED" = "$EXPECTED" ]

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
if: matrix.target.family == 'macos'

- name: "Install Composer dependencies (containers)"
if: matrix.target.family != 'macos'
# The distro composer wrapper runs under the distro-default PHP, not
# the matrix one — fine here: installing from composer.lock is
# PHP-version-independent, and platform requirements are ignored
# because they would be checked against the wrapper's PHP; the matrix
# PHP running the tests below has all the required extensions and
# fails loudly if one goes missing. (Never `php /usr/bin/composer` —
# on Alpine that is a shell script, which php would happily "run" by
# printing it and exiting 0 without installing anything.)
run: composer install --no-interaction --no-progress --ignore-platform-reqs

- name: "Smoke test (differential: native vs PHP implementations)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" turbo-ext/tests/smoke.php

- name: "Signature parity (reflect native classes against the PHP twins)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" turbo-ext/tests/signature-parity.php

- name: "Parser actions are generated (regenerate and diff)"
run: |
php turbo-ext/bin/generate-parser-actions.php
git diff --exit-code turbo-ext/src/parser/

- name: "Parser corpus (differential: native vs PHP ASTs must be byte-identical)"
run: php -d extension="$PWD/turbo-ext/phpstan_turbo.so" -d memory_limit=4G turbo-ext/tests/parser-corpus.php

- name: "Upload extension artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: "${{ matrix.target.artifact }}-${{ matrix.target.name }}-php${{ matrix.php-version }}"
path: "turbo-ext/phpstan_turbo.so"
if-no-files-found: "error"
ondrejmirtes and others added 3 commits July 14, 2026 09:25
The generated preload.php required every src and php-parser file by
path, but when the turbo extension is active the enabler has already
declared the shadowed classes through their stubs — the worker then
fataled with "Cannot redeclare class PHPStan\Analyser\
ConditionalExpressionHolder". The exclusion list comes from
turbo-ext/shadowed-classes.json, so new shadowed pairs are picked up
automatically; without the extension the skipped classes load lazily
through the autoloader.

Caught by an end-to-end run of the compiled phar with the extension
binaries laid out next to it: the analysis workers received the
-d extension= flag from the selector, activated the extension, and hit
the redeclaration on the preload path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
… images

Running the distro composer through `php` broke both distros: Alpine's
/usr/bin/composer is a shell wrapper, which php happily "executes" by
printing its text and exiting 0 — leaving no vendor directory for the
smoke test — and jammy's composer 2.2.6 fatals under PHP 8.5 in its
stream code. The step now runs the distro wrapper as intended (its own
PHP carries everything composer needs), with platform requirements
ignored since they would be checked against the wrapper's PHP rather
than the matrix PHP that runs the tests. The containers also need the
patch binary for cweagans/composer-patches — GitHub's VM runners have
it preinstalled, minimal images do not.

Containers move to ubuntu:24.04 (composer 2.7; raises the shipped gnu
binaries' glibc symbol floor to 2.39 — swap in an older container to
lower it again) and alpine:3.24, and the PHP-CPP cache key includes the
container image so an image bump cannot reuse a stale build.

Verified by replicating both container legs locally end to end:
packages, composer install with the patches applied, PHP-CPP + strict
extension build, smoke and signature parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
…4 leg

Back on jammy the shipped gnu binaries keep their glibc 2.34 symbol
floor (RHEL 9, Ubuntu 22.04+, Debian 12+), but its distro composer
2.2.6 fatals under PHP 8.5 — so the container legs now download a
checksum-pinned composer.phar and run it under the matrix PHP, which
also restores proper platform-requirements checking. The containers
additionally need unzip (jammy; without it composer falls back to
git-source installs and fails on pinned commits — Alpine's busybox
ships one) next to the patch binary — tools GitHub's VM runners
preinstall but minimal images lack.

The linux-musl-arm64 leg is removed: JavaScript-based actions cannot
run in Alpine containers on arm64 runners, where the runner only ships
an x64 musl Node. The selector treats the missing binary as "no
extension available".

Both container legs were replicated locally end to end (packages,
pinned composer install with patches applied, PHP-CPP + strict build,
smoke, signature parity); the jammy binary's maximum required symbol
version is GLIBC_2.34.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j6ePxd1BobZkr8YVLnNet
fetch-depth: 0 # git log over turbo-ext/src needs full history

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2

- name: "Install PHP (macOS)"
if: matrix.target.family == 'macos'
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
echo "built extension reports: $REPORTED, enabler expects: $EXPECTED"
[ "$REPORTED" = "$EXPECTED" ]

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240" # v2.37.2
sudo bash -c "echo 'extension=$GITHUB_WORKSPACE/turbo-ext/phpstan_turbo.so' >> $(php -r 'echo php_ini_loaded_file();')"
php -m | grep phpstan_turbo

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
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