Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 76 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
11 changes: 8 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 8 additions & 4 deletions tools/docker/php-debug.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'; \
Expand Down