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

$env:PATH += ';C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin'

cd c:\
gh repo clone microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install pthreads brotli

# 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"

# 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:\"
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:\"
Remove-Item "$env:TEMP\php-devel.zip"

$env:GOTOOLCHAIN = 'go1.26rc1'
$env:CC = 'clang'
$env:CXX = 'clang++'
$env:CGO_CFLAGS = "-I$env:C:\vcpkg\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 = '-LC:\vcpkg\installed\x64-windows\lib -lbrotlienc -LC:\watcher-x86_64-pc-windows-msvc -llibwatcher-c -LC:\php-8.5.1-Win32-vs17-x64 -LC:\php-8.5.1-devel-vs17-x64\lib -lphp8ts -lphp8embed'

# 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;C:\watcher-x86_64-pc-windows-msvc";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.

@dunglas dunglas marked this pull request as ready for review January 12, 2026 13:43
@dunglas
Copy link
Member Author

dunglas commented Jan 12, 2026

Here is a build I created locally: https://drive.google.com/file/d/1B09de1rERpRUN-bnAje0K2vHcwhVoDJa/view?usp=sharing
It's the official PHP binary distribution with FrankenPHP itself and the extra needed DLLs added.

On my computer, it runs Symfony without issue @henderkes.

@henderkes
Copy link
Contributor

henderkes commented Jan 12, 2026

Thanks, I'll try this one and report back.

Edit: it's working, but it was linked against 8.5.3-dev. I'll try to link against 8.5.1 again and see if I can get anywhere after the new commits.

@dunglas dunglas force-pushed the windows branch 2 times, most recently from 985e36b to f374dab Compare January 13, 2026 23:25
@henderkes
Copy link
Contributor

@dunglas I got it working to build with xcaddy, but locally it fails to build without adding double quotes around the CustomVersion, which in turn leads to them being in the frankenphp.exe version output as well.
Giving it a try to see if xcaddy can build without the quotes in CI, if not, this smells like a bug in Go or Xcaddy and not something we can fix.

@henderkes
Copy link
Contributor

Works in CI, so the last things to do are tests and making zizmor happy, I'll leave those to you. 😁

@henderkes
Copy link
Contributor

Last idea I have is that we should perhaps compile in watcher, brotli and pthreads statically. Then we could just release frankenphp.exe instead of a whole zip folder. Users would just need to put it in their php folder, or perhaps we could even get the Windows folk to add it to their release zips.

@dunglas
Copy link
Member Author

dunglas commented Jan 16, 2026

I'm not sure there is much gain to compile partially statically on Windows. However, that could be nice to have a full static binary with SPC, but there is more work to do.

Thanks a lot for your work on the CI @henderkes, I'll finish this.

@henderkes
Copy link
Contributor

I got a fully static binary working with spc, but the extension set is too limited as of now. It would be hundreds of hours to bring it (near) up to par with Linux and with the way windows distribution works, I don't think it's worth it.

If anything, an installer would make more sense, that acts much like our package distributions and registers a service like IIS.

@henderkes
Copy link
Contributor

Don't we rely on xcaddy to build in modules like mercure/vulcain/cbrotli/others without fetching them and using go mod ourselves?

@dunglas
Copy link
Member Author

dunglas commented Jan 16, 2026

Until now, no. That's why we maintain the file in caddy/frankenphp, as recommended by Caddy. This also ensures reproductible builds.

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