Universal automated Windows builds of PHP — any version, any php/php-src ref — plus a patched Imagick extension that follows whichever PHP you build.
Thread Safe (ZTS) for Apache mod_php / XAMPP. x64 + x86, each also as an AVX2-optimized build.
History. This repository started as
php86-windows-buildsand tracked the PHP 8.6.0-dev master snapshots (see the INIT 8.6 section). On 2026-09-26,php/php-srcmaster bumped to 8.7.0-dev after thePHP-8.6release branch was cut, so the repository was renamed tophp-windows-buildsand made version-agnostic end to end. Old URLs (git remote, releases, API) redirect automatically.
The build-php-dev-latest workflow builds a complete PHP distribution from whatever ref you point it at:
php_ref input |
What you get |
|---|---|
master (default) |
the next dev version — currently 8.7.0-dev |
any release branch — e.g. PHP-8.6 |
that branch (dev snapshots + its release candidates) |
any tag — e.g. php-8.6.0RC2 |
that exact release |
| any commit SHA | a reproducible snapshot |
The version label for artifacts and releases is auto-detected from the source that is actually built (main/php_version.h; the php_version input defaults to auto). A build can therefore never be mislabeled — the release job derives its tag, title and asset table from build metadata, not from a hardcoded string.
| Architecture | Optimization |
|---|---|
| x64 | Standard |
| x86 | Standard |
| x64-AVX2 | AVX2 (Intel Haswell 2013+, AMD Ryzen 2017+) |
| x86-AVX2 | AVX2 |
php.exe,php-cgi.exe(CLI + FastCGI)php8ts.dll(Thread Safe engine),php8apache2_4.dll(Apache 2.4 module)ext/*.dll— 40+ extensions (mysqli, opcache, curl, gd, intl, mbstring, soap, xsl, sockets, …)- dependency DLLs (libcrypto, libssl, icu, brotli)
php.ini-development/php.ini-production- devel pack (x64): headers,
.libfiles,phpize.bat— for building PECL extensions
Compiler: Visual Studio 2024 (VC18) on a windows-2025 runner.
Build type: Thread Safe (ZTS) — Apache mod_php / XAMPP.
- Download
x64-AVX2.zipfor best performance on modern CPUs - Stop Apache
- Replace
php8ts.dll,php8apache2_4.dll,php.exe,ext/inC:\xampp\php\ - Start Apache
- Verify:
C:\xampp\php\php.exe -v
Trigger: Actions → Build PHP TS Windows VS18 (x86 + x64 + AVX2, any php/php-src ref) → Run workflow.
The build-imagick workflow builds php_imagick.dll (x64, Thread Safe + Non-Thread Safe, each ± AVX2) against any PHP this repository provides:
-
TS variant — compiled against a PHP release from this repository, selected by the
php_releaseinput:latest(default) — the newestphp-*release- a series, e.g.
8.6or8.7— the newest release of that series - an exact tag, e.g.
php-8.6.0RC2
Artifact and release labels are derived from the release that was actually used, so they can never lie.
-
NTS variant — compiled against the official windows.php.net QA build selected by
qa_version(default8.6.0RC3), labeled accordingly.
If the TS and NTS variants end up on different PHP series, the release is published as an explicit multi-version release and every asset carries its own version in the filename.
PHP 8.6 turned zend_is_callable() from an exported API function into a non-exported static zend_always_inline function. Every Imagick DLL built for PHP ≤ 8.5 imports zend_is_callable directly and dies on startup with:
The procedure entry point
zend_is_callablecould not be located in the dynamic link libraryphp8ts.dll.
The vendored, pre-patched Imagick source (vendor/imagick-patched.zip) switches the call to zend_is_callable_ex, which is still an exported ZEND_API function — one fix for every PHP from 8.6 onward (8.6, 8.7, …). The workflow fail-fast-verifies the patch is present before building.
PHP checks ZEND_MODULE_API_NO when an extension is loaded, so headers, import libs and the runtime must all come from the same PHP build. The TS job therefore uses the official QA pack only as a tooling base (script/, phpize.bat, win32/ extras) and overlays both include/ and lib/ from the selected own-repo devel pack — headers and php8ts.lib always match the exact binary being built against, for any series. The built-in load test (php.exe -d extension=php_imagick.dll) fails loudly if anything ever mismatches.
Trigger: Actions → Build Imagick PHP VS18 x64 (from own releases) → Run workflow.
The build-mysql workflow builds php_mysql.dll — the classic mysql_* API from PHP 3-5 (removed from the core in PHP 7, maintained as php/pecl-database-mysql) — x64, Thread Safe + Non-Thread Safe, following whichever PHP you build, exactly like Imagick:
- TS variant — compiled against a PHP release from this repository, selected by the
php_releaseinput (latest(default) / a series like8.6or8.7/ an exact tag) - NTS variant — compiled against the official php.net QA build selected by
qa_version(default8.6.0RC3), labeled accordingly
No AVX2 variants: php_mysql.dll is pure C and ABI-identical for the AVX2 and standard builds of a series (php.net also ships its PECL extension DLLs per TS/NTS only) — one DLL per variant covers both.
PHP 8.7 removed the mysqlnd reverse API (php/php-src PR #21409, commit 58b4cc2df0, 2026-09-30). Every php_mysql.dll built against PHP ≤ 8.6 imports mysqlnd_reverse_api_register_api from php8ts.dll and dies at load time on PHP 8.7 with:
httpd.exe - Entry Point Not FoundThe procedure entry point mysqlnd_reverse_api_register_api could not be located in the dynamic link library …\ext\php_mysql.dll
— taking Apache down with it (and unpatched source does not even compile for 8.7: the header is gone).
The registration only existed so mysqli / PDO_MYSQL could accept foreign mysql_* connection resources via zval_to_mysqlnd(); those consumers dropped the feature upstream in the same commit, so skipping it on 8.7+ is lossless — ext/mysql keeps working through its own resource handling.
On top of that, current php-src (both the PHP-8.6 branch and master) also removed zval_dtor() and zend_exception_get_default() from the public API and changed php_get_current_user() to return a zend_string* — unpatched pecl-database-mysql master does not compile against current 8.6 or 8.7 at all.
The vendored, pre-patched source (vendor/mysql-patched.zip) fixes all four issues:
| Upstream change | Patch |
|---|---|
8.7 removed the mysqlnd reverse API (mysqlnd_reverse_api_register_api et al.) |
registration guarded with #if PHP_VERSION_ID < 80700 (kept for 8.6, skipped on 8.7+) |
zval_dtor() removed from the public API (8.6 + 8.7) |
zval_ptr_dtor() — correct for a value we own |
zend_exception_get_default() removed (8.6 + 8.7) |
zend_ce_exception — direct class-entry access |
php_get_current_user() now returns zend_string* (8.6 + 8.7) |
ZSTR_VAL(...) — the result is SAPI-cached for the request, so borrowing is safe |
— one source builds unchanged for 8.6 and 8.7+ (verified locally by compiling against a fresh php-src master build: zero errors, clean load, no mysqlnd_reverse_api_* imports). The workflow fail-fast-verifies all fixes are present before compiling and, for PHP ≥ 8.7 targets, additionally verifies via dumpbin that the produced DLL imports no mysqlnd_reverse_api_* symbol — the exact import that crashed Apache.
Trigger: Actions → Build MySQL extension PHP VS18 x64 (from own releases) → Run workflow.
| Workflow | Builds | Output |
|---|---|---|
build-php-dev-latest |
full PHP distro, any php/php-src ref, version auto-detected | GitHub Release (zips + devel pack) |
build-imagick |
patched php_imagick.dll — TS from own releases, NTS from official QA |
GitHub Release (zips) |
build-mysql |
patched php_mysql.dll (legacy ext/mysql) — TS from own releases, NTS from official QA |
GitHub Release (zips) |
- PHP source: php/php-src — any ref via
php_ref - PHP SDK: php/php-sdk-binary-tools
- Imagick source: Imagick/imagick — patched, vendored
- Ubuntu .deb builds: markusfoo/php-apt-builder
- PHP 8.7 removed the mysqlnd reverse API (php/php-src PR #21409, commit
58b4cc2df0, 2026-09-30). Everyphp_mysql.dllcompiled against PHP ≤ 8.6 importsmysqlnd_reverse_api_register_apifromphp8ts.dlland kills Apache at startup on 8.7 with "Entry Point Not Found" (observed live on XAMPP-Lite 8.7.0). Newbuild-mysql.yaml+ vendored patched source (vendor/mysql-patched.zip) fix this exactly the way Imagick was fixed for 8.6. The patch covers FOUR upstream API removals: the 8.7 mysqlnd reverse-API removal (registration version-guarded with#if PHP_VERSION_ID < 80700), pluszval_dtor()→zval_ptr_dtor(),zend_exception_get_default()→zend_ce_exceptionand thezend_string*return ofphp_get_current_user()— the latter three removed/changed in the PHP-8.6 branch as well, so unpatched pecl-database-mysql master compiles against current neither 8.6 nor 8.7. Verified locally against a fresh php-src master (8.7.0-dev) build before pushing; TS builds against own releases, NTS against the official php.net QA build, with fail-fast patch verification and — for PHP ≥ 8.7 targets — adumpbincheck that the DLL imports nomysqlnd_reverse_api_*symbol. No AVX2 variants: the DLL is pure C and ABI-identical across the AVX2/standard PHP builds of a series (php.net ships PECL DLLs per TS/NTS only). build-imagick.yamldefaultqa_versionbumped8.6.0RC2→8.6.0RC3— php.net deleted the RC2 QA packages, so the old default would 404 on a fresh run.- README: documented the MySQL extension; fixed the broken php-apt-builder link.
php/php-srcmaster bumped to 8.7.0-dev after thePHP-8.6release branch was cut, which made the old repository name (php86-windows-builds) and the version-coupled workflow inaccurate. The repository is renamed tophp-windows-builds; all old URLs redirect automatically.build-php-dev-latest.yaml(renamed frombuild-php86-dev-latest.yaml) is fully version-agnostic:- new
php_refinput — build any php/php-src ref:master, a release branch, a tag, or a SHA - new
php_version=autodefault — the version label is auto-detected frommain/php_version.hof the checked-out source; the release job consumes build metadata via abuild-metaartifact, so tag/title/asset table are all derived from what was actually built - validated by the first universal run: release
php-8.7.0-dev-2026-09-26— correctly labeled 8.7.0-dev (the same-dayphp-8.6.0-dev-2026-09-26snapshot was 8.7.0-dev content under an 8.6 name and was removed in its favor)
- new
build-imagick.yaml— per-variant, honest versioning:- the
php_versionnaming-only input was removed; the newphp_releaseinput (latest/ a series like8.6/ an exact tag) selects which own-repo release the TS variant builds against, and TS artifact names are derived from the release actually used - NTS artifact names derive from
qa_version(default moved to the current php.net QA cycle:8.6.0RC2, previously8.6.0beta2) - the TS job now overlays the own devel pack's
include/on the official QA base (in addition tolib/), because PHP checksZEND_MODULE_API_NOat extension load (8.6 =20260924, 8.7.0-dev =20260925) — a DLL compiled against another series' headers would fail to load - the release tag/name/asset table/version line are generated from the assets that were actually built; mixed TS/NTS series produce an explicit multi-version release
- own-releases API URL updated to the new repo name; vendored source renamed to
vendor/imagick-patched.zip; job names and banners made version-agnostic
- the
- README rewritten to be fully version-agnostic (version specifics now live only in this changelog); stale
php86-apt-builderlink fixed →php-apt-builder
The repository was created to track the PHP 8.6.0-dev master snapshots:
- TS (ZTS) VS18 Windows builds on
windows-2025runners — x64 + x86, each also as an AVX2 variant (--enable-native-intrinsics=avx2), XAMPP-ready (php8ts.dll+php8apache2_4.dll), with a devel pack (headers +.lib+phpize.bat) for building PECL extensions - Releases:
v1.0.0-php8.6.0beta1,php-8.6.0-dev-2026-08-19,php-8.6.0-dev-2026-08-27, plusimagick-php8.6-vs18-x64-*extension releases. Aphp-8.6.0-dev-2026-09-26snapshot was also published, but master had already moved to 8.7.0-dev by then — that mislabel is what triggered the universalization above (the release was later removed) - Patched Imagick for the PHP 8.6 API change (
zend_is_callableno longer exported →zend_is_callable_ex), with a hard fail-fast check that the patch is present in the vendored source before building - Imagick build matrix extended to TS + NTS × ± AVX2, with the ImageMagick headers/libs layout fixed to match
config.w32expectations and the official QA devel pack used as thescript/source