From 8820a26ca4077a920208c13958f8e10e1ee4965f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 9 Jan 2026 18:26:54 +0100 Subject: [PATCH 01/43] feat: Windows support --- caddy/frankenphp/cbrotli.go | 5 ++++ caddy/frankenphp/main.go | 1 - caddy/php-cli.go | 2 ++ cgi.go | 2 +- cgo.go | 6 ++-- cli.go | 35 ++++++++++++++++++++++ cli_test.go | 55 +++++++++++++++++++++++++++++++++++ ext.go | 2 +- frankenphp.c | 16 ++++++++-- frankenphp.go | 26 +---------------- frankenphp.h | 44 ++++++++++++++++++++++++++-- frankenphp_test.go | 43 --------------------------- internal/cpu/cpu_unix.go | 2 ++ internal/cpu/cpu_windows.go | 2 +- internal/extgen/stub_test.go | 10 +++++-- internal/extgen/utils_test.go | 5 ++++ internal/testext/extension.h | 1 + internal/testext/extensions.c | 1 + internal/testext/exttest.go | 8 +++-- internal/watcher/pattern.go | 4 +++ mercure.go | 1 + phpmainthread.go | 2 +- phpmainthread_test.go | 3 +- scaling.go | 3 -- scaling_test.go | 1 + testdata/session.php | 2 +- types.h | 2 +- watcher_test.go | 6 ++-- 28 files changed, 197 insertions(+), 93 deletions(-) create mode 100644 caddy/frankenphp/cbrotli.go create mode 100644 cli.go create mode 100644 cli_test.go diff --git a/caddy/frankenphp/cbrotli.go b/caddy/frankenphp/cbrotli.go new file mode 100644 index 0000000000..ee84e60373 --- /dev/null +++ b/caddy/frankenphp/cbrotli.go @@ -0,0 +1,5 @@ +//go:build !nobrotli + +package main + +import _ "github.com/dunglas/caddy-cbrotli" diff --git a/caddy/frankenphp/main.go b/caddy/frankenphp/main.go index 5b5fc0d7c9..6b9d40561f 100644 --- a/caddy/frankenphp/main.go +++ b/caddy/frankenphp/main.go @@ -5,7 +5,6 @@ import ( // plug in Caddy modules here. _ "github.com/caddyserver/caddy/v2/modules/standard" - _ "github.com/dunglas/caddy-cbrotli" _ "github.com/dunglas/frankenphp/caddy" _ "github.com/dunglas/mercure/caddy" _ "github.com/dunglas/vulcain/caddy" diff --git a/caddy/php-cli.go b/caddy/php-cli.go index 4e76ff147a..bed3042907 100644 --- a/caddy/php-cli.go +++ b/caddy/php-cli.go @@ -1,3 +1,5 @@ +//go:build !windows + package caddy import ( diff --git a/cgi.go b/cgi.go index 63fb1339b9..44e54786ff 100644 --- a/cgi.go +++ b/cgi.go @@ -8,8 +8,8 @@ package frankenphp // #cgo noescape frankenphp_register_variables_from_request_info // #cgo noescape frankenphp_register_variable_safe // #cgo noescape frankenphp_register_single -// #include // #include "frankenphp.h" +// #include import "C" import ( "context" diff --git a/cgo.go b/cgo.go index 2ec9586308..8dc2609007 100644 --- a/cgo.go +++ b/cgo.go @@ -1,9 +1,11 @@ package frankenphp // #cgo darwin pkg-config: libxml-2.0 -// #cgo CFLAGS: -Wall -Werror +// #cgo unix CFLAGS: -Wall -Werror // #cgo linux CFLAGS: -D_GNU_SOURCE -// #cgo LDFLAGS: -lphp -lm -lutil +// #cgo unix LDFLAGS: -lphp -lm -lutil // #cgo linux LDFLAGS: -ldl -lresolv // #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -liconv -ldl +// #cgo windows CFLAGS: -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 +// #cgo windows LDFLAGS: -lpthreadVC3 import "C" diff --git a/cli.go b/cli.go new file mode 100644 index 0000000000..ce5f344269 --- /dev/null +++ b/cli.go @@ -0,0 +1,35 @@ +//go:build !windows + +// TODO: ignored on Windows for now (even if it should work with a custom PHP build), +// because static builds of the embed SAPI aren't available yet and php.exe is ship with +// the standard PHP distribution. + +package frankenphp + +// #include "frankenphp.h" +import "C" +import "unsafe" + +// ExecuteScriptCLI executes the PHP script passed as parameter. +// It returns the exit status code of the script. +func ExecuteScriptCLI(script string, args []string) int { + // Ensure extensions are registered before CLI execution + registerExtensions() + + cScript := C.CString(script) + defer C.free(unsafe.Pointer(cScript)) + + argc, argv := convertArgs(args) + defer freeArgs(argv) + + return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false)) +} + +func ExecutePHPCode(phpCode string) int { + // Ensure extensions are registered before CLI execution + registerExtensions() + + cCode := C.CString(phpCode) + defer C.free(unsafe.Pointer(cCode)) + return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true)) +} diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000000..95818bde4f --- /dev/null +++ b/cli_test.go @@ -0,0 +1,55 @@ +//go:build !windows + +package frankenphp_test + +import ( + "errors" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExecuteScriptCLI(t *testing.T) { + if _, err := os.Stat("internal/testcli/testcli"); err != nil { + t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") + } + + cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar") + stdoutStderr, err := cmd.CombinedOutput() + assert.Error(t, err) + + var exitError *exec.ExitError + if errors.As(err, &exitError) { + assert.Equal(t, 3, exitError.ExitCode()) + } + + stdoutStderrStr := string(stdoutStderr) + + assert.Contains(t, stdoutStderrStr, `"foo"`) + assert.Contains(t, stdoutStderrStr, `"bar"`) + assert.Contains(t, stdoutStderrStr, "From the CLI") +} + +func TestExecuteCLICode(t *testing.T) { + if _, err := os.Stat("internal/testcli/testcli"); err != nil { + t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") + } + + cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';") + stdoutStderr, err := cmd.CombinedOutput() + assert.NoError(t, err) + + stdoutStderrStr := string(stdoutStderr) + assert.Equal(t, stdoutStderrStr, `Hello World`) +} + +func ExampleExecuteScriptCLI() { + if len(os.Args) <= 1 { + log.Println("Usage: my-program script.php") + os.Exit(1) + } + + os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args)) +} diff --git a/ext.go b/ext.go index 1c0656c820..b993bf83df 100644 --- a/ext.go +++ b/ext.go @@ -1,6 +1,6 @@ package frankenphp -//#include "frankenphp.h" +// #include "frankenphp.h" import "C" import ( "sync" diff --git a/frankenphp.c b/frankenphp.c index fd487edb8e..1e8ebc4ff4 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1,14 +1,18 @@ +#include "frankenphp.h" #include #include #include #include -#include #include #include #include #include #include +#ifdef PHP_WIN32 +#include +#else #include +#endif #include #include #include @@ -19,7 +23,9 @@ #include #include #include +#ifndef ZEND_WIN32 #include +#endif #if defined(__linux__) #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) @@ -205,7 +211,7 @@ bool frankenphp_shutdown_dummy_request(void) { return true; } -PHPAPI void get_full_env(zval *track_vars_array) { +void get_full_env(zval *track_vars_array) { go_getfullenv(thread_index, track_vars_array); } @@ -959,6 +965,7 @@ static void *php_thread(void *arg) { } static void *php_main(void *arg) { +#ifndef ZEND_WIN32 /* * SIGPIPE must be masked in non-Go threads: * https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG @@ -971,6 +978,7 @@ static void *php_main(void *arg) { perror("failed to block SIGPIPE"); exit(EXIT_FAILURE); } +#endif set_thread_name("php-main"); @@ -1188,6 +1196,7 @@ static void sapi_cli_register_variables(zval *track_vars_array) /* {{{ */ } /* }}} */ +#ifndef ZEND_WIN32 static void *execute_script_cli(void *arg) { void *exit_status; bool eval = (bool)arg; @@ -1249,6 +1258,7 @@ int frankenphp_execute_script_cli(char *script, int argc, char **argv, return (intptr_t)exit_status; } +#endif int frankenphp_reset_opcache(void) { zend_function *opcache_reset = @@ -1266,7 +1276,7 @@ static zend_module_entry **modules = NULL; static int modules_len = 0; static int (*original_php_register_internal_extensions_func)(void) = NULL; -PHPAPI int register_internal_extensions(void) { +int register_internal_extensions(void) { if (original_php_register_internal_extensions_func != NULL && original_php_register_internal_extensions_func() != SUCCESS) { return FAILURE; diff --git a/frankenphp.go b/frankenphp.go index 693870e1d0..b20d6f6681 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -14,10 +14,10 @@ package frankenphp // #include // #include +// #include "frankenphp.h" // #include // #include // #include -// #include "frankenphp.h" import "C" import ( "bytes" @@ -753,30 +753,6 @@ func go_is_context_done(threadIndex C.uintptr_t) C.bool { return C.bool(phpThreads[threadIndex].frankenPHPContext().isDone) } -// ExecuteScriptCLI executes the PHP script passed as parameter. -// It returns the exit status code of the script. -func ExecuteScriptCLI(script string, args []string) int { - // Ensure extensions are registered before CLI execution - registerExtensions() - - cScript := C.CString(script) - defer C.free(unsafe.Pointer(cScript)) - - argc, argv := convertArgs(args) - defer freeArgs(argv) - - return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false)) -} - -func ExecutePHPCode(phpCode string) int { - // Ensure extensions are registered before CLI execution - registerExtensions() - - cCode := C.CString(phpCode) - defer C.free(unsafe.Pointer(cCode)) - return int(C.frankenphp_execute_script_cli(cCode, 0, nil, true)) -} - func convertArgs(args []string) (C.int, []*C.char) { argc := C.int(len(args)) argv := make([]*C.char, argc) diff --git a/frankenphp.h b/frankenphp.h index c833c44f97..e3be6556b5 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -1,10 +1,48 @@ #ifndef _FRANKENPHP_H #define _FRANKENPHP_H -#include -#include +#ifdef _WIN32 + // Define this to prevent windows.h from including legacy winsock.h + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + // Explicitly include Winsock2 BEFORE windows.h + #include + #include + #include + #include + + // Fix for missing IntSafe functions (LongLongAdd) when building with Clang + #ifdef __clang__ + #ifndef INTSAFE_E_ARITHMETIC_OVERFLOW + #define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) + #endif + + #ifndef LongLongAdd + static inline HRESULT LongLongAdd(LONGLONG llAugend, LONGLONG llAddend, LONGLONG* pllResult) { + if (__builtin_add_overflow(llAugend, llAddend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; + } + #endif + + #ifndef LongLongSub + static inline HRESULT LongLongSub(LONGLONG llMinuend, LONGLONG llSubtrahend, LONGLONG* pllResult) { + if (__builtin_sub_overflow(llMinuend, llSubtrahend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; + } + #endif + #endif +#endif + #include #include +#include +#include #ifndef FRANKENPHP_VERSION #define FRANKENPHP_VERSION dev @@ -47,8 +85,10 @@ bool frankenphp_shutdown_dummy_request(void); int frankenphp_execute_script(char *file_name); void frankenphp_update_local_thread_context(bool is_worker); +#ifndef ZEND_WIN32 int frankenphp_execute_script_cli(char *script, int argc, char **argv, bool eval); +#endif void frankenphp_register_variables_from_request_info( zval *track_vars_array, zend_string *content_type, diff --git a/frankenphp_test.go b/frankenphp_test.go index 8c6f3c90da..fdebbfcdf7 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -733,40 +733,6 @@ func testFileUpload(t *testing.T, opts *testOptions) { }, opts) } -func TestExecuteScriptCLI(t *testing.T) { - if _, err := os.Stat("internal/testcli/testcli"); err != nil { - t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") - } - - cmd := exec.Command("internal/testcli/testcli", "testdata/command.php", "foo", "bar") - stdoutStderr, err := cmd.CombinedOutput() - assert.Error(t, err) - - var exitError *exec.ExitError - if errors.As(err, &exitError) { - assert.Equal(t, 3, exitError.ExitCode()) - } - - stdoutStderrStr := string(stdoutStderr) - - assert.Contains(t, stdoutStderrStr, `"foo"`) - assert.Contains(t, stdoutStderrStr, `"bar"`) - assert.Contains(t, stdoutStderrStr, "From the CLI") -} - -func TestExecuteCLICode(t *testing.T) { - if _, err := os.Stat("internal/testcli/testcli"); err != nil { - t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`") - } - - cmd := exec.Command("internal/testcli/testcli", "-r", "echo 'Hello World';") - stdoutStderr, err := cmd.CombinedOutput() - assert.NoError(t, err) - - stdoutStderrStr := string(stdoutStderr) - assert.Equal(t, stdoutStderrStr, `Hello World`) -} - func ExampleServeHTTP() { if err := frankenphp.Init(); err != nil { panic(err) @@ -786,15 +752,6 @@ func ExampleServeHTTP() { log.Fatal(http.ListenAndServe(":8080", nil)) } -func ExampleExecuteScriptCLI() { - if len(os.Args) <= 1 { - log.Println("Usage: my-program script.php") - os.Exit(1) - } - - os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args)) -} - func BenchmarkHelloWorld(b *testing.B) { require.NoError(b, frankenphp.Init()) b.Cleanup(frankenphp.Shutdown) diff --git a/internal/cpu/cpu_unix.go b/internal/cpu/cpu_unix.go index 4d18215226..b33f9e3bf8 100644 --- a/internal/cpu/cpu_unix.go +++ b/internal/cpu/cpu_unix.go @@ -1,3 +1,5 @@ +//go:build unix + package cpu // #include diff --git a/internal/cpu/cpu_windows.go b/internal/cpu/cpu_windows.go index d09d552410..fae6689f08 100644 --- a/internal/cpu/cpu_windows.go +++ b/internal/cpu/cpu_windows.go @@ -5,7 +5,7 @@ import ( ) // ProbeCPUs fallback that always determines that the CPU limits are not reached -func ProbeCPUs(probeTime time.Duration, maxCPUUsage float64, abort chan struct{}) bool { +func ProbeCPUs(probeTime time.Duration, _ float64, abort chan struct{}) bool { select { case <-abort: return false diff --git a/internal/extgen/stub_test.go b/internal/extgen/stub_test.go index b9d689ffbc..172d4966ad 100644 --- a/internal/extgen/stub_test.go +++ b/internal/extgen/stub_test.go @@ -2,6 +2,7 @@ package extgen import ( "path/filepath" + "runtime" "strings" "testing" @@ -536,7 +537,12 @@ func TestStubGenerator_FileStructure(t *testing.T) { content, err := stubGen.buildContent() assert.NoError(t, err, "buildContent() failed") - lines := strings.Split(content, "\n") + sep := "\n" + if runtime.GOOS == "windows" { + sep = "\r\n" + } + + lines := strings.Split(content, sep) assert.GreaterOrEqual(t, len(lines), 3, "Stub file should have multiple lines") assert.Equal(t, " extern zend_module_entry module1_entry; diff --git a/internal/testext/extensions.c b/internal/testext/extensions.c index 721955f621..749d00b56d 100644 --- a/internal/testext/extensions.c +++ b/internal/testext/extensions.c @@ -1,3 +1,4 @@ +#include "extension.h" #include #include diff --git a/internal/testext/exttest.go b/internal/testext/exttest.go index 1a8477d4a8..a088ef3105 100644 --- a/internal/testext/exttest.go +++ b/internal/testext/exttest.go @@ -1,13 +1,15 @@ package testext // #cgo darwin pkg-config: libxml-2.0 -// #cgo CFLAGS: -Wall -Werror -// #cgo CFLAGS: -I/usr/local/include -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib +// #cgo unix CFLAGS: -Wall -Werror +// #cgo unix CFLAGS: -I/usr/local/include -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib // #cgo linux CFLAGS: -D_GNU_SOURCE // #cgo darwin CFLAGS: -I/opt/homebrew/include -// #cgo LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lm -lutil +// #cgo unix LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lm -lutil // #cgo linux LDFLAGS: -ldl -lresolv // #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -L/opt/homebrew/lib -L/opt/homebrew/opt/libiconv/lib -liconv -ldl +// #cgo windows CFLAGS: -IC:\vcpkg\installed\x64-windows\include -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 +// #cgo windows LDFLAGS: -LC:\vcpkg\installed\x64-windows\lib // #include "extension.h" import "C" import ( diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 5e6fda282d..8e72486c27 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -3,6 +3,7 @@ package watcher import ( + "log" "log/slog" "path/filepath" "strings" @@ -22,6 +23,7 @@ type pattern struct { } func (p *pattern) startSession() { + log.Printf("value %#v\n", p.value) p.watcher = watcher.NewWatcher(p.value, p.handle) if globalLogger.Enabled(globalCtx, slog.LevelDebug) { @@ -84,6 +86,8 @@ func (p *pattern) allowReload(event *watcher.Event) bool { } func (p *pattern) handle(event *watcher.Event) { + log.Printf("received: %#v", event) + // If the watcher prematurely sends the die@ event, retry watching if event.PathType == watcher.PathTypeWatcher && strings.HasPrefix(event.PathName, "e/self/die@") && watcherIsActive.Load() { p.retryWatching() diff --git a/mercure.go b/mercure.go index 9dc27e2290..d7cf33609e 100644 --- a/mercure.go +++ b/mercure.go @@ -3,6 +3,7 @@ package frankenphp // #include +// #include "frankenphp.h" // #include import "C" import ( diff --git a/phpmainthread.go b/phpmainthread.go index cecadc1653..1d7383cc57 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -4,8 +4,8 @@ package frankenphp // #cgo nocallback frankenphp_init_persistent_string // #cgo noescape frankenphp_new_main_thread // #cgo noescape frankenphp_init_persistent_string -// #include // #include "frankenphp.h" +// #include import "C" import ( "log/slog" diff --git a/phpmainthread_test.go b/phpmainthread_test.go index 7e6bf32c1e..daf93532f0 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -92,11 +92,12 @@ func TestTransitionAThreadBetween2DifferentWorkers(t *testing.T) { // try all possible handler transitions // takes around 200ms and is supposed to force race conditions func TestTransitionThreadsWhileDoingRequests(t *testing.T) { + t.SkipNow() t.Cleanup(Shutdown) var ( isDone atomic.Bool - wg sync.WaitGroup + wg sync.WaitGroup ) numThreads := 10 diff --git a/scaling.go b/scaling.go index 37e081abb9..51acd1cd37 100644 --- a/scaling.go +++ b/scaling.go @@ -1,8 +1,5 @@ package frankenphp -//#include "frankenphp.h" -//#include -import "C" import ( "errors" "log/slog" diff --git a/scaling_test.go b/scaling_test.go index f7ecc05e05..7faec4b509 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -30,6 +30,7 @@ func TestScaleARegularThreadUpAndDown(t *testing.T) { } func TestScaleAWorkerThreadUpAndDown(t *testing.T) { + t.SkipNow() t.Cleanup(Shutdown) workerName := "worker1" diff --git a/testdata/session.php b/testdata/session.php index dacc631151..9598359c6a 100644 --- a/testdata/session.php +++ b/testdata/session.php @@ -11,5 +11,5 @@ $_SESSION['count'] = 0; } - echo 'Count: '.$_SESSION['count'].PHP_EOL; + echo 'Count: '.$_SESSION['count']."\n"; }; diff --git a/types.h b/types.h index 552ddfe7fa..619603ab97 100644 --- a/types.h +++ b/types.h @@ -1,11 +1,11 @@ #ifndef TYPES_H #define TYPES_H +#include "frankenphp.h" #include #include #include #include -#include zval *get_ht_packed_data(HashTable *, uint32_t index); Bucket *get_ht_bucket_data(HashTable *, uint32_t index); diff --git a/watcher_test.go b/watcher_test.go index 3e0d9d108a..91032649f1 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -24,7 +24,9 @@ const minTimesToPollForChanges = 3 const maxTimesToPollForChanges = 60 func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { - watch := []string{"./testdata/**/*.txt"} + sep := string(os.PathSeparator) + watch := []string{"." + sep + "testdata" + sep + "**" + sep + "*.txt"} + t.Log(watch[0]) runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, maxTimesToPollForChanges) @@ -33,7 +35,7 @@ func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { } func TestWorkersShouldNotReloadOnExcludingPattern(t *testing.T) { - watch := []string{"./testdata/**/*.php"} + watch := []string{filepath.Join(".", "testdata", "**", "*.txt")} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, minTimesToPollForChanges) From a5b6be8bd1b12fc6356d8dd8bb0db44c8c792f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 10 Jan 2026 12:36:22 +0100 Subject: [PATCH 02/43] fix tests --- cli_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli_test.go b/cli_test.go index 95818bde4f..f2c47b38af 100644 --- a/cli_test.go +++ b/cli_test.go @@ -4,10 +4,12 @@ package frankenphp_test import ( "errors" + "log" "os" "os/exec" "testing" + "github.com/dunglas/frankenphp" "github.com/stretchr/testify/assert" ) From 9b214e6daee90c087e430e4e84568533bdd94776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 10 Jan 2026 17:29:13 +0100 Subject: [PATCH 03/43] revert changes to watcher_test.go --- watcher_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/watcher_test.go b/watcher_test.go index 91032649f1..3e0d9d108a 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -24,9 +24,7 @@ const minTimesToPollForChanges = 3 const maxTimesToPollForChanges = 60 func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { - sep := string(os.PathSeparator) - watch := []string{"." + sep + "testdata" + sep + "**" + sep + "*.txt"} - t.Log(watch[0]) + watch := []string{"./testdata/**/*.txt"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, maxTimesToPollForChanges) @@ -35,7 +33,7 @@ func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { } func TestWorkersShouldNotReloadOnExcludingPattern(t *testing.T) { - watch := []string{filepath.Join(".", "testdata", "**", "*.txt")} + watch := []string{"./testdata/**/*.php"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, minTimesToPollForChanges) From 0d67a8fb909d3f5797efdb45dc31e4eb6b23b47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 10 Jan 2026 18:27:07 +0100 Subject: [PATCH 04/43] cs --- frankenphp.h | 76 +++++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/frankenphp.h b/frankenphp.h index e3be6556b5..133ab07336 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -2,47 +2,49 @@ #define _FRANKENPHP_H #ifdef _WIN32 - // Define this to prevent windows.h from including legacy winsock.h - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - - // Explicitly include Winsock2 BEFORE windows.h - #include - #include - #include - #include - - // Fix for missing IntSafe functions (LongLongAdd) when building with Clang - #ifdef __clang__ - #ifndef INTSAFE_E_ARITHMETIC_OVERFLOW - #define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) - #endif - - #ifndef LongLongAdd - static inline HRESULT LongLongAdd(LONGLONG llAugend, LONGLONG llAddend, LONGLONG* pllResult) { - if (__builtin_add_overflow(llAugend, llAddend, pllResult)) { - return INTSAFE_E_ARITHMETIC_OVERFLOW; - } - return S_OK; - } - #endif - - #ifndef LongLongSub - static inline HRESULT LongLongSub(LONGLONG llMinuend, LONGLONG llSubtrahend, LONGLONG* pllResult) { - if (__builtin_sub_overflow(llMinuend, llSubtrahend, pllResult)) { - return INTSAFE_E_ARITHMETIC_OVERFLOW; - } - return S_OK; - } - #endif - #endif +// Define this to prevent windows.h from including legacy winsock.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Explicitly include Winsock2 BEFORE windows.h +#include +#include +#include +#include + +// Fix for missing IntSafe functions (LongLongAdd) when building with Clang +#ifdef __clang__ +#ifndef INTSAFE_E_ARITHMETIC_OVERFLOW +#define INTSAFE_E_ARITHMETIC_OVERFLOW ((HRESULT)0x80070216L) +#endif + +#ifndef LongLongAdd +static inline HRESULT LongLongAdd(LONGLONG llAugend, LONGLONG llAddend, + LONGLONG *pllResult) { + if (__builtin_add_overflow(llAugend, llAddend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; +} +#endif + +#ifndef LongLongSub +static inline HRESULT LongLongSub(LONGLONG llMinuend, LONGLONG llSubtrahend, + LONGLONG *pllResult) { + if (__builtin_sub_overflow(llMinuend, llSubtrahend, pllResult)) { + return INTSAFE_E_ARITHMETIC_OVERFLOW; + } + return S_OK; +} +#endif +#endif #endif -#include -#include #include #include +#include +#include #ifndef FRANKENPHP_VERSION #define FRANKENPHP_VERSION dev From 29bf6df2f68ac68926b40609c08465d125b78d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 11 Jan 2026 00:06:37 +0100 Subject: [PATCH 05/43] fix remaining failing tests --- phpmainthread_test.go | 5 ++--- scaling_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/phpmainthread_test.go b/phpmainthread_test.go index daf93532f0..3282f46364 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -92,7 +92,6 @@ func TestTransitionAThreadBetween2DifferentWorkers(t *testing.T) { // try all possible handler transitions // takes around 200ms and is supposed to force race conditions func TestTransitionThreadsWhileDoingRequests(t *testing.T) { - t.SkipNow() t.Cleanup(Shutdown) var ( @@ -102,9 +101,9 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) { numThreads := 10 numRequestsPerThread := 100 - worker1Path := testDataPath + "/transition-worker-1.php" + worker1Path := filepath.Join(testDataPath, "transition-worker-1.php") worker1Name := "worker-1" - worker2Path := testDataPath + "/transition-worker-2.php" + worker2Path := filepath.Join(testDataPath, "transition-worker-2.php") worker2Name := "worker-2" assert.NoError(t, Init( diff --git a/scaling_test.go b/scaling_test.go index 7faec4b509..2397c665c4 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -1,6 +1,7 @@ package frankenphp import ( + "path/filepath" "testing" "time" @@ -30,11 +31,10 @@ func TestScaleARegularThreadUpAndDown(t *testing.T) { } func TestScaleAWorkerThreadUpAndDown(t *testing.T) { - t.SkipNow() t.Cleanup(Shutdown) workerName := "worker1" - workerPath := testDataPath + "/transition-worker-1.php" + workerPath := filepath.Join(testDataPath, "/transition-worker-1.php") assert.NoError(t, Init( WithNumThreads(2), WithMaxThreads(3), From 58f55ef88f7dd5c5068af0ebff5f7e9ce8eeea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 11 Jan 2026 01:13:23 +0100 Subject: [PATCH 06/43] fix watcher support --- internal/watcher/pattern.go | 17 +++++++++++------ watcher_test.go | 10 ++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 8e72486c27..93ae26d692 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -3,9 +3,9 @@ package watcher import ( - "log" "log/slog" "path/filepath" + "runtime" "strings" "github.com/dunglas/frankenphp/internal/fastabs" @@ -23,7 +23,6 @@ type pattern struct { } func (p *pattern) startSession() { - log.Printf("value %#v\n", p.value) p.watcher = watcher.NewWatcher(p.value, p.handle) if globalLogger.Enabled(globalCtx, slog.LevelDebug) { @@ -45,6 +44,10 @@ func (p *pattern) parse() (err error) { splitPattern := strings.Split(absPattern, string(filepath.Separator)) patternWithoutDir := "" for i, part := range splitPattern { + if i == 0 && runtime.GOOS == "windows" { + splitPattern[i] = splitPattern[0] + "\\" + } + isFilename := i == len(splitPattern)-1 && strings.Contains(part, ".") isGlobCharacter := strings.ContainsAny(part, "[*?{") @@ -62,8 +65,12 @@ func (p *pattern) parse() (err error) { p.parsedValues[i] = strings.Trim(pp, string(filepath.Separator)) } - // remove the trailing separator and add leading separator - p.value = string(filepath.Separator) + strings.Trim(p.value, string(filepath.Separator)) + // remove the trailing separator and add leading separator (except on Windows) + if runtime.GOOS == "windows" { + p.value = strings.Trim(p.value, string(filepath.Separator)) + } else { + p.value = string(filepath.Separator) + strings.Trim(p.value, string(filepath.Separator)) + } // try to canonicalize the path canonicalPattern, err := filepath.EvalSymlinks(p.value) @@ -86,8 +93,6 @@ func (p *pattern) allowReload(event *watcher.Event) bool { } func (p *pattern) handle(event *watcher.Event) { - log.Printf("received: %#v", event) - // If the watcher prematurely sends the die@ event, retry watching if event.PathType == watcher.PathTypeWatcher && strings.HasPrefix(event.PathName, "e/self/die@") && watcherIsActive.Load() { p.retryWatching() diff --git a/watcher_test.go b/watcher_test.go index 3e0d9d108a..59c7c1b253 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -24,7 +24,7 @@ const minTimesToPollForChanges = 3 const maxTimesToPollForChanges = 60 func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { - watch := []string{"./testdata/**/*.txt"} + watch := []string{filepath.Join("testdata", "**", "*.txt")} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, maxTimesToPollForChanges) @@ -33,7 +33,7 @@ func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { } func TestWorkersShouldNotReloadOnExcludingPattern(t *testing.T) { - watch := []string{"./testdata/**/*.php"} + watch := []string{filepath.Join("testdata", "**", "*.php")} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, minTimesToPollForChanges) @@ -50,7 +50,9 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re // now we spam file updates and check if the request counter resets for range limit { - updateTestFile("./testdata/files/test.txt", "updated", t) + dir, _ := filepath.Abs("./testdata") + updateTestFile(t, filepath.Join(dir, "files", "test.txt"), "updated") + t.Log(filepath.Join(dir, "files", "test.txt")) time.Sleep(pollingTime * time.Millisecond) body, _ := testGet("http://example.com/worker-with-counter.php", handler, t) if body == "requests:1" { @@ -61,7 +63,7 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re return false } -func updateTestFile(fileName string, content string, t *testing.T) { +func updateTestFile(t *testing.T, fileName string, content string) { absFileName, err := filepath.Abs(fileName) require.NoError(t, err) From d91e8ba0ab0560ae7474b2f986e6acbd14e8bfe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 11 Jan 2026 01:27:18 +0100 Subject: [PATCH 07/43] cleanup --- internal/watcher/pattern.go | 3 ++- watcher_test.go | 9 ++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 93ae26d692..285594df33 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -44,8 +44,9 @@ func (p *pattern) parse() (err error) { splitPattern := strings.Split(absPattern, string(filepath.Separator)) patternWithoutDir := "" for i, part := range splitPattern { + // add a \ after the drive letter on Windows to force filepath.Join to work as expected if i == 0 && runtime.GOOS == "windows" { - splitPattern[i] = splitPattern[0] + "\\" + splitPattern[0] = splitPattern[0] + "\\" } isFilename := i == len(splitPattern)-1 && strings.Contains(part, ".") diff --git a/watcher_test.go b/watcher_test.go index 59c7c1b253..cf26956bb6 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -63,15 +63,10 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re return false } -func updateTestFile(t *testing.T, fileName string, content string) { +func updateTestFile(t *testing.T, fileName, content string) { absFileName, err := filepath.Abs(fileName) require.NoError(t, err) - dirName := filepath.Dir(absFileName) - if _, err = os.Stat(dirName); os.IsNotExist(err) { - err = os.MkdirAll(dirName, 0700) - } - require.NoError(t, err) - + require.NoError(t, os.MkdirAll(filepath.Dir(absFileName), 0700)) require.NoError(t, os.WriteFile(absFileName, []byte(content), 0644)) } From cc1be56b214d2b3d08a36388fa0d8d997d2c2739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 11:11:22 +0100 Subject: [PATCH 08/43] improve watcher tests (wip) --- internal/watcher/pattern.go | 29 +++++------ internal/watcher/pattern_test.go | 88 ++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 43 deletions(-) diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 285594df33..404d3fa2b8 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -5,13 +5,14 @@ package watcher import ( "log/slog" "path/filepath" - "runtime" "strings" "github.com/dunglas/frankenphp/internal/fastabs" "github.com/e-dant/watcher/watcher-go" ) +const sep = string(filepath.Separator) + type pattern struct { patternGroup *PatternGroup value string @@ -40,15 +41,13 @@ func (p *pattern) parse() (err error) { p.value = absPattern + volumeName := filepath.VolumeName(absPattern) + absPattern = strings.TrimPrefix(absPattern, volumeName) + // then we split the pattern to determine where the directory ends and the pattern starts - splitPattern := strings.Split(absPattern, string(filepath.Separator)) + splitPattern := strings.Split(absPattern, sep) patternWithoutDir := "" for i, part := range splitPattern { - // add a \ after the drive letter on Windows to force filepath.Join to work as expected - if i == 0 && runtime.GOOS == "windows" { - splitPattern[0] = splitPattern[0] + "\\" - } - isFilename := i == len(splitPattern)-1 && strings.Contains(part, ".") isGlobCharacter := strings.ContainsAny(part, "[*?{") @@ -63,14 +62,14 @@ func (p *pattern) parse() (err error) { // now we split the pattern according to the recursive '**' syntax p.parsedValues = strings.Split(patternWithoutDir, "**") for i, pp := range p.parsedValues { - p.parsedValues[i] = strings.Trim(pp, string(filepath.Separator)) + p.parsedValues[i] = strings.Trim(pp, sep) } // remove the trailing separator and add leading separator (except on Windows) - if runtime.GOOS == "windows" { - p.value = strings.Trim(p.value, string(filepath.Separator)) + if volumeName == "" { + p.value = sep + strings.Trim(p.value, sep) } else { - p.value = string(filepath.Separator) + strings.Trim(p.value, string(filepath.Separator)) + p.value = volumeName + sep + strings.Trim(p.value, sep) } // try to canonicalize the path @@ -133,7 +132,7 @@ func (p *pattern) isValidPattern(fileName string) bool { } // remove the directory path and separator from the filename - fileNameWithoutDir := strings.TrimPrefix(strings.TrimPrefix(fileName, p.value), string(filepath.Separator)) + fileNameWithoutDir := strings.TrimPrefix(strings.TrimPrefix(fileName, p.value), sep) // if the pattern has size 1 we can match it directly against the filename if len(p.parsedValues) == 1 { @@ -144,12 +143,12 @@ func (p *pattern) isValidPattern(fileName string) bool { } func (p *pattern) matchPatterns(fileName string) bool { - partsToMatch := strings.Split(fileName, string(filepath.Separator)) + partsToMatch := strings.Split(fileName, sep) cursor := 0 // if there are multiple parsedValues due to '**' we need to match them individually for i, pattern := range p.parsedValues { - patternSize := strings.Count(pattern, string(filepath.Separator)) + 1 + patternSize := strings.Count(pattern, sep) + 1 // if we are at the last pattern we will start matching from the end of the filename if i == len(p.parsedValues)-1 { @@ -167,7 +166,7 @@ func (p *pattern) matchPatterns(fileName string) bool { } cursor = j - subPattern := strings.Join(partsToMatch[j:j+patternSize], string(filepath.Separator)) + subPattern := strings.Join(partsToMatch[j:j+patternSize], sep) if matchCurlyBracePattern(pattern, subPattern) { cursor = j + patternSize - 1 diff --git a/internal/watcher/pattern_test.go b/internal/watcher/pattern_test.go index 25b4dd58da..365675e6fe 100644 --- a/internal/watcher/pattern_test.go +++ b/internal/watcher/pattern_test.go @@ -4,6 +4,7 @@ package watcher import ( "path/filepath" + "strings" "testing" "github.com/e-dant/watcher/watcher-go" @@ -11,16 +12,42 @@ import ( "github.com/stretchr/testify/require" ) +func normalizePath(t *testing.T, path string) string { + t.Helper() + + if filepath.Separator == '/' { + return path + } + + path = filepath.FromSlash(path) + if strings.HasPrefix(path, "/") { + return "C:\\"+path[1:] + } + + return path +} + +func newPattern(t *testing.T, value string) pattern { + t.Helper() + + p := pattern{value: normalizePath(t, value)} + require.NoError(t, p.parse()) + + return p +} + func TestDisallowOnEventTypeBiggerThan3(t *testing.T) { - w := pattern{value: "/some/path"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t, "/some/path") assert.False(t, w.allowReload(&watcher.Event{PathName: "/some/path/watch-me.php", EffectType: watcher.EffectTypeOwner})) } func TestDisallowOnPathTypeBiggerThan2(t *testing.T) { - w := pattern{value: "/some/path"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t, "/some/path") assert.False(t, w.allowReload(&watcher.Event{PathName: "/some/path/watch-me.php", PathType: watcher.PathTypeSymLink})) } @@ -77,7 +104,7 @@ func TestValidRecursiveDirectories(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } @@ -98,7 +125,7 @@ func TestInvalidRecursiveDirectories(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -122,7 +149,7 @@ func TestValidNonRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } @@ -145,7 +172,7 @@ func TestInValidNonRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -170,7 +197,7 @@ func TestValidRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } @@ -198,7 +225,7 @@ func TestInvalidRecursiveFilePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } @@ -225,13 +252,14 @@ func TestValidDirectoryPatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } func TestInvalidDirectoryPatterns(t *testing.T) { t.Parallel() + data := []struct { pattern string dir string @@ -254,12 +282,14 @@ func TestInvalidDirectoryPatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } } func TestValidCurlyBracePatterns(t *testing.T) { + t.Parallel() + data := []struct { pattern string dir string @@ -282,12 +312,14 @@ func TestValidCurlyBracePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.dir) }) } } func TestInvalidCurlyBracePatterns(t *testing.T) { + t.Parallel() + data := []struct { pattern string dir string @@ -306,15 +338,15 @@ func TestInvalidCurlyBracePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - shouldNotMatch(t, d.pattern, d.dir) + assertPatternNotMatch(t, d.pattern, d.dir) }) } - } func TestAnAssociatedEventTriggersTheWatcher(t *testing.T) { - w := pattern{value: "/**/*.php"} - require.NoError(t, w.parse()) + t.Parallel() + + w := newPattern(t,"/**/*.php") w.events = make(chan eventHolder) e := &watcher.Event{PathName: "/path/temporary_file", AssociatedPathName: "/path/file.php"} @@ -324,34 +356,34 @@ func TestAnAssociatedEventTriggersTheWatcher(t *testing.T) { } func relativeDir(t *testing.T, relativePath string) string { + t.Helper() + dir, err := filepath.Abs("./" + relativePath) assert.NoError(t, err) + return dir } func hasDir(t *testing.T, p string, dir string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.Equal(t, dir, w.value) + assert.Equal(t, normalizePath(t, dir), normalizePath(t, w.value)) } -func shouldMatch(t *testing.T, p string, fileName string) { +func assertPatternMatch(t *testing.T, p, fileName string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.True(t, w.allowReload(&watcher.Event{PathName: fileName})) + assert.True(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } -func shouldNotMatch(t *testing.T, p string, fileName string) { +func assertPatternNotMatch(t *testing.T, p, fileName string) { t.Helper() - w := pattern{value: p} - require.NoError(t, w.parse()) + w := newPattern(t, p) - assert.False(t, w.allowReload(&watcher.Event{PathName: fileName})) + assert.False(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } From 5b39fa8c7aa7fd1fe1ca4de5d7a8acc8cc02239f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 14:42:22 +0100 Subject: [PATCH 09/43] all tests are green now! --- internal/testcli/main.go | 2 ++ internal/watcher/pattern.go | 8 ++++---- internal/watcher/pattern_test.go | 26 +++++++++++++------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/internal/testcli/main.go b/internal/testcli/main.go index c03c836c4d..60c2675f11 100644 --- a/internal/testcli/main.go +++ b/internal/testcli/main.go @@ -1,3 +1,5 @@ +//go:build !windows + package main import ( diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index 404d3fa2b8..c3f0e6be8e 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -41,11 +41,11 @@ func (p *pattern) parse() (err error) { p.value = absPattern - volumeName := filepath.VolumeName(absPattern) - absPattern = strings.TrimPrefix(absPattern, volumeName) + volumeName := filepath.VolumeName(p.value) + p.value = strings.TrimPrefix(p.value, volumeName) // then we split the pattern to determine where the directory ends and the pattern starts - splitPattern := strings.Split(absPattern, sep) + splitPattern := strings.Split(p.value, sep) patternWithoutDir := "" for i, part := range splitPattern { isFilename := i == len(splitPattern)-1 && strings.Contains(part, ".") @@ -126,7 +126,7 @@ func (p *pattern) isValidPattern(fileName string) bool { return false } - // first we remove the dir from the file name + // first we remove the file from the file name if !strings.HasPrefix(fileName, p.value) { return false } diff --git a/internal/watcher/pattern_test.go b/internal/watcher/pattern_test.go index 365675e6fe..44edd3df74 100644 --- a/internal/watcher/pattern_test.go +++ b/internal/watcher/pattern_test.go @@ -20,8 +20,8 @@ func normalizePath(t *testing.T, path string) string { } path = filepath.FromSlash(path) - if strings.HasPrefix(path, "/") { - return "C:\\"+path[1:] + if strings.HasPrefix(path, "\\") { + path = "C:\\" + path[1:] } return path @@ -86,7 +86,7 @@ func TestValidRecursiveDirectories(t *testing.T) { data := []struct { pattern string - dir string + file string }{ {"/path", "/path/file.php"}, {"/path", "/path/subpath/file.php"}, @@ -104,7 +104,7 @@ func TestValidRecursiveDirectories(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - assertPatternMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.file) }) } } @@ -292,7 +292,7 @@ func TestValidCurlyBracePatterns(t *testing.T) { data := []struct { pattern string - dir string + file string }{ {"/path/*.{php}", "/path/file.php"}, {"/path/*.{php,twig}", "/path/file.php"}, @@ -312,7 +312,7 @@ func TestValidCurlyBracePatterns(t *testing.T) { t.Run(d.pattern, func(t *testing.T) { t.Parallel() - assertPatternMatch(t, d.pattern, d.dir) + assertPatternMatch(t, d.pattern, d.file) }) } } @@ -331,7 +331,7 @@ func TestInvalidCurlyBracePatterns(t *testing.T) { {"/path/{dir1,dir2}/**/*.php", "/path/dir1/subpath/file.txt"}, {"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir1/ac.php"}, {"/path/{}/{a,b}{a,b}.php", "/path/dir1/ac.php"}, - {"/path/}dir{/{a,b}{a,b}.php", "/path/dir1/aa.php"}, + {"/path/}file{/{a,b}{a,b}.php", "/path/dir1/aa.php"}, } for _, d := range data { @@ -346,10 +346,10 @@ func TestInvalidCurlyBracePatterns(t *testing.T) { func TestAnAssociatedEventTriggersTheWatcher(t *testing.T) { t.Parallel() - w := newPattern(t,"/**/*.php") + w := newPattern(t, "/**/*.php") w.events = make(chan eventHolder) - e := &watcher.Event{PathName: "/path/temporary_file", AssociatedPathName: "/path/file.php"} + e := &watcher.Event{PathName: normalizePath(t, "/path/temporary_file"), AssociatedPathName: normalizePath(t, "/path/file.php")} go w.handle(e) assert.Equal(t, e, (<-w.events).event) @@ -367,15 +367,15 @@ func relativeDir(t *testing.T, relativePath string) string { func hasDir(t *testing.T, p string, dir string) { t.Helper() - w := newPattern(t, p) + w := newPattern(t, p) - assert.Equal(t, normalizePath(t, dir), normalizePath(t, w.value)) + assert.Equal(t, normalizePath(t, dir), w.value) } func assertPatternMatch(t *testing.T, p, fileName string) { t.Helper() - w := newPattern(t, p) + w := newPattern(t, p) assert.True(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } @@ -383,7 +383,7 @@ func assertPatternMatch(t *testing.T, p, fileName string) { func assertPatternNotMatch(t *testing.T, p, fileName string) { t.Helper() - w := newPattern(t, p) + w := newPattern(t, p) assert.False(t, w.allowReload(&watcher.Event{PathName: normalizePath(t, fileName)})) } From 4799c3aeb5570cd4b9a12121215b629226b264b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 14:52:26 +0100 Subject: [PATCH 10/43] cleanup --- internal/extgen/stub_test.go | 3 +-- internal/watcher/pattern.go | 2 +- internal/watcher/pattern_test.go | 2 +- watcher_test.go | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/extgen/stub_test.go b/internal/extgen/stub_test.go index 172d4966ad..67b4203eac 100644 --- a/internal/extgen/stub_test.go +++ b/internal/extgen/stub_test.go @@ -2,7 +2,6 @@ package extgen import ( "path/filepath" - "runtime" "strings" "testing" @@ -538,7 +537,7 @@ func TestStubGenerator_FileStructure(t *testing.T) { assert.NoError(t, err, "buildContent() failed") sep := "\n" - if runtime.GOOS == "windows" { + if filepath.Separator == '\\' { sep = "\r\n" } diff --git a/internal/watcher/pattern.go b/internal/watcher/pattern.go index c3f0e6be8e..be045f747d 100644 --- a/internal/watcher/pattern.go +++ b/internal/watcher/pattern.go @@ -126,7 +126,7 @@ func (p *pattern) isValidPattern(fileName string) bool { return false } - // first we remove the file from the file name + // first we remove the dir from the file name if !strings.HasPrefix(fileName, p.value) { return false } diff --git a/internal/watcher/pattern_test.go b/internal/watcher/pattern_test.go index 44edd3df74..8ee3907c7e 100644 --- a/internal/watcher/pattern_test.go +++ b/internal/watcher/pattern_test.go @@ -331,7 +331,7 @@ func TestInvalidCurlyBracePatterns(t *testing.T) { {"/path/{dir1,dir2}/**/*.php", "/path/dir1/subpath/file.txt"}, {"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir1/ac.php"}, {"/path/{}/{a,b}{a,b}.php", "/path/dir1/ac.php"}, - {"/path/}file{/{a,b}{a,b}.php", "/path/dir1/aa.php"}, + {"/path/}dir{/{a,b}{a,b}.php", "/path/dir1/aa.php"}, } for _, d := range data { diff --git a/watcher_test.go b/watcher_test.go index cf26956bb6..cb3736e58a 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -52,7 +52,6 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re for range limit { dir, _ := filepath.Abs("./testdata") updateTestFile(t, filepath.Join(dir, "files", "test.txt"), "updated") - t.Log(filepath.Join(dir, "files", "test.txt")) time.Sleep(pollingTime * time.Millisecond) body, _ := testGet("http://example.com/worker-with-counter.php", handler, t) if body == "requests:1" { From 5da1dc029aab82ad14db9b13ff2660257d50e803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 14:55:55 +0100 Subject: [PATCH 11/43] test forward slashes on Windows --- watcher_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/watcher_test.go b/watcher_test.go index cb3736e58a..c3a3b570b8 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -24,7 +24,8 @@ const minTimesToPollForChanges = 3 const maxTimesToPollForChanges = 60 func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { - watch := []string{filepath.Join("testdata", "**", "*.txt")} + //watch := []string{filepath.Join("testdata", "**", "*.txt")} + watch := []string{"testdata/**/*.txt"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, maxTimesToPollForChanges) @@ -33,7 +34,7 @@ func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { } func TestWorkersShouldNotReloadOnExcludingPattern(t *testing.T) { - watch := []string{filepath.Join("testdata", "**", "*.php")} + watch := []string{"testdata/**/*.php"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, minTimesToPollForChanges) From 12585fdd80df16d70ad0d874b8ca6518906ef1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 16:32:37 +0100 Subject: [PATCH 12/43] upgrade watcher to simplify Windows linking --- caddy/go.mod | 2 +- caddy/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index ae29d7565c..60fffe9c18 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -62,7 +62,7 @@ require ( github.com/dunglas/skipfilter v1.0.0 // indirect github.com/dunglas/vulcain v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 // indirect + github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/caddy/go.sum b/caddy/go.sum index f190ab0fde..490909fafb 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -158,8 +158,8 @@ github.com/dunglas/vulcain/caddy v1.2.1/go.mod h1:8QrmLTfURmW2VgjTR6Gb9a53FrZjsp github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 h1:azdbL1jat1ReH6wQrP+cawhsyXRLFRuCo0hGBUQHnv4= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/go.mod b/go.mod index 56bb84b591..5140b7e43b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ retract v1.0.0-rc.1 // Human error require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/dunglas/mercure v0.21.4 - github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 + github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 github.com/maypok86/otter/v2 v2.2.1 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index af58b44cb3..83dd4e2a31 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/dunglas/mercure v0.21.4 h1:mXPXHfB+4cYfFFCRRDY198mfef5+MQcdCpUnAHBUW2 github.com/dunglas/mercure v0.21.4/go.mod h1:l/dglCjp/OQx8/quRyceRPx2hqZQ3CNviwoLMRQiJ/k= github.com/dunglas/skipfilter v1.0.0 h1:JG9SgGg4n6BlFwuTYzb9RIqjH7PfwszvWehanrYWPF4= github.com/dunglas/skipfilter v1.0.0/go.mod h1:ryhr8j7CAHSjzeN7wI6YEuwoArQ3OQmRqWWVCEAfb9w= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146 h1:h3vVM6X45PK0mAk8NqiYNQGXTyhvXy1HQ5GhuQN4eeA= -github.com/e-dant/watcher/watcher-go v0.0.0-20251208164151-f88ec3b7e146/go.mod h1:sVUOkwtftoj71nnJRG2S0oWNfXFdKpz/M9vK0z06nmM= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 h1:azdbL1jat1ReH6wQrP+cawhsyXRLFRuCo0hGBUQHnv4= +github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24/go.mod h1:PmV4IVmBJVqT2NcfTGN4+sZ+qGe3PA0qkphAtOHeFG0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From 25d9a98dc6b6cbeee06f62c7cd67b82918563499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Jan 2026 23:45:13 +0100 Subject: [PATCH 13/43] add php-cli support --- caddy/php-cli.go | 2 -- cli.go | 2 -- cli_test.go | 2 -- frankenphp.c | 2 -- frankenphp.h | 2 -- 5 files changed, 10 deletions(-) diff --git a/caddy/php-cli.go b/caddy/php-cli.go index bed3042907..4e76ff147a 100644 --- a/caddy/php-cli.go +++ b/caddy/php-cli.go @@ -1,5 +1,3 @@ -//go:build !windows - package caddy import ( diff --git a/cli.go b/cli.go index ce5f344269..ffddb55796 100644 --- a/cli.go +++ b/cli.go @@ -1,5 +1,3 @@ -//go:build !windows - // TODO: ignored on Windows for now (even if it should work with a custom PHP build), // because static builds of the embed SAPI aren't available yet and php.exe is ship with // the standard PHP distribution. diff --git a/cli_test.go b/cli_test.go index f2c47b38af..f9ee03fea2 100644 --- a/cli_test.go +++ b/cli_test.go @@ -1,5 +1,3 @@ -//go:build !windows - package frankenphp_test import ( diff --git a/frankenphp.c b/frankenphp.c index 1e8ebc4ff4..d465299509 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1196,7 +1196,6 @@ static void sapi_cli_register_variables(zval *track_vars_array) /* {{{ */ } /* }}} */ -#ifndef ZEND_WIN32 static void *execute_script_cli(void *arg) { void *exit_status; bool eval = (bool)arg; @@ -1258,7 +1257,6 @@ int frankenphp_execute_script_cli(char *script, int argc, char **argv, return (intptr_t)exit_status; } -#endif int frankenphp_reset_opcache(void) { zend_function *opcache_reset = diff --git a/frankenphp.h b/frankenphp.h index 133ab07336..51833cf6fc 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -87,10 +87,8 @@ bool frankenphp_shutdown_dummy_request(void); int frankenphp_execute_script(char *file_name); void frankenphp_update_local_thread_context(bool is_worker); -#ifndef ZEND_WIN32 int frankenphp_execute_script_cli(char *script, int argc, char **argv, bool eval); -#endif void frankenphp_register_variables_from_request_info( zval *track_vars_array, zend_string *content_type, From c9ab80e74553b2baba626698a4a797eca639ffbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 13 Jan 2026 00:01:17 +0100 Subject: [PATCH 14/43] review --- cli.go | 4 ---- internal/extgen/utils_test.go | 3 +-- internal/testcli/main.go | 2 -- internal/testext/exttest.go | 3 +-- scaling_test.go | 2 +- watcher_test.go | 8 +++----- 6 files changed, 6 insertions(+), 16 deletions(-) diff --git a/cli.go b/cli.go index ffddb55796..96821a2392 100644 --- a/cli.go +++ b/cli.go @@ -1,7 +1,3 @@ -// TODO: ignored on Windows for now (even if it should work with a custom PHP build), -// because static builds of the embed SAPI aren't available yet and php.exe is ship with -// the standard PHP distribution. - package frankenphp // #include "frankenphp.h" diff --git a/internal/extgen/utils_test.go b/internal/extgen/utils_test.go index d6700a2def..1cb9092b9a 100644 --- a/internal/extgen/utils_test.go +++ b/internal/extgen/utils_test.go @@ -3,7 +3,6 @@ package extgen import ( "os" "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -69,7 +68,7 @@ func TestWriteFile(t *testing.T) { assert.NoError(t, err, "Failed to stat file") expectedMode := os.FileMode(0644) - if runtime.GOOS == "windows" { + if filepath.Separator == '\\' { expectedMode = os.FileMode(0666) } diff --git a/internal/testcli/main.go b/internal/testcli/main.go index 60c2675f11..c03c836c4d 100644 --- a/internal/testcli/main.go +++ b/internal/testcli/main.go @@ -1,5 +1,3 @@ -//go:build !windows - package main import ( diff --git a/internal/testext/exttest.go b/internal/testext/exttest.go index a088ef3105..bbbe9bbd22 100644 --- a/internal/testext/exttest.go +++ b/internal/testext/exttest.go @@ -8,8 +8,7 @@ package testext // #cgo unix LDFLAGS: -L/usr/local/lib -L/usr/lib -lphp -lm -lutil // #cgo linux LDFLAGS: -ldl -lresolv // #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/lib -L/opt/homebrew/lib -L/opt/homebrew/opt/libiconv/lib -liconv -ldl -// #cgo windows CFLAGS: -IC:\vcpkg\installed\x64-windows\include -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 -// #cgo windows LDFLAGS: -LC:\vcpkg\installed\x64-windows\lib +// #cgo windows CFLAGS: -D_WINDOWS -DWINDOWS=1 -DZEND_WIN32=1 -DPHP_WIN32=1 -DWIN32 -D_MBCS -D_USE_MATH_DEFINES -DNDebug -DNDEBUG -DZEND_DEBUG=0 -DZTS=1 -DFD_SETSIZE=256 // #include "extension.h" import "C" import ( diff --git a/scaling_test.go b/scaling_test.go index 2397c665c4..c79ae6e044 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -34,7 +34,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) { t.Cleanup(Shutdown) workerName := "worker1" - workerPath := filepath.Join(testDataPath, "/transition-worker-1.php") + workerPath := filepath.Join(testDataPath, "transition-worker-1.php") assert.NoError(t, Init( WithNumThreads(2), WithMaxThreads(3), diff --git a/watcher_test.go b/watcher_test.go index c3a3b570b8..6ccca16aec 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -24,8 +24,7 @@ const minTimesToPollForChanges = 3 const maxTimesToPollForChanges = 60 func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { - //watch := []string{filepath.Join("testdata", "**", "*.txt")} - watch := []string{"testdata/**/*.txt"} + watch := []string{"./testdata/**/*.txt"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, maxTimesToPollForChanges) @@ -34,7 +33,7 @@ func TestWorkersShouldReloadOnMatchingPattern(t *testing.T) { } func TestWorkersShouldNotReloadOnExcludingPattern(t *testing.T) { - watch := []string{"testdata/**/*.php"} + watch := []string{"./testdata/**/*.php"} runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { requestBodyHasReset := pollForWorkerReset(t, handler, minTimesToPollForChanges) @@ -51,8 +50,7 @@ func pollForWorkerReset(t *testing.T, handler func(http.ResponseWriter, *http.Re // now we spam file updates and check if the request counter resets for range limit { - dir, _ := filepath.Abs("./testdata") - updateTestFile(t, filepath.Join(dir, "files", "test.txt"), "updated") + updateTestFile(t, filepath.Join(".", "testdata", "files", "test.txt"), "updated") time.Sleep(pollingTime * time.Millisecond) body, _ := testGet("http://example.com/worker-with-counter.php", handler, t) if body == "requests:1" { From 4bbe3623cd4a32691737f42f7b99374d5b8d0eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 13 Jan 2026 17:42:04 +0100 Subject: [PATCH 15/43] GitHub Actions worklow --- .github/workflows/windows.yaml | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 .github/workflows/windows.yaml diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml new file mode 100644 index 0000000000..0d75ff93d1 --- /dev/null +++ b/.github/workflows/windows.yaml @@ -0,0 +1,184 @@ +name: Build Windows release + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +on: + pull_request: + branches: + - main + paths: + - "docker-bake.hcl" + - ".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 + 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: Checkout Code + uses: actions/checkout@v6 + with: + path: frankenphp + persist-credentials: false + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: "1.26.0-rc.1" + cache-dependency-path: | + go.sum + caddy/go.sum + cache: false + check-latest: true + + - name: Update Vcpkg + run: | + cd "$env:VCPKG_INSTALLATION_ROOT" + git pull + .\bootstrap-vcpkg.bat + + - name: Cache Vcpkg Packages + uses: actions/cache@v5 + with: + path: | + ${{ env.VCPKG_INSTALLATION_ROOT }}\installed + ${{ env.VCPKG_INSTALLATION_ROOT }}\downloads + key: ${{ runner.os }}-vcpkg-libs-${{ hashFiles('vcpkg/ports/**') }} + restore-keys: | + ${{ runner.os }}-vcpkg-libs- + lookup-only: true + + - name: Install Vcpkg Libraries + run: '& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install pthreads brotli --triplet x64-windows' + + - 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" --dir "$env:TEMP" + + tar -xf "$env:TEMP\watcher-x86_64-pc-windows-msvc.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 + Move-Item -Path .\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 { [version]($_ -replace 'php-', '' -replace '-Win32-vs17-x64.zip', '') } | Select-Object -Last 1 + + $version = $latestFile -replace 'php-', '' -replace '-Win32-vs17-x64.zip', '' + 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" + + Invoke-WebRequest -Uri "$env:PHP_DOWNLOAD_BASE/$phpZip" -OutFile "$env:TEMP\php.zip" + Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "$env:GITHUB_WORKSPACE\php-bin" + + 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:VCPKG_INSTALLATION_ROOT\installed\x64-windows" + $watcherRoot = "$env:GITHUB_WORKSPACE\watcher" + $phpBin = "$env:GITHUB_WORKSPACE\php-bin" + $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 "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Append + echo "$env:VCPKG_INSTALLATION_ROOT\watcher" | Out-File -FilePath $env:GITHUB_PATH -Append + echo "$env:GITHUB_WORKSPACE\php-bin" | Out-File -FilePath $env:GITHUB_PATH -Append + + echo "CGO_CFLAGS=-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: go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx + working-directory: ${{ env.GITHUB_WORKSPACE }}/frankenphp + + - name: Create Zip Archive + run: | + Move-Item frankenphp\caddy\frankenphp\frankenphp.exe php-bin + Move-Item watcher\libwatcher-c.dll php-bin + Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\brotlienc.dll" php-bin + Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\brotlicommon.dll" php-bin + Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\pthreadVC3.dll" php-bin + + $version = $env:PHP_VERSION + $zipName = "frankenphp-php-$version-Win32-vs17-x64.zip" + + # TODO: create a single folder inside the zip + Compress-Archive -Path "php-bin\*" -DestinationPath "$zipName" + + echo "ASSET_NAME=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Upload Artifact + if: github.event_name != 'release' + uses: actions/upload-artifact@v6 + with: + name: ${{ env.ASSET_NAME }} + path: ${{ env.GITHUB_WORKSPACE }}\${{ env.ASSET_PATH }} + + - name: Upload Release Asset + if: github.event_name == 'release' + run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:ASSET_PATH" --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 -ldflags '-extldflags="-fuse-ld=lld"' ./... + cd caddy + go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... + working-directory: ${{ env.GITHUB_WORKSPACE }}/frankenphp From f08f4bbdab59f9c2d60d7291cbc6e07fcf1605d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 15 Jan 2026 23:50:26 +0100 Subject: [PATCH 16/43] wip --- .github/workflows/windows.yaml | 17 ++++++----------- vcpkg.json | 6 ++++++ 2 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 vcpkg.json diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 0d75ff93d1..8b1d4c2597 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -60,17 +60,11 @@ jobs: with: go-version: "1.26.0-rc.1" cache-dependency-path: | - go.sum - caddy/go.sum + frankenphp/go.sum + frankenphp/caddy/go.sum cache: false check-latest: true - - name: Update Vcpkg - run: | - cd "$env:VCPKG_INSTALLATION_ROOT" - git pull - .\bootstrap-vcpkg.bat - - name: Cache Vcpkg Packages uses: actions/cache@v5 with: @@ -83,16 +77,17 @@ jobs: lookup-only: true - name: Install Vcpkg Libraries - run: '& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install pthreads brotli --triplet x64-windows' + working-directory: frankenphp + run: '& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" 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" --dir "$env:TEMP" + gh release download $latestTag --repo e-dant/watcher --pattern "*x86_64-pc-windows-msvc.tar" --dir "$env:TEMP" -O watcher.tar - tar -xf "$env:TEMP\watcher-x86_64-pc-windows-msvc.tar" -C "$env:GITHUB_WORKSPACE" + tar -xf "$env:TEMP\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 diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000000..728a385652 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,6 @@ +{ + "dependencies": [ + "brotli", + "pthreads" + ] +} From 6bab64f9912fd4fde89427c9db7a1a8b3f80e969 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 09:00:18 +0100 Subject: [PATCH 17/43] work on windows workflow --- .github/workflows/windows.yaml | 37 +++++++++++++++++----------------- vcpkg.json | 5 +---- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 8b1d4c2597..63f101699d 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -69,43 +69,42 @@ jobs: uses: actions/cache@v5 with: path: | - ${{ env.VCPKG_INSTALLATION_ROOT }}\installed + frankenphp\vcpkg_installed ${{ env.VCPKG_INSTALLATION_ROOT }}\downloads - key: ${{ runner.os }}-vcpkg-libs-${{ hashFiles('vcpkg/ports/**') }} + key: ${{ runner.os }}-vcpkg-libs-${{ hashFiles('frankenphp/vcpkg.json') }} restore-keys: | ${{ runner.os }}-vcpkg-libs- - lookup-only: true - name: Install Vcpkg Libraries working-directory: frankenphp - run: '& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install' + 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" --dir "$env:TEMP" -O watcher.tar + gh release download $latestTag --repo e-dant/watcher --pattern "*x86_64-pc-windows-msvc.tar" -O watcher.tar - tar -xf "$env:TEMP\watcher.tar" -C "$env:GITHUB_WORKSPACE" + 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 - Move-Item -Path .\watcher-c.h -Destination .\watcher\wtr\watcher-c.h + 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" } + $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 { [version]($_ -replace 'php-', '' -replace '-Win32-vs17-x64.zip', '') } | Select-Object -Last 1 + $latestFile = $links | Sort-Object { if ($_ -match '(\d+\.\d+\.\d+)') { [version]$matches[1] } } | Select-Object -Last 1 - $version = $latestFile -replace 'php-', '' -replace '-Win32-vs17-x64.zip', '' + $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 @@ -121,14 +120,14 @@ jobs: - name: Prepare env run: | - $vcpkgRoot = "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows" + $vcpkgRoot = "$env:GITHUB_WORKSPACE\frankenphp\vcpkg_installed\x64-windows" $watcherRoot = "$env:GITHUB_WORKSPACE\watcher" $phpBin = "$env:GITHUB_WORKSPACE\php-bin" $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 "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Append - echo "$env:VCPKG_INSTALLATION_ROOT\watcher" | Out-File -FilePath $env:GITHUB_PATH -Append + echo "$vcpkgRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Append + echo "$env:GITHUB_WORKSPACE\watcher" | Out-File -FilePath $env:GITHUB_PATH -Append echo "$env:GITHUB_WORKSPACE\php-bin" | Out-File -FilePath $env:GITHUB_PATH -Append echo "CGO_CFLAGS=-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 @@ -136,15 +135,15 @@ jobs: - name: Build FrankenPHP run: go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx - working-directory: ${{ env.GITHUB_WORKSPACE }}/frankenphp + working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive run: | Move-Item frankenphp\caddy\frankenphp\frankenphp.exe php-bin Move-Item watcher\libwatcher-c.dll php-bin - Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\brotlienc.dll" php-bin - Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\brotlicommon.dll" php-bin - Move-Item "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin\pthreadVC3.dll" php-bin + Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll" php-bin + Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll" php-bin + Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll" php-bin $version = $env:PHP_VERSION $zipName = "frankenphp-php-$version-Win32-vs17-x64.zip" @@ -176,4 +175,4 @@ jobs: go test -ldflags '-extldflags="-fuse-ld=lld"' ./... cd caddy go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... - working-directory: ${{ env.GITHUB_WORKSPACE }}/frankenphp + working-directory: ${{ env.GITHUB_WORKSPACE }}\frankenphp diff --git a/vcpkg.json b/vcpkg.json index 728a385652..79fc2096b6 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,3 @@ { - "dependencies": [ - "brotli", - "pthreads" - ] + "dependencies": ["brotli", "pthreads"] } From 36e34922eb9ebfb59ffd05bdc969d705256b9478 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 10:18:07 +0100 Subject: [PATCH 18/43] linter --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 63f101699d..e5da478720 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -77,7 +77,7 @@ jobs: - name: Install Vcpkg Libraries working-directory: frankenphp - run: 'vcpkg install' + run: "vcpkg install" - name: Download Watcher run: | From 684b5e0ac5a87de1db078464accd24b5961f5b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 10:56:54 +0100 Subject: [PATCH 19/43] fix env var --- .github/workflows/windows.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e5da478720..cbd1395e51 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -158,11 +158,12 @@ jobs: uses: actions/upload-artifact@v6 with: name: ${{ env.ASSET_NAME }} - path: ${{ env.GITHUB_WORKSPACE }}\${{ env.ASSET_PATH }} + path: ${{ env.GITHUB_WORKSPACE }}\${{ env.ASSET_NAME }} + compression-level: 0 - name: Upload Release Asset if: github.event_name == 'release' - run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:ASSET_PATH" --clobber + run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:ASSET_NAME" --clobber env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} From 25b4b9f895d8027a2e2cea19480700ccb215c73a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 11:29:48 +0100 Subject: [PATCH 20/43] use xcaddy --- .github/workflows/windows.yaml | 43 +++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index cbd1395e51..e5a026b9e5 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -55,16 +55,31 @@ jobs: 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 -Encoding utf8 -Append + - name: Setup Go uses: actions/setup-go@v6 with: go-version: "1.26.0-rc.1" cache-dependency-path: | - frankenphp/go.sum + frankenphp/go.sum frankenphp/caddy/go.sum cache: false check-latest: true + - name: Install xcaddy + run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + - name: Cache Vcpkg Packages uses: actions/cache@v5 with: @@ -134,7 +149,16 @@ jobs: 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: go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx + run: | + $customVersion = "FrankenPHP v$env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" + $ldflags = "-linkmode=external -extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=\`"$customVersion\`"'" + + $env:CGO_ENABLED = "1" + $env:XCADDY_GO_BUILD_FLAGS = "-ldflags=`"$ldflags`" -tags=nobadger,nomysql,nopgx" + + echo $env:XCADDY_GO_BUILD_FLAGS + + xcaddy build --with github.com/dunglas/frankenphp/caddy=../ --with github.com/dunglas/frankenphp=../../ --output frankenphp.exe working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive @@ -145,11 +169,9 @@ jobs: Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll" php-bin Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll" php-bin - $version = $env:PHP_VERSION - $zipName = "frankenphp-php-$version-Win32-vs17-x64.zip" - - # TODO: create a single folder inside the zip - Compress-Archive -Path "php-bin\*" -DestinationPath "$zipName" + $phpVersion = $env:PHP_VERSION + $frankenphpVersion = $env:FRANKENPHP_VERSION + $zipName = "frankenphp-$frankenphpVersion-php-$phpVersion-Win32-vs17-x64.zip" echo "ASSET_NAME=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append @@ -158,8 +180,7 @@ jobs: uses: actions/upload-artifact@v6 with: name: ${{ env.ASSET_NAME }} - path: ${{ env.GITHUB_WORKSPACE }}\${{ env.ASSET_NAME }} - compression-level: 0 + path: php-bin\* - name: Upload Release Asset if: github.event_name == 'release' @@ -177,3 +198,7 @@ jobs: cd caddy go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... working-directory: ${{ env.GITHUB_WORKSPACE }}\frankenphp + + - name: tmate + uses: mxschmitt/action-tmate@v3 + if: failure() From bb3fb4ed78c3df3bcd5075fb74d73171bf8407ef Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 11:40:53 +0100 Subject: [PATCH 21/43] correct path --- .github/workflows/windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e5a026b9e5..8e11fdf302 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -151,7 +151,7 @@ jobs: - name: Build FrankenPHP run: | $customVersion = "FrankenPHP v$env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - $ldflags = "-linkmode=external -extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=\`"$customVersion\`"'" + $ldflags = "-linkmode=external -extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" $env:CGO_ENABLED = "1" $env:XCADDY_GO_BUILD_FLAGS = "-ldflags=`"$ldflags`" -tags=nobadger,nomysql,nopgx" @@ -197,7 +197,7 @@ jobs: go test -ldflags '-extldflags="-fuse-ld=lld"' ./... cd caddy go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... - working-directory: ${{ env.GITHUB_WORKSPACE }}\frankenphp + working-directory: ${{ github.workspace }}\frankenphp - name: tmate uses: mxschmitt/action-tmate@v3 From 4aa0cbbb75f3461758e49e87da0154d04c7c2adb Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 11:52:04 +0100 Subject: [PATCH 22/43] remove tmate action --- .github/workflows/windows.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 8e11fdf302..e876b3eab4 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -198,7 +198,3 @@ jobs: cd caddy go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... working-directory: ${{ github.workspace }}\frankenphp - - - name: tmate - uses: mxschmitt/action-tmate@v3 - if: failure() From d720ab18f34afc0933674e88015d464068b12a5b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 12:03:23 +0100 Subject: [PATCH 23/43] add caddy modules back in --- .github/workflows/windows.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e876b3eab4..6e782fad17 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -158,7 +158,13 @@ jobs: echo $env:XCADDY_GO_BUILD_FLAGS - xcaddy build --with github.com/dunglas/frankenphp/caddy=../ --with github.com/dunglas/frankenphp=../../ --output frankenphp.exe + xcaddy build ` + --with github.com/dunglas/frankenphp/caddy=../ ` + --with github.com/dunglas/frankenphp=../../ ` + --with github.com/dunglas/mercure/caddy ` + --with github.com/dunglas/vulcain/caddy ` + --with github.com/dunglas/caddy-cbrotli ` + --output frankenphp.exe working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive From a7ed5047f29584211a2bdef0a5efdee8cb786c2b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 12:05:17 +0100 Subject: [PATCH 24/43] remove extra v --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 6e782fad17..e8781b6ff2 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -150,7 +150,7 @@ jobs: - name: Build FrankenPHP run: | - $customVersion = "FrankenPHP v$env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" + $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" $ldflags = "-linkmode=external -extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" $env:CGO_ENABLED = "1" From 2e71f67aa0aac69615c488a4cbdecc61e3f3b47c Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 16 Jan 2026 12:15:54 +0100 Subject: [PATCH 25/43] brotlidec missing --- .github/workflows/windows.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e8781b6ff2..6f7316816f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -172,6 +172,7 @@ jobs: Move-Item frankenphp\caddy\frankenphp\frankenphp.exe php-bin Move-Item watcher\libwatcher-c.dll php-bin Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll" php-bin + Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlidec.dll" php-bin Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll" php-bin Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll" php-bin From 734203f4209424a87dd21ee41d76671ff6a54630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 16:59:08 +0100 Subject: [PATCH 26/43] wip --- .github/workflows/windows.yaml | 53 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 6f7316816f..fc8710b31a 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -10,6 +10,7 @@ on: - main paths: - "docker-bake.hcl" + - "vcpkg.json" - ".github/workflows/static.yaml" - "**cgo.go" - "**Dockerfile" @@ -37,6 +38,7 @@ permissions: 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++ @@ -65,23 +67,24 @@ jobs: $frankenphpVersion = $env:GITHUB_SHA } - echo "FRANKENPHP_VERSION=$frankenphpVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "FRANKENPHP_VERSION=$frankenphpVersion" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Setup Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v6 # zizmor: ignore[cache-poisoning] with: go-version: "1.26.0-rc.1" cache-dependency-path: | frankenphp/go.sum frankenphp/caddy/go.sum - cache: false + cache: github.event_name != 'release' check-latest: true - name: Install xcaddy run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - name: Cache Vcpkg Packages - uses: actions/cache@v5 + if: github.event_name != 'release' + uses: actions/cache@v5 # zizmor: ignore[cache-poisoning] with: path: | frankenphp\vcpkg_installed @@ -126,9 +129,13 @@ jobs: $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\php-bin" + 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" @@ -137,13 +144,13 @@ jobs: run: | $vcpkgRoot = "$env:GITHUB_WORKSPACE\frankenphp\vcpkg_installed\x64-windows" $watcherRoot = "$env:GITHUB_WORKSPACE\watcher" - $phpBin = "$env:GITHUB_WORKSPACE\php-bin" + $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 "$env:GITHUB_WORKSPACE\watcher" | Out-File -FilePath $env:GITHUB_PATH -Append - echo "$env:GITHUB_WORKSPACE\php-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=-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 @@ -169,29 +176,27 @@ jobs: - name: Create Zip Archive run: | - Move-Item frankenphp\caddy\frankenphp\frankenphp.exe php-bin - Move-Item watcher\libwatcher-c.dll php-bin - Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlienc.dll" php-bin - Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlidec.dll" php-bin - Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\brotlicommon.dll" php-bin - Move-Item "frankenphp\vcpkg_installed\x64-windows\bin\pthreadVC3.dll" php-bin - - $phpVersion = $env:PHP_VERSION - $frankenphpVersion = $env:FRANKENPHP_VERSION - $zipName = "frankenphp-$frankenphpVersion-php-$phpVersion-Win32-vs17-x64.zip" + 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 - echo "ASSET_NAME=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + Compress-Archive -Path $env:DIR_NAME -DestinationPath "$env:DIR_NAME.zip" - name: Upload Artifact if: github.event_name != 'release' uses: actions/upload-artifact@v6 with: - name: ${{ env.ASSET_NAME }} - path: php-bin\* + name: ${{ env.DIR_NAME }}.zip + path: ${{ env.DIR_NAME }}.zip + compression-level: 0 + if-no-files-found: error - name: Upload Release Asset if: github.event_name == 'release' - run: gh release upload $env:GITHUB_EVENT_RELEASE_TAG_NAME "$env:ASSET_NAME" --clobber + 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 }} @@ -201,7 +206,7 @@ jobs: "opcache.enable=0`r`nopcache.enable_cli=0" | Out-File php.ini $env:PHPRC = Get-Location - go test -ldflags '-extldflags="-fuse-ld=lld"' ./... + go test ./... cd caddy - go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nobadger,nomysql,nopgx ./... + go test ./... working-directory: ${{ github.workspace }}\frankenphp From 4946a177c643cdfbbf60983d57be822d349bd890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 17:14:45 +0100 Subject: [PATCH 27/43] wip --- .github/workflows/windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index fc8710b31a..f05f95233e 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -76,7 +76,7 @@ jobs: cache-dependency-path: | frankenphp/go.sum frankenphp/caddy/go.sum - cache: github.event_name != 'release' + cache: ${{ github.event_name != 'release' }} check-latest: true - name: Install xcaddy @@ -129,7 +129,7 @@ jobs: $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 From 63103cb898608395c576a5e31b9ca39f13aaeea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 17:27:36 +0100 Subject: [PATCH 28/43] with GOFLAGS --- .github/workflows/tests.yaml | 5 +++-- .github/workflows/windows.yaml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1d48784abc..82b783f391 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,6 +19,7 @@ permissions: env: GOTOOLCHAIN: local GOEXPERIMENT: cgocheck2 + GOFLAGS: "-tags=nobadger,nomysql,nopgx" jobs: tests-linux: name: Tests (Linux, PHP ${{ matrix.php-versions }}) @@ -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 @@ -172,4 +173,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 ./... diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index f05f95233e..de3e576682 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -38,7 +38,7 @@ permissions: env: GOTOOLCHAIN: local - GOFLAGS: "-ldflags '-extldflags=-fuse-ld=lld' -tags nobadger,nomysql,nopgx" + GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx" PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases" CC: clang CXX: clang++ From 7c4507aca32ed5804d1745d216185c7536986287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 17:40:12 +0100 Subject: [PATCH 29/43] wip --- .github/workflows/docker.yaml | 4 ++-- .github/workflows/static.yaml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 73d212c6c7..05b9ba2288 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -208,7 +208,7 @@ 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/* @@ -216,7 +216,7 @@ jobs: 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/* diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index cb97a302be..ce384f4d91 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -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/* @@ -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' || '' }} @@ -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/* @@ -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/* @@ -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 }} @@ -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 }} From 8136c69f3752ac61b25bb85fd4332ed02ef76d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 18:07:47 +0100 Subject: [PATCH 30/43] wip --- .github/workflows/static.yaml | 4 ++-- .github/workflows/windows.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index ce384f4d91..2b7a93766a 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -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 diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index de3e576682..0dfe3fa7ec 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -70,8 +70,8 @@ jobs: echo "FRANKENPHP_VERSION=$frankenphpVersion" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Setup Go - uses: actions/setup-go@v6 # zizmor: ignore[cache-poisoning] - with: + uses: actions/setup-go@v6 + with: # zizmor: ignore[cache-poisoning] go-version: "1.26.0-rc.1" cache-dependency-path: | frankenphp/go.sum @@ -207,6 +207,6 @@ jobs: $env:PHPRC = Get-Location go test ./... - cd caddy - go test ./... + #cd caddy + #go test ./... working-directory: ${{ github.workspace }}\frankenphp From 4bf3e92430c0c92acf6d8243abcfde3d52eed66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 18:14:30 +0100 Subject: [PATCH 31/43] wip --- .github/workflows/tests.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 82b783f391..3d1e344d4b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,7 +19,6 @@ permissions: env: GOTOOLCHAIN: local GOEXPERIMENT: cgocheck2 - GOFLAGS: "-tags=nobadger,nomysql,nopgx" jobs: tests-linux: name: Tests (Linux, PHP ${{ matrix.php-versions }}) @@ -36,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: @@ -101,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: @@ -142,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: From 45b5bfecfc0d527bc09cf4f6f79a439e617fa2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Jan 2026 18:35:36 +0100 Subject: [PATCH 32/43] don't use xcaddy --- .github/workflows/windows.yaml | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 0dfe3fa7ec..9fdf94bacb 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -79,20 +79,6 @@ jobs: cache: ${{ github.event_name != 'release' }} check-latest: true - - name: Install xcaddy - run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - - - name: Cache Vcpkg Packages - if: github.event_name != 'release' - uses: actions/cache@v5 # zizmor: ignore[cache-poisoning] - with: - path: | - frankenphp\vcpkg_installed - ${{ env.VCPKG_INSTALLATION_ROOT }}\downloads - key: ${{ runner.os }}-vcpkg-libs-${{ hashFiles('frankenphp/vcpkg.json') }} - restore-keys: | - ${{ runner.os }}-vcpkg-libs- - - name: Install Vcpkg Libraries working-directory: frankenphp run: "vcpkg install" @@ -158,20 +144,7 @@ jobs: - name: Build FrankenPHP run: | $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - $ldflags = "-linkmode=external -extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" - - $env:CGO_ENABLED = "1" - $env:XCADDY_GO_BUILD_FLAGS = "-ldflags=`"$ldflags`" -tags=nobadger,nomysql,nopgx" - - echo $env:XCADDY_GO_BUILD_FLAGS - - xcaddy build ` - --with github.com/dunglas/frankenphp/caddy=../ ` - --with github.com/dunglas/frankenphp=../../ ` - --with github.com/dunglas/mercure/caddy ` - --with github.com/dunglas/vulcain/caddy ` - --with github.com/dunglas/caddy-cbrotli ` - --output frankenphp.exe + go build "-X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive From 5c1fe581111c28e1172f5c27ac2a2b428efe4276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 17 Jan 2026 13:19:29 +0100 Subject: [PATCH 33/43] fix Caddy module tests --- .github/workflows/windows.yaml | 4 ++-- caddy/caddy_test.go | 25 +++++++++++++++++++++++++ internal/fastabs/filepath.go | 3 ++- testdata/files/index.php | 2 ++ testdata/mercure-publish.php | 4 ++-- worker.go | 2 +- 6 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 testdata/files/index.php diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 9fdf94bacb..782591b8be 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -180,6 +180,6 @@ jobs: $env:PHPRC = Get-Location go test ./... - #cd caddy - #go test ./... + cd caddy + go test ./... working-directory: ${{ github.workspace }}\frankenphp diff --git a/caddy/caddy_test.go b/caddy/caddy_test.go index 4233fc3111..f5460ad0da 100644 --- a/caddy/caddy_test.go +++ b/caddy/caddy_test.go @@ -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) @@ -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 { @@ -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 { @@ -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(` { @@ -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 @@ -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 @@ -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 diff --git a/internal/fastabs/filepath.go b/internal/fastabs/filepath.go index 297e6a756b..c0b159439a 100644 --- a/internal/fastabs/filepath.go +++ b/internal/fastabs/filepath.go @@ -9,5 +9,6 @@ import ( // FastAbs can't be optimized on Windows because the // syscall.FullPath function takes an input. func FastAbs(path string) (string, error) { - return filepath.Abs(path) + // Normalize forward slashes to backslashes for Windows compatibility + return filepath.Abs(filepath.FromSlash(path)) } diff --git a/testdata/files/index.php b/testdata/files/index.php new file mode 100644 index 0000000000..b9138e0898 --- /dev/null +++ b/testdata/files/index.php @@ -0,0 +1,2 @@ + Date: Sat, 17 Jan 2026 17:47:02 +0100 Subject: [PATCH 34/43] -X CustomVersion is part of -ldflags --- .github/workflows/windows.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 782591b8be..604f852dec 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -144,7 +144,8 @@ jobs: - name: Build FrankenPHP run: | $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - go build "-X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" + $env:GOFLAGS += " -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" + go build working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive From 75615e5a1476846d9c3ddcb7b9bd8b96cc91a2c7 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 17:54:42 +0100 Subject: [PATCH 35/43] fix --- .github/workflows/windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 604f852dec..361cba9142 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -38,7 +38,7 @@ permissions: env: GOTOOLCHAIN: local - GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx" + GOFLAGS: "-tags=nobadger,nomysql,nopgx" PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases" CC: clang CXX: clang++ @@ -144,7 +144,7 @@ jobs: - name: Build FrankenPHP run: | $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - $env:GOFLAGS += " -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" + $env:GOFLAGS += "-ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" go build working-directory: frankenphp\caddy\frankenphp From 2dbed0ad9d579aeab04d82b8b722416726e50da3 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 17:58:33 +0100 Subject: [PATCH 36/43] don't forget to add FRANKENPHP_VERSION to CGO_CFLAGS, otherwise phpinfo() reports "dev" --- .github/workflows/windows.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 361cba9142..c74bacade7 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -138,13 +138,13 @@ jobs: echo "$watcherRoot" | Out-File -FilePath $env:GITHUB_PATH -Append echo "$phpBin" | Out-File -FilePath $env:GITHUB_PATH -Append - echo "CGO_CFLAGS=-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_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" - $env:GOFLAGS += "-ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" + $env:GOFLAGS += " -ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" go build working-directory: frankenphp\caddy\frankenphp From 7e8e250fed0cb160d97101430ce238acb2d13eb0 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 18:09:26 +0100 Subject: [PATCH 37/43] why is quoting so hard in powershell --- .github/workflows/windows.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index c74bacade7..481880cf26 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -137,15 +137,15 @@ jobs: 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 + + $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" + $env:GOFLAGS += " -ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" 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" - $env:GOFLAGS += " -ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" - go build + run: go build working-directory: frankenphp\caddy\frankenphp - name: Create Zip Archive @@ -184,3 +184,7 @@ jobs: cd caddy go test ./... working-directory: ${{ github.workspace }}\frankenphp + + - name: setup tmate + uses: mxschmitt/action-tmate@v3 + if: ${{ failure() }} From 6f2745d9f662661ba552c38ff78ff017e50280c8 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 18:15:23 +0100 Subject: [PATCH 38/43] permanently change it --- .github/workflows/windows.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 481880cf26..5f5094f45f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -139,7 +139,8 @@ jobs: echo "$phpBin" | Out-File -FilePath $env:GITHUB_PATH -Append $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - $env:GOFLAGS += " -ldflags=`"-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'`"" + $ldflags = "-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" + echo "GOFLAGS=$env:GOFLAGS -ldflags=`"$ldflags`"" | Out-File -FilePath $env:GITHUB_ENV -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 From 3a66f36b6388825ea3c4d4698eb5406e00d54478 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 18:39:05 +0100 Subject: [PATCH 39/43] seems impossible to do with GOFLAGS --- .github/workflows/windows.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 5f5094f45f..9ec5ec66e0 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -38,7 +38,7 @@ permissions: env: GOTOOLCHAIN: local - GOFLAGS: "-tags=nobadger,nomysql,nopgx" + GOFLAGS: "-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx " PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases" CC: clang CXX: clang++ @@ -137,16 +137,14 @@ jobs: 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 - - $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" - $ldflags = "-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" - echo "GOFLAGS=$env:GOFLAGS -ldflags=`"$ldflags`"" | Out-File -FilePath $env:GITHUB_ENV -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: go build + 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 Zip Archive From efbd36240bd37d7ff8eff0a962f08a9ab8bcc725 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 18:42:31 +0100 Subject: [PATCH 40/43] woopsie --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 9ec5ec66e0..816af4114f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -38,7 +38,7 @@ permissions: env: GOTOOLCHAIN: local - GOFLAGS: "-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx " + GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx" PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases" CC: clang CXX: clang++ From ebb4fbd906b727d202c61055c56559baeb49672a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 18:55:02 +0100 Subject: [PATCH 41/43] fix \n vs \r\n issues --- .gitattributes | 1 + .github/workflows/windows.yaml | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..6313b56c57 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 816af4114f..e25662f253 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -51,6 +51,11 @@ jobs: 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: @@ -183,7 +188,3 @@ jobs: cd caddy go test ./... working-directory: ${{ github.workspace }}\frankenphp - - - name: setup tmate - uses: mxschmitt/action-tmate@v3 - if: ${{ failure() }} From 7e756996da43e6c8c1d86c49b9fc92380e7887c2 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 17 Jan 2026 23:33:18 +0100 Subject: [PATCH 42/43] fix double zipping, re-enable compression (actually makes a big difference) --- .github/workflows/windows.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e25662f253..85efce240e 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -152,7 +152,7 @@ jobs: go build -ldflags="-extldflags=-fuse-ld=lld -X 'github.com/caddyserver/caddy/v2.CustomVersion=$customVersion'" working-directory: frankenphp\caddy\frankenphp - - name: Create Zip Archive + - name: Create Directory run: | Copy-Item frankenphp\caddy\frankenphp\frankenphp.exe $env:DIR_NAME Copy-Item watcher\libwatcher-c.dll $env:DIR_NAME @@ -161,17 +161,18 @@ jobs: 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 - Compress-Archive -Path $env:DIR_NAME -DestinationPath "$env:DIR_NAME.zip" - - name: Upload Artifact if: github.event_name != 'release' uses: actions/upload-artifact@v6 with: - name: ${{ env.DIR_NAME }}.zip - path: ${{ env.DIR_NAME }}.zip - compression-level: 0 + 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 From 5645b66687c8e2c9290304a5e90889b4bc031d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 23 Jan 2026 16:26:51 +0100 Subject: [PATCH 43/43] fix worker match --- caddy/module.go | 7 +++++++ internal/extgen/stub_test.go | 3 ++- internal/extgen/utils_test.go | 3 ++- testdata/files/index.php | 2 -- 4 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 testdata/files/index.php diff --git a/caddy/module.go b/caddy/module.go index 6416362694..475b484228 100644 --- a/caddy/module.go +++ b/caddy/module.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/http" "path/filepath" + "runtime" "slices" "strconv" "strings" @@ -494,7 +495,13 @@ func parsePhpServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) if indexFile != "off" { dirRedir := false dirIndex := "{http.request.uri.path}/" + indexFile + // On Windows, first_exist_fallback doesn't work correctly because + // glob is skipped and patterns are returned as-is without checking existence. + // Use first_exist instead to ensure all files are checked. tryPolicy := "first_exist_fallback" + if runtime.GOOS == "windows" { + tryPolicy = "first_exist" + } // if tryFiles wasn't overridden, use a reasonable default if len(tryFiles) == 0 { diff --git a/internal/extgen/stub_test.go b/internal/extgen/stub_test.go index 67b4203eac..172d4966ad 100644 --- a/internal/extgen/stub_test.go +++ b/internal/extgen/stub_test.go @@ -2,6 +2,7 @@ package extgen import ( "path/filepath" + "runtime" "strings" "testing" @@ -537,7 +538,7 @@ func TestStubGenerator_FileStructure(t *testing.T) { assert.NoError(t, err, "buildContent() failed") sep := "\n" - if filepath.Separator == '\\' { + if runtime.GOOS == "windows" { sep = "\r\n" } diff --git a/internal/extgen/utils_test.go b/internal/extgen/utils_test.go index 1cb9092b9a..d6700a2def 100644 --- a/internal/extgen/utils_test.go +++ b/internal/extgen/utils_test.go @@ -3,6 +3,7 @@ package extgen import ( "os" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -68,7 +69,7 @@ func TestWriteFile(t *testing.T) { assert.NoError(t, err, "Failed to stat file") expectedMode := os.FileMode(0644) - if filepath.Separator == '\\' { + if runtime.GOOS == "windows" { expectedMode = os.FileMode(0666) } diff --git a/testdata/files/index.php b/testdata/files/index.php deleted file mode 100644 index b9138e0898..0000000000 --- a/testdata/files/index.php +++ /dev/null @@ -1,2 +0,0 @@ -