Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8820a26
feat: Windows support
Jan 9, 2026
a5b6be8
fix tests
Jan 10, 2026
9b214e6
revert changes to watcher_test.go
dunglas Jan 10, 2026
0d67a8f
cs
dunglas Jan 10, 2026
29bf6df
fix remaining failing tests
Jan 10, 2026
58f55ef
fix watcher support
Jan 11, 2026
d91e8ba
cleanup
Jan 11, 2026
cc1be56
improve watcher tests (wip)
dunglas Jan 12, 2026
5b39fa8
all tests are green now!
Jan 12, 2026
4799c3a
cleanup
Jan 12, 2026
5da1dc0
test forward slashes on Windows
Jan 12, 2026
12585fd
upgrade watcher to simplify Windows linking
Jan 12, 2026
25d9a98
add php-cli support
Jan 12, 2026
c9ab80e
review
Jan 12, 2026
4bbe362
GitHub Actions worklow
dunglas Jan 13, 2026
f08f4bb
wip
dunglas Jan 15, 2026
6bab64f
work on windows workflow
henderkes Jan 16, 2026
36e3492
linter
henderkes Jan 16, 2026
684b5e0
fix env var
dunglas Jan 16, 2026
25b4b9f
use xcaddy
henderkes Jan 16, 2026
bb3fb4e
correct path
henderkes Jan 16, 2026
4aa0cbb
remove tmate action
henderkes Jan 16, 2026
d720ab1
add caddy modules back in
henderkes Jan 16, 2026
a7ed504
remove extra v
henderkes Jan 16, 2026
2e71f67
brotlidec missing
henderkes Jan 16, 2026
734203f
wip
dunglas Jan 16, 2026
4946a17
wip
dunglas Jan 16, 2026
63103cb
with GOFLAGS
dunglas Jan 16, 2026
7c4507a
wip
dunglas Jan 16, 2026
8136c69
wip
dunglas Jan 16, 2026
4bf3e92
wip
dunglas Jan 16, 2026
45b5bfe
don't use xcaddy
dunglas Jan 16, 2026
5c1fe58
fix Caddy module tests
Jan 17, 2026
aa2d96e
-X CustomVersion is part of -ldflags
henderkes Jan 17, 2026
75615e5
fix
henderkes Jan 17, 2026
2dbed0a
don't forget to add FRANKENPHP_VERSION to CGO_CFLAGS, otherwise phpin…
henderkes Jan 17, 2026
7e8e250
why is quoting so hard in powershell
henderkes Jan 17, 2026
6f2745d
permanently change it
henderkes Jan 17, 2026
3a66f36
seems impossible to do with GOFLAGS
henderkes Jan 17, 2026
efbd362
woopsie
henderkes Jan 17, 2026
ebb4fbd
fix \n vs \r\n issues
henderkes Jan 17, 2026
7e75699
fix double zipping, re-enable compression (actually makes a big diffe…
henderkes Jan 17, 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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
4 changes: 2 additions & 2 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,15 @@ jobs:
VARIANT: ${{ matrix.variant }}
- name: Upload builder metadata
if: fromJson(needs.prepare.outputs.push)
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: metadata-builder-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
path: /tmp/metadata/builder/*
if-no-files-found: error
retention-days: 1
- name: Upload runner metadata
if: fromJson(needs.prepare.outputs.push)
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: metadata-runner-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
path: /tmp/metadata/runner/*
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/static.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ jobs:
METADATA: ${{ steps.build.outputs.metadata }}
- name: Upload metadata
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }}
path: /tmp/metadata/*
Expand All @@ -188,7 +188,7 @@ jobs:
PLATFORM: ${{ matrix.platform }}
- name: Upload artifact
if: ${{ !fromJson(needs.prepare.outputs.push) }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
Expand Down Expand Up @@ -320,7 +320,7 @@ jobs:
METADATA: ${{ steps.build.outputs.metadata }}
- name: Upload metadata
if: fromJson(needs.prepare.outputs.push)
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }}
path: /tmp/metadata-gnu/*
Expand All @@ -344,7 +344,7 @@ jobs:
PLATFORM: ${{ matrix.platform }}
- name: Upload artifact
if: ${{ !fromJson(needs.prepare.outputs.push) }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu-files
path: gh-output/*
Expand Down Expand Up @@ -448,12 +448,12 @@ jobs:
ref: ${{ needs.prepare.outputs.ref }}
persist-credentials: false
- uses: actions/setup-go@v6
with:
with: # zizmor: ignore[cache-poisoning]
go-version: "1.25"
cache-dependency-path: |
go.sum
caddy/go.sum
cache: false
cache: ${{ github.event_name != 'release' }}
- name: Set FRANKENPHP_VERSION
run: |
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
Expand All @@ -475,7 +475,7 @@ jobs:
NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }}
- name: Upload logs
if: ${{ failure() }}
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
path: dist/static-php-cli/log
name: static-php-cli-log-${{ matrix.platform }}-${{ github.sha }}
Expand All @@ -485,7 +485,7 @@ jobs:
subject-path: ${{ github.workspace }}/dist/frankenphp-mac-*
- name: Upload artifact
if: github.ref_type == 'branch'
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: frankenphp-mac-${{ matrix.platform }}
path: dist/frankenphp-mac-${{ matrix.platform }}
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
env:
GOMAXPROCS: 10
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
GOFLAGS: "-tags=nobadger,nomysql,nopgx"
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -69,7 +70,7 @@ jobs:
run: ./frankenphp.test -test.v
- name: Run Caddy module tests
working-directory: caddy/
run: go test -tags nobadger,nomysql,nopgx -race -v ./...
run: go test -race -v ./...
- name: Run Fuzzing Tests
working-directory: caddy/
run: go test -fuzz FuzzRequest -fuzztime 20s
Expand Down Expand Up @@ -100,6 +101,8 @@ jobs:
fail-fast: false
matrix:
php-versions: ["8.3", "8.4", "8.5"]
env:
XCADDY_GO_BUILD_FLAGS: "-tags=nobadger,nomysql,nopgx"
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -141,6 +144,7 @@ jobs:
runs-on: macos-latest
env:
HOMEBREW_NO_AUTO_UPDATE: 1
GOFLAGS: "-tags=nowatcher,nobadger,nomysql,nopgx"
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -172,4 +176,4 @@ jobs:
run: go test -tags nowatcher -race -v ./...
- name: Run Caddy module tests
working-directory: caddy/
run: go test -tags nowatcher,nobadger,nomysql,nopgx -race -v ./...
run: go test -race -v ./...
191 changes: 191 additions & 0 deletions .github/workflows/windows.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
name: Build Windows release

concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}

on:
pull_request:
branches:
- main
paths:
- "docker-bake.hcl"
- "vcpkg.json"
- ".github/workflows/static.yaml"
- "**cgo.go"
- "**Dockerfile"
- "**.c"
- "**.h"
- "**.sh"
- "**.stub.php"
push:
branches:
- main
tags:
- v*.*.*
workflow_dispatch:
inputs:
#checkov:skip=CKV_GHA_7
version:
description: "FrankenPHP version"
required: false
type: string
schedule:
- cron: "0 8 * * *"

permissions:
contents: read

env:
GOTOOLCHAIN: local
GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx"
PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases"
CC: clang
CXX: clang++

jobs:
build:
runs-on: windows-latest
defaults:
run:
shell: powershell

steps:
- name: Configure Git
run: |
git config --global core.autocrlf false
git config --global core.eol lf

- name: Checkout Code
uses: actions/checkout@v6
with:
path: frankenphp
persist-credentials: false

- name: Set FRANKENPHP_VERSION
run: |
if ($env:GITHUB_REF_TYPE -eq "tag") {
$frankenphpVersion = $env:GITHUB_REF_NAME.Substring(1)
} elseif ($env:GITHUB_EVENT_NAME -eq "schedule") {
$frankenphpVersion = $env:GITHUB_REF
} else {
$frankenphpVersion = $env:GITHUB_SHA
}

echo "FRANKENPHP_VERSION=$frankenphpVersion" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Setup Go
uses: actions/setup-go@v6
with: # zizmor: ignore[cache-poisoning]
go-version: "1.26.0-rc.1"
cache-dependency-path: |
frankenphp/go.sum
frankenphp/caddy/go.sum
cache: ${{ github.event_name != 'release' }}
check-latest: true

- name: Install Vcpkg Libraries
working-directory: frankenphp
run: "vcpkg install"

- name: Download Watcher
run: |
$latestTag = gh release list --repo e-dant/watcher --limit 1 --exclude-drafts --exclude-pre-releases --json tagName --jq '.[0].tagName'
Write-Host "Latest Watcher version: $latestTag"

gh release download $latestTag --repo e-dant/watcher --pattern "*x86_64-pc-windows-msvc.tar" -O watcher.tar

tar -xf "watcher.tar" -C "$env:GITHUB_WORKSPACE"
Rename-Item -Path "$env:GITHUB_WORKSPACE\x86_64-pc-windows-msvc" -NewName "watcher"

# See https://github.com/e-dant/watcher/issues/108
New-Item -Path .\watcher\wtr -ItemType Directory -Force
Move-Item -Path .\watcher\watcher-c.h -Destination .\watcher\wtr\watcher-c.h
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Download PHP
run: |
$webContent = Invoke-WebRequest -Uri $env:PHP_DOWNLOAD_BASE -UseBasicParsing
$links = $webContent.Links.Href | Where-Object { $_ -match "php-\d+\.\d+\.\d+-Win32-vs17-x64\.zip$" }

if (-not $links) { throw "Could not find PHP zip files at $env:PHP_DOWNLOAD_BASE" }

$latestFile = $links | Sort-Object { if ($_ -match '(\d+\.\d+\.\d+)') { [version]$matches[1] } } | Select-Object -Last 1

$version = if ($latestFile -match '(\d+\.\d+\.\d+)') { $matches[1] }
Write-Host "Detected latest PHP version: $version"

"PHP_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append

$phpZip = "php-$version-Win32-vs17-x64.zip"
$develZip = "php-devel-pack-$version-Win32-vs17-x64.zip"

$dirName = "frankenphp-$env:FRANKENPHP_VERSION-php-$version-Win32-vs17-x64"

echo "DIR_NAME=$dirName" | Out-File -FilePath $env:GITHUB_ENV -Append

Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$phpZip" -OutFile "$env:TEMP\php.zip"
Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "$env:GITHUB_WORKSPACE\$dirName"

Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$develZip" -OutFile "$env:TEMP\php-devel.zip"
Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "$env:GITHUB_WORKSPACE\php-devel"

- name: Prepare env
run: |
$vcpkgRoot = "$env:GITHUB_WORKSPACE\frankenphp\vcpkg_installed\x64-windows"
$watcherRoot = "$env:GITHUB_WORKSPACE\watcher"
$phpBin = "$env:GITHUB_WORKSPACE\$env:DIR_NAME"
$phpDevel = "$env:GITHUB_WORKSPACE\php-devel\php-$env:PHP_VERSION-devel-vs17-x64"

echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "$vcpkgRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "$watcherRoot" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "$phpBin" | Out-File -FilePath $env:GITHUB_PATH -Append

echo "CGO_CFLAGS=-DFRANKENPHP_VERSION=$env:FRANKENPHP_VERSION -I$vcpkgRoot\include -I$watcherRoot -I$phpDevel\include -I$phpDevel\include\main -I$phpDevel\include\TSRM -I$phpDevel\include\Zend -I$phpDevel\include\ext" | Out-File -FilePath $env:GITHUB_ENV -Append
echo "CGO_LDFLAGS=-L$vcpkgRoot\lib -lbrotlienc -L$watcherRoot -llibwatcher-c -L$phpBin -L$phpDevel\lib -lphp8ts -lphp8embed" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Build FrankenPHP
run: |
$customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy"
go build -ldflags="-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'"
working-directory: frankenphp\caddy\frankenphp

- name: Create Directory
run: |
Copy-Item frankenphp\caddy\frankenphp\frankenphp.exe $env:DIR_NAME
Copy-Item watcher\libwatcher-c.dll $env:DIR_NAME
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll $env:DIR_NAME
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlidec.dll $env:DIR_NAME
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll $env:DIR_NAME
Copy-Item frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll $env:DIR_NAME

- name: Upload Artifact
if: github.event_name != 'release'
uses: actions/upload-artifact@v6
with:
name: ${{ env.DIR_NAME }}
path: ${{ env.DIR_NAME }}
if-no-files-found: error

- name: Zip Release Artifact
if: github.event_name == 'release'
run: Compress-Archive -Path "$env:DIR_NAME\*" -DestinationPath "$env:DIR_NAME.zip"

- name: Upload Release Asset
if: github.event_name == 'release'
run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:DIR_NAME.zip" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}

- name: Run Tests
run: |
"opcache.enable=0`r`nopcache.enable_cli=0" | Out-File php.ini
$env:PHPRC = Get-Location

go test ./...
cd caddy
go test ./...
working-directory: ${{ github.workspace }}\frankenphp
25 changes: 25 additions & 0 deletions caddy/caddy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ import (

var testPort = "9080"

// skipIfSymlinkNotValid skips the test if the given path is not a valid symlink
func skipIfSymlinkNotValid(t *testing.T, path string) {
t.Helper()

info, err := os.Lstat(path)
if err != nil {
t.Skipf("symlink test skipped: cannot stat %s: %v", path, err)
}

if info.Mode()&os.ModeSymlink == 0 {
t.Skipf("symlink test skipped: %s is not a symlink (git may not support symlinks on this platform)", path)
}
}

// escapeMetricLabel escapes backslashes in label values for Prometheus text format
func escapeMetricLabel(s string) string {
return strings.ReplaceAll(s, "\\", "\\\\")
}

func TestPHP(t *testing.T) {
var wg sync.WaitGroup
tester := caddytest.NewTester(t)
Expand Down Expand Up @@ -548,6 +567,7 @@ func TestWorkerMetrics(t *testing.T) {
`, "caddyfile")

workerName, _ := fastabs.FastAbs("../testdata/index.php")
workerName = escapeMetricLabel(workerName)

// Make some requests
for i := range 10 {
Expand Down Expand Up @@ -731,6 +751,7 @@ func TestAutoWorkerConfig(t *testing.T) {
`, "caddyfile")

workerName, _ := fastabs.FastAbs("../testdata/index.php")
workerName = escapeMetricLabel(workerName)

// Make some requests
for i := range 10 {
Expand Down Expand Up @@ -804,6 +825,7 @@ func TestAllDefinedServerVars(t *testing.T) {
expectedBody = strings.ReplaceAll(expectedBody, "{documentRoot}", documentRoot)
expectedBody = strings.ReplaceAll(expectedBody, "\r\n", "\n")
expectedBody = strings.ReplaceAll(expectedBody, "{testPort}", testPort)
expectedBody = strings.ReplaceAll(expectedBody, documentRoot+"/", documentRoot+string(filepath.Separator))
tester := caddytest.NewTester(t)
tester.InitServer(`
{
Expand Down Expand Up @@ -1505,6 +1527,7 @@ func TestLog(t *testing.T) {
func TestSymlinkWorkerPaths(t *testing.T) {
cwd, _ := os.Getwd()
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
skipIfSymlinkNotValid(t, publicDir)

t.Run("NeighboringWorkerScript", func(t *testing.T) {
// Scenario: neighboring worker script
Expand Down Expand Up @@ -1640,6 +1663,7 @@ func TestSymlinkResolveRoot(t *testing.T) {
cwd, _ := os.Getwd()
testDir := filepath.Join(cwd, "..", "testdata", "symlinks", "test")
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
skipIfSymlinkNotValid(t, publicDir)

t.Run("ResolveRootSymlink", func(t *testing.T) {
// Tests that resolve_root_symlink directive works correctly
Expand Down Expand Up @@ -1698,6 +1722,7 @@ func TestSymlinkResolveRoot(t *testing.T) {
func TestSymlinkWorkerBehavior(t *testing.T) {
cwd, _ := os.Getwd()
publicDir := filepath.Join(cwd, "..", "testdata", "symlinks", "public")
skipIfSymlinkNotValid(t, publicDir)

t.Run("WorkerScriptFailsWithoutWorkerMode", func(t *testing.T) {
// Tests that accessing a worker-only script without configuring it as a worker actually results in an error
Expand Down
Loading
Loading