Skip to content

Conversation

@dunglas
Copy link
Member

@dunglas dunglas commented Jan 9, 2026

Closes #83 #880 #1286.

Working patch for Windows support.

Supports linking to the official PHP release (TS version).
Includes some work from #1286 (thanks @TenHian!!)

This patch allows using Visual Studio to compile the cgo code. To do so, it must be compiled with Go 1.26 (RC) with the following setup:

winget install -e --id Microsoft.VisualStudio.2022.Community --override "--passive --wait --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended"
winget install -e --id GoLang.Go

# Add C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin to the path

cd c:\
gh repo clone microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install pthreads
.\vcpkg\vcpkg install brotli
$env:VCPKG_ROOT = 'C:\vcpkg'

# build watcher
Invoke-WebRequest -Uri "https://github.com/e-dant/watcher/releases/download/0.14.3/x86_64-pc-windows-msvc.tar" -OutFile "$env:TEMP\watcher.tar"
tar -xf "$env:TEMP\watcher.tar" -C C:\
Rename-Item -Path "C:\x86_64-pc-windows-msvc" -NewName "watcher-x86_64-pc-windows-msvc"
Remove-Item "$env:TEMP\watcher.tar"
# rename files to be found by go compilation
Get-ChildItem "C:\watcher-x86_64-pc-windows-msvc\libwatcher-c*" | Rename-Item -NewName { $_.Name -replace '^libwatcher-c', 'watcher-c' }

# download php
Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php.zip"
Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "C:\php-8.5.1-Win32-vs17-x64"
Remove-Item "$env:TEMP\php.zip"

# download php development package
Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-devel-pack-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php-devel.zip"
Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "C:\php-8.5.1-devel-vs17-x64"
Remove-Item "$env:TEMP\php-devel.zip"

$env:GOTOOLCHAIN = 'go1.26rc1'
$env:CC = 'clang'
$env:CXX = 'clang++'
$env:CGO_CFLAGS = "-I$env:VCPKG_ROOT\installed\x64-windows\include -IC:\watcher-x86_64-pc-windows-msvc -IC:\php-8.5.1-devel-vs17-x64\include -IC:\php-8.5.1-devel-vs17-x64\include\main -IC:\php-8.5.1-devel-vs17-x64\include\TSRM -IC:\php-8.5.1-devel-vs17-x64\include\Zend -IC:\php-8.5.1-devel-vs17-x64\include\ext"
$env:CGO_LDFLAGS = "-L$env:VCPKG_ROOT\installed\x64-windows\lib -lbrotlienc -LC:\watcher-x86_64-pc-windows-msvc -lwatcher-c -LC:\php-8.5.1-Win32-vs17-x64\dev -lphp8ts"

# clone frankenphp and build
git clone -b windows https://github.com/php/frankenphp.git
cd frankenphp\caddy\frankenphp
go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx

# Tests

$env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin"
$env:PATH += ";C:\watcher-x86_64-pc-windows-msvc"
$env:PATH += ";C:\php-8.5.1-Win32-vs17-x64"
"opcache.enable=0`r`nopcache.enable_cli=0" | Out-File -Encoding ascii php.ini
$env:PHPRC = Get-Location
go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx .

TODO:

  • Fix remaining skipped tests (scaling and watcher)
  • Test if the watcher mode works as expected
  • Automate the build with GitHub Actions

@henderkes
Copy link
Contributor

Very interesting, I had no idea that lld could link MinGW and MSVC objects together. This links against php8embed.lib?

@dunglas
Copy link
Member Author

dunglas commented Jan 9, 2026

Mingw isn't used at all with this patch. Everything uses Visual Studio.
For cgo, it uses the clang version provided by VS, which has a VS backend (needs Go 1.26, I stumbled upon this undocumented Google patch very recently).

php8embed.dll is not even needed, only php8ts.dll is needed, because I disabled the php-cli subcommand.

Locally, I even manage to run php-cli, but this requires manually compiling PHP to enable the embed SAPI, so I disabled it for now because both php8ts.dll and php.exe are shipped with the official PHP package.

I also have a patch for Static PHP CLI, but it's not working because the PHP source code and Makefile on Windows doesn't support building a static version of php8ts.dll and php8embed.dll. Patching the PHP source code will be necessary.

@henderkes
Copy link
Contributor

For cgo, it uses the clang version provided by VS, which has a VS backend (needs Go 1.26, I stumbled upon this undocumented Google patch very recently).

That's the part I was missing, thank you!

@henderkes
Copy link
Contributor

henderkes commented Jan 10, 2026

Gave it a shot and updated your initial post for instructions, however, once it attempts to serve a php file with .\frankenphp.exe php-server --root=./ the app stops.

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

Have you copied all the necessary DDLs in the same directory?

@henderkes
Copy link
Contributor

Shouldn't be necessary with

$env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin"
$env:PATH += ";C:\watcher-x86_64-pc-windows-msvc"
$env:PATH += ";C:\php-8.5.1-Win32-vs17-x64"

Is anything else required?

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

You must also add $env:PATH += ";C:\php-8.5.1-Win32-vs17-x64\lib".

@henderkes
Copy link
Contributor

Same issue, I don't think it could be related to libraries anyway, as it would fail to run in the first place then. Shared libraries are (by default) loaded at initialisation time and we're not passing delayload arguments to the compilation.

Log:

❯❯ frankenphp git:(windows) 21:46 .\frankenphp.exe php-server --root=./
2026/01/10 20:46:22.912 WARN    admin   admin endpoint disabled
2026/01/10 20:46:22.912 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x2e61d3850500"}
2026/01/10 20:46:22.912 WARN    http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "php", "http_port": 80}
2026/01/10 20:46:22.949 INFO    frankenphp      FrankenPHP started 🐘   {"php_version": "8.5.1", "num_threads": 64, "max_threads": 64}
2026/01/10 20:46:22.950 WARN    http    HTTP/2 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2026/01/10 20:46:22.950 WARN    http    HTTP/3 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2026/01/10 20:46:22.950 INFO    http.log        server running  {"name": "php", "protocols": ["h1", "h2", "h3"]}
2026/01/10 20:46:22.951 INFO    Caddy serving PHP app on :80
2026/01/10 20:46:22.953 INFO    tls     storage cleaning happened too recently; skipping for now        {"storage": "FileStorage:C:\\Users\\m\\AppData\\Roaming\\Caddy", "instance": "489ade52-21ab-40c6-b18a-2932fb8eab4d", "try_again": "2026/01/11 20:46:22.953", "try_again_in": 86400}
2026/01/10 20:46:22.953 INFO    tls     finished cleaning storage units
❯❯ frankenphp git:(windows)  21:46

Text files like an index.html page are served just fine. Only when php is attempted to be executed does the program simply stop.

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

I didn't try the php-server command yet, only run with a custom Caddyfile.
Are the tests green? (They are on my local installation)

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

Could you also show me the content of your PHP script?

@henderkes
Copy link
Contributor

Content of the php script:

<?php
echo phpinfo();

I haven't ran the test suite yet, but frankenphp run shows the same behaviour.

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.

Windows compatibility

3 participants