Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f66e687
fable output
henderkes Jun 14, 2026
5a5c784
fix(frankenphp): strip Go's -mthreads so the Windows build survives C…
m-this Jul 7, 2026
83ce54b
docs(frankenphp): link golang/go#80290 so the -mthreads workaround ca…
Jul 8, 2026
5ffbf2d
fix(frankenphp): strip Go's -mthreads so the Windows build survives C…
crazywhalecc Jul 8, 2026
f1722a0
Merge branch 'v3' into fable-v3-windows
henderkes Jul 8, 2026
ab30c9d
fix: update Visual Studio version handling in imagemagick configuration
crazywhalecc Jul 8, 2026
323f21a
Add gdi32.lib
crazywhalecc Jul 8, 2026
1dce506
fix(postgresql): provision meson, ninja and winflexbison on windows
m-this Jul 8, 2026
f3afd81
fix(php): pass /FORCE:MULTIPLE when linking php-cgi.exe and micro.sfx
m-this Jul 8, 2026
6dc5c5b
fix(pgsql): link secur32 and crypt32 on windows
m-this Jul 8, 2026
43b2da0
fix(libiconv): only put the static lib on the windows link line
m-this Jul 8, 2026
d7902b9
feat(tool): bundle python for the meson build
m-this Jul 8, 2026
303dca5
Remove doctor item
crazywhalecc Jul 9, 2026
1509987
Fix windows captainhook
crazywhalecc Jul 9, 2026
0bd94b0
Lint
crazywhalecc Jul 9, 2026
2747060
Fix frankenphp package path bug
crazywhalecc Jul 9, 2026
859c06a
Fix the Windows extension test failures on fable-v3-windows (#1208)
crazywhalecc Jul 9, 2026
6c7a47c
Trigger full test when containing 'need-full-test' tag
crazywhalecc Jul 9, 2026
be8f239
Merge remote-tracking branch 'origin/fable-v3-windows' into fable-v3-…
crazywhalecc Jul 9, 2026
7edd835
Enhance artifact extraction logic to check for existing files and han…
crazywhalecc Jul 9, 2026
dca2458
Add frankenphp test label
crazywhalecc Jul 9, 2026
d04d82a
Fix strawberry-perl detect time
crazywhalecc Jul 9, 2026
79db27d
Merge branch 'v3' into fable-v3-windows
crazywhalecc Jul 9, 2026
4dac0d1
Fix windows file delete permissions
crazywhalecc Jul 9, 2026
44daaad
Resolve xlswriter dllimport issue
crazywhalecc Jul 9, 2026
e99423c
Unify GOPATH with go-win custom binary extraction method
crazywhalecc Jul 9, 2026
4d6f334
Refactor UPX patching logic for phpmicro binary
crazywhalecc Jul 9, 2026
c54fa78
fix(windows): survive cmd.exe's 8191 character command line limit
m-this Jul 9, 2026
6d58d53
fix(imagemagick): build the VisualMagick tree under source/ on windows
m-this Jul 10, 2026
6144f7a
fix(windows): survive cmd.exe's 8191 character command line limit (#1…
crazywhalecc Jul 10, 2026
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
5 changes: 3 additions & 2 deletions .github/workflows/ext-matrix-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ name: "Extension Matrix Tests"

on:
workflow_dispatch:
push:
pull_request:
types: [opened, synchronize, labeled]

jobs:
test:
name: "${{ matrix.extension }} (PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }})"
runs-on: ${{ matrix.operating-system }}
if: contains(github.event.head_commit.message, 'extension test') || contains(github.event.head_commit.message, 'test extensions')
if: contains(github.event.pull_request.labels.*.name, 'need-full-test')
strategy:
fail-fast: false
matrix:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ jobs:
echo "matrix=$(echo "$FINAL" | jq -c '{"combo": .}')" >> "$GITHUB_OUTPUT"

ext-test:
name: "Ext test: ${{ matrix.combo.extension }} (PHP ${{ matrix.combo.php-version }} · ${{ matrix.combo.os }}-${{ matrix.combo.arch }})"
name: "Ext test: ${{ matrix.combo.extension }} [${{ matrix.combo.sapi }}] (PHP ${{ matrix.combo.php-version }} - ${{ matrix.combo.os }}-${{ matrix.combo.arch }})"
needs: gen-matrix
runs-on: ${{ matrix.combo.runner }}
timeout-minutes: 120
Expand Down Expand Up @@ -266,5 +266,5 @@ jobs:
if: always() && hashFiles('log/**') != ''
uses: actions/upload-artifact@v4
with:
name: logs-${{ matrix.combo.os }}-${{ matrix.combo.arch }}-${{ matrix.combo.extension }}-php${{ matrix.combo.php-version }}
name: logs-${{ matrix.combo.os }}-${{ matrix.combo.arch }}-${{ matrix.combo.sapi }}-${{ matrix.combo.extension }}-php${{ matrix.combo.php-version }}
path: log
2 changes: 1 addition & 1 deletion captainhook.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
]
},
{
"action": "bin/spc dev:lint-config --check",
"action": "php bin/spc dev:lint-config --check",
"conditions": [
{
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\InDirectory",
Expand Down
1 change: 1 addition & 0 deletions config/pkg/ext/ext-imagick.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ ext-imagick:
os:
- Linux
- Darwin
- Windows
arg-type: custom
46 changes: 46 additions & 0 deletions config/pkg/lib/imagemagick.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,59 @@ imagemagick:
- libtiff
- libheif
- bzip2
depends@windows:
- zlib
suggests:
- zstd
- xz
- libzip
- libxml2
headers@windows:
- imagemagick/MagickWand/MagickWand.h
lang: cpp
pkg-configs:
- Magick++-7.Q16HDRI
- MagickCore-7.Q16HDRI
- MagickWand-7.Q16HDRI
static-libs@windows:
- CORE_RL_MagickWand_.lib
- CORE_RL_MagickCore_.lib
- CORE_RL_coders_.lib
- CORE_RL_filters_.lib
- CORE_RL_aom_.lib
- CORE_RL_brotli_.lib
- CORE_RL_bzip2_.lib
- CORE_RL_cairo_.lib
- CORE_RL_croco_.lib
- CORE_RL_de265_.lib
- CORE_RL_exr_.lib
- CORE_RL_ffi_.lib
- CORE_RL_freetype_.lib
- CORE_RL_fribidi_.lib
- CORE_RL_gdk-pixbuf_.lib
- CORE_RL_glib_.lib
- CORE_RL_harfbuzz_.lib
- CORE_RL_heif_.lib
- CORE_RL_highway_.lib
- CORE_RL_imath_.lib
- CORE_RL_jpeg-turbo-12_.lib
- CORE_RL_jpeg-turbo-16_.lib
- CORE_RL_jpeg-turbo_.lib
- CORE_RL_jpeg-xl_.lib
- CORE_RL_lcms_.lib
- CORE_RL_lqr_.lib
- CORE_RL_lzma_.lib
- CORE_RL_openh264_.lib
- CORE_RL_openjpeg_.lib
- CORE_RL_openjph_.lib
- CORE_RL_pango_.lib
- CORE_RL_pixman_.lib
- CORE_RL_png_.lib
- CORE_RL_raqm_.lib
- CORE_RL_raw_.lib
- CORE_RL_rsvg_.lib
- CORE_RL_tiff_.lib
- CORE_RL_webp_.lib
- CORE_RL_xml_.lib
- CORE_RL_zip_.lib
- CORE_RL_zlib_.lib
1 change: 0 additions & 1 deletion config/pkg/lib/libiconv-win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ libiconv-win:
license-files: [source/COPYING]
license: GPL-3.0-or-later
static-libs@windows:
- libiconv.lib
- libiconv_a.lib
4 changes: 4 additions & 0 deletions config/pkg/lib/libzip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ libzip:
license: BSD-3-Clause
depends:
- zlib
depends@windows:
- zlib
- bzip2
- xz
suggests:
- bzip2
- xz
Expand Down
1 change: 1 addition & 0 deletions config/pkg/lib/openssl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ openssl:
- libcrypto.lib
tools@windows:
- jom
- nasm
- strawberry-perl
11 changes: 9 additions & 2 deletions config/pkg/lib/postgresql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ postgresql:
type: ghtagtar
repo: postgres/postgres
match: REL_18_\d+
binary:
windows-x86_64: { type: url, url: 'https://get.enterprisedb.com/postgresql/postgresql-16.8-1-windows-x64-binaries.zip', extract: { lib/libpq.lib: '{build_root_path}/lib/libpq.lib', lib/libpgport.lib: '{build_root_path}/lib/libpgport.lib', lib/libpgcommon.lib: '{build_root_path}/lib/libpgcommon.lib', include/libpq-fe.h: '{build_root_path}/include/libpq-fe.h', include/postgres_ext.h: '{build_root_path}/include/postgres_ext.h', include/pg_config_ext.h: '{build_root_path}/include/pg_config_ext.h', include/libpq/libpq-fs.h: '{build_root_path}/include/libpq/libpq-fs.h' } }
metadata:
license-files: ['@/postgresql.txt']
license: PostgreSQL
Expand All @@ -16,6 +14,9 @@ postgresql:
- openssl
- zlib
- libedit
depends@windows:
- openssl
- zlib
suggests@unix:
- icu
- libxslt
Expand All @@ -27,3 +28,9 @@ postgresql:
- libpq.lib
- libpgport.lib
- libpgcommon.lib
tools@windows:
- python-win
- meson
- ninja
- winflexbison
- strawberry-perl
2 changes: 1 addition & 1 deletion config/pkg/tool/go-win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go-win:
env:
GOROOT: '{pkg_root_path}/go-win'
GOBIN: '{pkg_root_path}/go-win/bin'
GOPATH: '{pkg_root_path}/go-win/go'
GOPATH: '{pkg_root_path}/go-win/gopath'
path@windows:
- '{pkg_root_path}/go-win/bin'
tool:
Expand Down
11 changes: 11 additions & 0 deletions config/pkg/tool/meson.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meson:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://github.com/mesonbuild/meson/releases/download/1.11.1/meson-1.11.1-py3-none-any.whl', extract: '{pkg_root_path}/meson/meson-1.11.1-py3-none-any.whl' }
path@windows:
- '{pkg_root_path}\meson'
tool:
provides:
- meson.bat
binary-subdir: meson
9 changes: 9 additions & 0 deletions config/pkg/tool/ninja.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ninja:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-win.zip', extract: { ninja.exe: '{pkg_root_path}/bin/ninja.exe' } }
tool:
provides:
- ninja.exe
binary-subdir: bin
8 changes: 8 additions & 0 deletions config/pkg/tool/python-win.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
python-win:
type: tool
artifact:
binary: custom
tool:
provides:
- python.exe
binary-subdir: python-win/tools
12 changes: 12 additions & 0 deletions config/pkg/tool/winflexbison.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
winflexbison:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip', extract: '{pkg_root_path}/winflexbison' }
path@windows:
- '{pkg_root_path}\winflexbison'
tool:
provides:
- win_flex.exe
- win_bison.exe
binary-subdir: winflexbison
48 changes: 48 additions & 0 deletions src/Package/Artifact/meson_win.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Package\Artifact;

use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Exception\EnvironmentException;
use StaticPHP\Util\FileSystem;

class meson_win
{
/**
* Meson 1.11 dropped the Windows MSI, so the artifact is the wheel, installed offline
* into a private venv. The venv is not just packaging hygiene: pip generates a real
* meson.exe launcher there, and PostgreSQL needs one because its build re-invokes meson
* through find_program(), which rejects non-executables like the meson.pyz zipapp.
*/
#[AfterBinaryExtract('meson', ['windows-x86_64'])]
public function afterExtract(string $target_path): void
{
$dir = dirname($target_path);
$venv = "{$dir}\\venv";

// Prefer the python-win tool package (installed alongside via tools@windows),
// fall back to whatever Python the machine already has.
$candidates = ['"' . PKG_ROOT_PATH . '\python-win\tools\python.exe"', 'python', 'py -3'];
$python = null;
foreach ($candidates as $candidate) {
[$code] = cmd()->execWithResult("{$candidate} --version", false);
if ($code === 0) {
$python = $candidate;
break;
}
}
if ($python === null) {
throw new EnvironmentException('meson needs Python 3; the python-win tool package did not install and no system Python was found.');
}

if (is_dir($venv)) {
FileSystem::removeDir($venv);
}
cmd()->exec("{$python} -m venv \"{$venv}\"")
->exec("\"{$venv}\\Scripts\\python.exe\" -m pip install --no-index --no-deps \"{$target_path}\"");

FileSystem::writeFile("{$dir}\\meson.bat", "@echo off\r\n\"%~dp0venv\\Scripts\\meson.exe\" %*\r\n");
}
}
66 changes: 66 additions & 0 deletions src/Package/Artifact/python_win.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Package\Artifact;

use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\DownloaderException;

/**
* Full CPython for Windows from the official python.org nuget package. It is a plain zip
* (python.exe plus stdlib under tools/, venv and ensurepip included), so it installs into
* the pkgroot without touching the system. Used by the meson tool package.
*/
class python_win
{
#[CustomBinary('python-win', [
'windows-x86_64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$version = $this->getLatestVersion($downloader->getRetry());

$url = "https://api.nuget.org/v3-flatcontainer/python/{$version}/python.{$version}.nupkg";
// .nupkg is a zip; name the cache file .zip so the extractor treats it as one
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . "python-win-{$version}.zip";
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());

return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: '{pkg_root_path}/python-win', version: $version);
}

#[CustomBinaryCheckUpdate('python-win', ['windows-x86_64'])]
public function checkUpdateBinary(?string $old_version): CheckUpdateResult
{
$version = $this->getLatestVersion();
return new CheckUpdateResult(
old: $old_version,
new: $version,
needUpdate: $old_version === null || $version !== $old_version,
);
}

#[AfterBinaryExtract('python-win', ['windows-x86_64'])]
public function afterExtract(string $target_path): void
{
if (!file_exists("{$target_path}\\tools\\python.exe")) {
throw new DownloaderException("Python installation appears incomplete: python.exe not found at {$target_path}\\tools\\python.exe");
}
}

private function getLatestVersion(int $retries = 0): string
{
$index = default_shell()->executeCurl('https://api.nuget.org/v3-flatcontainer/python/index.json', retries: $retries);
$versions = $index ? (json_decode($index, true)['versions'] ?? []) : [];
$stable = array_filter($versions, fn ($v) => preg_match('/^\d+\.\d+\.\d+$/', $v));
if ($stable === []) {
throw new DownloaderException('Failed to get Python versions from the nuget index');
}
return end($stable);
}
}
38 changes: 37 additions & 1 deletion src/Package/Extension/imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

namespace Package\Extension;

use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\CustomPhpConfigureArg;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\FileSystem;

#[Extension('imagick')]
class imagick
class imagick extends PhpExtensionPackage
{
#[CustomPhpConfigureArg('Darwin')]
#[CustomPhpConfigureArg('Linux')]
Expand All @@ -18,4 +23,35 @@ public function getUnixConfigureArg(bool $shared, PackageBuilder $builder): stri
$disable_omp = ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . ($shared ? 'shared,' : '') . $builder->getBuildRootPath() . $disable_omp;
}

#[CustomPhpConfigureArg('Windows')]
public function getWindowsConfigureArg(bool $shared): string
{
// config.w32 uses PHP_IMAGICK as an extra search path for CORE_RL_*.lib; the static
// ImageMagick libs are installed flat in buildroot/lib (headers in buildroot/include/imagemagick).
return '--with-imagick=' . BUILD_LIB_PATH;
}

#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-imagick')]
#[PatchDescription('Add the Win32 system libraries the static ImageMagick stack needs')]
public function patchConfigW32ForWindows(): void
{
$config = $this->getSourceDir() . '/config.w32';

// Idempotency guard (the source dir may be patched in place and reused across builds).
if (str_contains(FileSystem::readFile($config), 'LIBS_IMAGICK')) {
return;
}

// The static ImageMagick stack needs several Win32 system libraries (GDI/GDI+, WIC, urlmon, ...)
// that aren't already pulled in by the other extensions. (imagick itself builds as plain C:
// ImageMagick is built with a 32-bit channel mask, see imagemagick.php buildWin, so the
// MagickCore headers don't require a C++ translation unit.)
FileSystem::replaceFileStr(
$config,
"AC_DEFINE('HAVE_IMAGICK', 1);",
'ADD_FLAG("LIBS_IMAGICK", "gdi32.lib gdiplus.lib urlmon.lib msimg32.lib oleaut32.lib windowscodecs.lib iphlpapi.lib");' . "\n\t\t" .
"AC_DEFINE('HAVE_IMAGICK', 1);"
);
}
}
Loading
Loading