diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 312ec3f..ca4be7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,22 +6,26 @@ on: - main pull_request: -env: - # Engine structures are version-specific; this package tracks one PHP minor at a time, - # exactly like the z-engine branch it builds on. - PHP_MINOR: '8.4' - +# Engine structures are version-specific and z-engine tracks one PHP minor per +# release line, but the "8.4.x-dev || 8.5.x-dev" constraint lets composer +# resolve the line matching each runner's PHP - so this package runs on +# PHP 8.4 and 8.5 in parallel, and every engine-touching job runs against +# both minors. jobs: tests: - name: Tests (release, NTS) + name: Tests (release, NTS, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off coverage: none @@ -32,15 +36,19 @@ jobs: run: composer test tests-opcache: - name: Tests (opcache, save_comments=0) + name: Tests (opcache, save_comments=0, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi # save_comments=0 is a normal production setting that makes doc comments # unavailable at runtime; this job is the permanent guard that nothing in the @@ -63,15 +71,19 @@ jobs: composer test:preload benchmarks-smoke: - name: Benchmarks (smoke) + name: Benchmarks (smoke, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off coverage: none @@ -84,8 +96,12 @@ jobs: run: composer bench:smoke analysis-tests: - name: PHPStan extension tests (FFI disabled) + name: PHPStan extension tests (FFI disabled, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 @@ -96,7 +112,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} ini-values: ffi.enable=0 coverage: none @@ -105,16 +121,46 @@ jobs: - name: Run the analysis suite run: composer test:analysis + # The analysis layer needs no engine, so it is the one thing that can (and should) + # already run on the NEXT minor: an early warning for parser/reflection changes, not + # a support statement. The engine-touching jobs stay off nightly until z-engine + # bundles definitions for it. + analysis-tests-nightly: + name: PHPStan extension tests (PHP 8.6 nightly, experimental) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v7 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.6' + ini-values: ffi.enable=0 + coverage: none + + # z-engine's platform constraint stops at the minors it has definitions for; the + # analysis suite never boots it, so installing past the constraint is safe here. + - name: Install dependencies (ignore the PHP platform requirement) + run: composer install --no-interaction --no-progress --ignore-platform-req=php+ + + - name: Run the analysis suite + run: composer test:analysis + generated-stubs: - name: Generated stubs are up to date + name: Generated stubs are up to date (PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1 coverage: none @@ -123,13 +169,17 @@ jobs: # The project analyses itself with stubs its own generator produced. Regenerating here # makes a generator change that stops matching them a failing build rather than a stale - # file nobody re-runs. + # file nobody re-runs. Running it per minor proves the stubs are version-independent. - name: Regenerate and compare run: composer stubs:check tests-internal-debug: - name: Internal group (debug build) + name: Internal group (debug build, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] # The destructive eviction tests run destroy_zend_class() over a copied class entry while # its template is still live. On a release build a copy that released a block the template # still owns usually survives the test and crashes somewhere unrelated later; on a @@ -149,7 +199,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi coverage: none @@ -159,7 +209,7 @@ jobs: run: | docker build \ -f tools/docker/php-debug.Dockerfile \ - --build-arg "PHP_VERSION=${PHP_MINOR}" \ + --build-arg "PHP_VERSION=${{ matrix.php }}" \ -t generics-php:debug . - name: Confirm the image really is a debug build @@ -188,15 +238,19 @@ jobs: || { echo "::error::${skipped} destructive test(s) skipped - the debug container cannot reach the engine"; exit 1; } static-analysis: - name: PHPStan (level max) + name: PHPStan (level max, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1 coverage: none @@ -215,7 +269,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: '8.5' coverage: none - uses: ramsey/composer-install@v4 diff --git a/AGENTS.md b/AGENTS.md index b29e56a..5947dca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,9 +79,14 @@ needs a reason, not a refactor. Each is explained in full in the README. ## 1. Version matching is still non-negotiable -Engine struct layouts are version-specific. This package tracks **one PHP minor at a time**, the same -one the z-engine branch it depends on tracks. Never run the suite against a different minor, and never -try to defeat `Core::init()`'s guard. +Engine struct layouts are version-specific, and z-engine tracks **one PHP minor per release +line** (`8.4` branch for PHP 8.4, `master` for PHP 8.5). This package supports **PHP 8.4 and +8.5 in parallel** by declaring z-engine as `8.4.x-dev || 8.5.x-dev`, so composer resolves the +line matching the running platform — z-engine owns all version-dependent header complexity, +and CI exercises the whole matrix. The rule is unchanged: never run the suite on a minor the +resolved z-engine does not target, and never try to defeat `Core::init()`'s guard. +PHP 8.6 nightly may `composer install` (the analysis layer works there), but nothing that +touches the engine is supported until a z-engine line for 8.6 exists. Running anything that touches the engine requires: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2e5416..0086c03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,10 @@ about the engine itself. ## Before you start -- **Match your PHP version to the branch.** Engine struct layouts are version-specific; this package - tracks one PHP minor at a time. +- **Run only supported PHP minors.** Engine struct layouts are version-specific; z-engine tracks + one minor per release line, and the `8.4.x-dev || 8.5.x-dev` constraint makes composer resolve + the line matching your PHP, so this package supports both minors in parallel. Anything newer + (8.6 nightly) installs but cannot touch the engine yet. - **Develop against a debug build** (`--enable-debug`, FFI on). It turns silent memory corruption into loud assertion failures. - FFI must be enabled (`ffi.enable=1`) and the JIT disabled (`opcache.jit=off`). @@ -48,7 +50,7 @@ PHP build, so you can work on the PHPStan extension anywhere. ## Pull request checklist -- [ ] `composer test` passes on the matching PHP minor +- [ ] `composer test` passes on every supported PHP minor (CI runs 8.4 and 8.5) - [ ] `composer phpstan` and `composer cs:check` are green - [ ] `composer stubs:check` is green if you touched a template or the generator - [ ] tests added or updated, with unique specialized class names diff --git a/README.md b/README.md index 0f938bf..ed0ea93 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ real class whose `int` is enforced by the Zend Engine itself — the same `TypeError` you get from a hand-written class, on a class that did not exist a microsecond ago.** -[![PHP Version](https://img.shields.io/badge/php-8.4-8892BF.svg)](https://www.php.net/) +[![PHP Version](https://img.shields.io/badge/php-8.4%20%7C%208.5-8892BF.svg)](https://www.php.net/) [![License](https://img.shields.io/packagist/l/lisachenko/userland-php-generics.svg)](LICENSE) [![PHPStan](https://img.shields.io/badge/PHPStan-level%20max-brightgreen.svg)](https://phpstan.org/) @@ -208,10 +208,13 @@ autoloader is a side effect a library should not impose. ## Requirements and installation -- PHP 8.4, NTS, x86-64 +- PHP 8.4 or 8.5 (supported in parallel), NTS, x86-64 - `ext-ffi` with `ffi.enable=1` - `opcache.jit=off` (the JIT rewrites the executor internals z-engine hooks into) -- [`lisachenko/z-engine`](https://github.com/lisachenko/z-engine) on the matching branch +- [`lisachenko/z-engine`](https://github.com/lisachenko/z-engine) — the + `8.4.x-dev || 8.5.x-dev` constraint lets composer resolve the z-engine line matching your + PHP minor (the `8.4` branch on PHP 8.4, `master` on PHP 8.5); z-engine owns all + version-dependent header complexity, so this package never deals with it ```bash composer require --dev lisachenko/userland-php-generics diff --git a/composer.json b/composer.json index 646fae2..704f650 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ } ], "require": { - "php": "~8.4.0", + "php": "^8.4", "ext-ffi": "*", - "lisachenko/z-engine": "~8.4.0" + "lisachenko/z-engine": "8.4.x-dev || 8.5.x-dev" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.75", diff --git a/tools/docker/php-debug.Dockerfile b/tools/docker/php-debug.Dockerfile index 8419c51..55b6594 100644 --- a/tools/docker/php-debug.Dockerfile +++ b/tools/docker/php-debug.Dockerfile @@ -67,14 +67,18 @@ RUN rm -f /usr/local/etc/php/conf.d/docker-php-ext-*.ini # explicitly in the child processes that exercise shared memory - which is where # a debug build turns the shared-memory corruption of issue #41 into a loud # zend_function_dtor() assertion instead of a silent wrong result. -# opcache is a shared zend_extension here (the debug build installs its own -# ABI-tagged extension directory), so it is loaded by absolute path - a bare +# Up to PHP 8.4 opcache is a shared zend_extension (the debug build installs +# its own ABI-tagged extension directory), loaded by absolute path - a bare # file name resolves against whatever extension_dir the base image left behind. +# Since PHP 8.5 opcache is linked statically into the binary: no opcache.so +# exists and no zend_extension line is needed. The sanity check below asserts +# the extension is present either way. RUN set -eux; \ extension_dir="$(php-config --extension-dir)"; \ - test -f "${extension_dir}/opcache.so"; \ { \ - echo "zend_extension=${extension_dir}/opcache.so"; \ + if [ -f "${extension_dir}/opcache.so" ]; then \ + echo "zend_extension=${extension_dir}/opcache.so"; \ + fi; \ echo 'ffi.enable=1'; \ echo 'zend.assertions=1'; \ echo 'report_memleaks=1'; \