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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] -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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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/64] 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 @@ - Date: Fri, 6 Feb 2026 15:32:49 +0100 Subject: [PATCH 44/64] add frankenphp icon for windows .exe --- frankenphp.ico | Bin 0 -> 3230 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 frankenphp.ico diff --git a/frankenphp.ico b/frankenphp.ico new file mode 100644 index 0000000000000000000000000000000000000000..379e94d2ba196d0b8e62a9e11bd31a9a03f17d2c GIT binary patch literal 3230 zcmcK62~bq`9S86~2*DhY@@BMCLS>!89SLwP18&}bvjL*rk&PSzx}=KcBZzC*mnAu5C8xE-|_ps z|NP(YJ;u!FZ_^u$`_F2ZF}9X5ww-Juj)WUGZnW?Jx#^gY#d^lF0_N(YW^M(ioKeckriYq`8IjY^qbrF zs%dOFwbo<}*-YLb+;J-@zB%Yx>LsGcDA~S8VWG)To4GX+ImPCaSBV)}M_wlz$a2EP z?$ z4=#^V(kQ$;yFj=(=p&QDd$R-b?*vx+DO7)Pyg3fRCv)%~=|YxG;OQP(`V=F9oF|>6 zi5wzBWR4i<8t)P(Q_IbV1=&Ejbznp6$tJRnyctqhCYJQH?&3dqZ!7(_jZIcaxXS(-o4aXSyO?dB>b;?`g)F+PX?+;Y#rf3oE= zCu_~pogeC!`JUD>2b&~^&<{neb&#HFgsfeUonxmFE-(06!}wy&D_&8$Wj?>8m=Z4T zf3t01)-09rnUc90^LJp(ymnjEXm>)<6RnlUaBy_`@4Svx!LPXi(#|IM^q;~WZRwLd z=3L$^%lfo!SI)1pnEe(melA@jn%Tc}8XD%z)iOHo74sryzrKX+*jT|feCxP$Aydbu2)<*yHXe0_8Qqp3BYC$Tqe1{qt@!d|mbtq-F6 zaAo;Sv}hhjn1Jb+;H|9Jy-(5_`!vQU{`1t$ffs7tiFokSOSe!Ln-5-VHF!T7g5drf z=vg#!{$L#05jpts<^xR0kA0=EaH&7IF<>1E=tq4TDF^VDz{ zY=GTR0|d?02u{vMu`nJ%nM&A>>EU>F6!c7j*0B+ni+^h)8Uqzj24z5+br?c@6$CBS z*p-(3G}Fd^RL7jwEtUYyzbrT={ZIB6yTQLcjve3Y!S;z}_$pNh2~UOHa047J7$CSl z0l{60P52*AgMZ^31h=OUo}`3?VsD#hg57i*-u=ND*e@91pPqv}E02SV#iyF;7P>d} zG4IL{g!gEE>H4l??TGT*56AXecy^scMPn!Gnhe-I-fMjJGdem^-)z9X-qY})wGbVW z3c(pY5_I-m7GLGO&zAIyD_2CW2~|Z(xFZS7gr#ttkPH)1s_dO9^(j$On*E3U#f%LKCT`odhbVCq6$6g1|*l)A|N&U zNj`H8d2w{cs781N$7TGUcXb3#SLxkK&z8?XI}*I&kZu=>gs==`*oGmUABlaz=}43& zBF!lpDdKn>kR=)W340DA#U&P^OCu1^^+R}N1itwN$h8z_EFRxckQ6EZN_1-y{QI-; zcrcHM^COVI9sH2$=a+ew9#At8eQUXc>(e)%u}|M!#=gqj9l2JXh@pEH{bU}FR3CYF zrVykpgxW%!wpe^6dwY4IUDTAeyASfqvnXipe0szy<)d5+5%)fGA(iCV3)JPAi%M0y z<)10Wh7oaR0ZBI(P!N^-D4z)yEx;P%_k*)t@moQC<@Pa`k1Ur|sF?lc>{UMh_FYbS z{L6Uu$^d$Y3EYY|?m1T(qWHQ}CjX7jT2heBwpeoa5Bw3p&*<}YliBn0VaD*7?Zj_Q YO4$lJXvs7Brus)T_T2C1_F19quMq5b(EtDd literal 0 HcmV?d00001 From c0a3ffcf708d17f7b1858c2149f7425511b5367d Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:14:49 +0100 Subject: [PATCH 45/64] use goversioninfo to embed icon and metadata --- .github/workflows/windows.yaml | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 85efce240e..e308609752 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -146,6 +146,44 @@ jobs: 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: Embed Windows icon and metadata + working-directory: frankenphp\caddy\frankenphp + run: | + go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest + + $maj = 0; $min = 0; $pat = 0; $bld = 0 + if ($env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?

\d+)$') { + $maj = [int]$Matches['M'] + $min = [int]$Matches['m'] + $pat = [int]$Matches['p'] + } + + # Create versioninfo.json next to the main package so resource.syso is linked automatically + @" + { + "FixedFileInfo": { + "FileVersion": { "Major": $maj, "Minor": $min, "Patch": $pat, "Build": $bld }, + "ProductVersion": { "Major": $maj, "Minor": $min, "Patch": $pat, "Build": $bld } + }, + "StringFileInfo": { + "CompanyName": "FrankenPHP", + "FileDescription": "The Modern PHP Webserver", + "FileVersion": "$env:FRANKENPHP_VERSION", + "InternalName": "frankenphp", + "OriginalFilename": "frankenphp.exe", + "LegalCopyright": "Kévin Dunglas", + "ProductName": "FrankenPHP", + "ProductVersion": "$env:FRANKENPHP_VERSION", + "Comments": "https://frankenphp.dev/" + }, + "VarFileInfo": { + "Translation": { "LangID": 1033, "CharsetID": 1200 } + } + } + "@ | Out-File -Encoding utf8 versioninfo.json + + goversioninfo -icon ..\..\..\frankenphp.ico -versioninfo versioninfo.json -o resource.syso + - name: Build FrankenPHP run: | $customVersion = "FrankenPHP $env:FRANKENPHP_VERSION PHP $env:PHP_VERSION Caddy" From e01a08e5975c87b5cd8e0d67e8d621d34e37d50f Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:24:45 +0100 Subject: [PATCH 46/64] remove unnecessary moving of watcher-c.h file --- .github/workflows/windows.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e308609752..62ee5a0f6a 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -97,10 +97,6 @@ jobs: tar -xf "watcher.tar" -C "$env:GITHUB_WORKSPACE" Rename-Item -Path "$env:GITHUB_WORKSPACE\x86_64-pc-windows-msvc" -NewName "watcher" - - # See https://github.com/e-dant/watcher/issues/108 - New-Item -Path .\watcher\wtr -ItemType Directory -Force - Move-Item -Path .\watcher\watcher-c.h -Destination .\watcher\wtr\watcher-c.h env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 5482bbbe1ef71b915a10a1b31e5bf4ecd7d156ec Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:32:07 +0100 Subject: [PATCH 47/64] go mod tidy --- caddy/go.mod | 1 - caddy/go.sum | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/caddy/go.mod b/caddy/go.mod index f165aa021c..29dc86125f 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -63,7 +63,6 @@ require ( github.com/dunglas/vulcain v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/e-dant/watcher v0.0.0-20260110212511-0b8aea576c24 // indirect - github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a // 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 198b93a422..9a358c11fc 100644 --- a/caddy/go.sum +++ b/caddy/go.sum @@ -159,8 +159,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-20260104182512-c28e9078050a h1:e/m9m8cJgjzw2Ol7tKTu4B/lM5F3Ym7ryKI+oyw0T8Y= -github.com/e-dant/watcher/watcher-go v0.0.0-20260104182512-c28e9078050a/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 79db4c0b2cf3f2fd3839c8ba2cfaa8f36394ab2a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:34:40 +0100 Subject: [PATCH 48/64] goversioninfo param --- .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 62ee5a0f6a..5939bf103f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -178,7 +178,7 @@ jobs: } "@ | Out-File -Encoding utf8 versioninfo.json - goversioninfo -icon ..\..\..\frankenphp.ico -versioninfo versioninfo.json -o resource.syso + goversioninfo -icon ..\..\..\frankenphp.ico versioninfo.json -o resource.syso - name: Build FrankenPHP run: | From 451c96ffa1f3baa6c53f41f50a02b378d2471d88 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:41:16 +0100 Subject: [PATCH 49/64] throw AI at how to create a file without BOM in powershell --- .github/workflows/windows.yaml | 42 ++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 5939bf103f..d66a97327a 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -154,29 +154,27 @@ jobs: $pat = [int]$Matches['p'] } - # Create versioninfo.json next to the main package so resource.syso is linked automatically - @" - { - "FixedFileInfo": { - "FileVersion": { "Major": $maj, "Minor": $min, "Patch": $pat, "Build": $bld }, - "ProductVersion": { "Major": $maj, "Minor": $min, "Patch": $pat, "Build": $bld } - }, - "StringFileInfo": { - "CompanyName": "FrankenPHP", - "FileDescription": "The Modern PHP Webserver", - "FileVersion": "$env:FRANKENPHP_VERSION", - "InternalName": "frankenphp", - "OriginalFilename": "frankenphp.exe", - "LegalCopyright": "Kévin Dunglas", - "ProductName": "FrankenPHP", - "ProductVersion": "$env:FRANKENPHP_VERSION", - "Comments": "https://frankenphp.dev/" - }, - "VarFileInfo": { - "Translation": { "LangID": 1033, "CharsetID": 1200 } + $json = @{ + FixedFileInfo = @{ + FileVersion = @{ Major = $maj; Minor = $min; Patch = $pat; Build = $bld } + ProductVersion = @{ Major = $maj; Minor = $min; Patch = $pat; Build = $bld } } - } - "@ | Out-File -Encoding utf8 versioninfo.json + StringFileInfo = @{ + CompanyName = "FrankenPHP" + FileDescription = "The Modern PHP Webserver" + FileVersion = $env:FRANKENPHP_VERSION + InternalName = "frankenphp" + OriginalFilename = "frankenphp.exe" + LegalCopyright = "Kévin Dunglas" + ProductName = "FrankenPHP" + ProductVersion = $env:FRANKENPHP_VERSION + Comments = "https://frankenphp.dev/" + } + VarFileInfo = @{ + Translation = @{ LangID = 1033; CharsetID = 1200 } + } + } | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText("versioninfo.json", $json) goversioninfo -icon ..\..\..\frankenphp.ico versioninfo.json -o resource.syso From e522dfc72c3146d56ad4053a960ea7c9b267328f Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:44:56 +0100 Subject: [PATCH 50/64] fix file path to icon --- .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 d66a97327a..b24d3ede46 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -176,7 +176,7 @@ jobs: } | ConvertTo-Json -Depth 10 [System.IO.File]::WriteAllText("versioninfo.json", $json) - goversioninfo -icon ..\..\..\frankenphp.ico versioninfo.json -o resource.syso + goversioninfo -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso - name: Build FrankenPHP run: | From 13de7c3f6666545516d07824038ce1ed8b292f3b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 18:54:24 +0100 Subject: [PATCH 51/64] use the latest windows release, apparently the php hosted one is out of date --- .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 b24d3ede46..93eeccc2f9 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -39,7 +39,7 @@ permissions: env: GOTOOLCHAIN: local GOFLAGS: "-ldflags=-extldflags=-fuse-ld=lld -tags=nobadger,nomysql,nopgx" - PHP_DOWNLOAD_BASE: "https://windows.php.net/downloads/releases" + PHP_DOWNLOAD_BASE: "https://downloads.php.net/~windows/releases/" CC: clang CXX: clang++ @@ -176,7 +176,7 @@ jobs: } | ConvertTo-Json -Depth 10 [System.IO.File]::WriteAllText("versioninfo.json", $json) - goversioninfo -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso + goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso - name: Build FrankenPHP run: | From 3697e0cf937addf2a0b6afabc33ddb82d70bb767 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 19:03:00 +0100 Subject: [PATCH 52/64] use malloc/free because I don't want to mess with linking against php8embed dll yet --- frankenphp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index a7981dcb6c..baee472507 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -269,7 +269,7 @@ static void frankenphp_restore_ini(void) { * it calls zend_hash_del() on EG(modified_ini_directives). */ uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives)); zend_string **entries_to_restore = - max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL; + max_entries ? malloc(max_entries * sizeof(zend_string *)) : NULL; size_t restore_count = 0; ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name, @@ -295,7 +295,7 @@ static void frankenphp_restore_ini(void) { zend_string_release(entries_to_restore[i]); } if (entries_to_restore) { - efree(entries_to_restore); + free(entries_to_restore); } } @@ -317,7 +317,7 @@ static void frankenphp_snapshot_session_handlers(void) { return; /* No user handlers to snapshot */ } - worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers)); + worker_session_handlers_snapshot = malloc(sizeof(session_user_handlers)); /* Copy each handler zval with incremented reference count */ #define SNAPSHOT_HANDLER(h) \ @@ -372,7 +372,7 @@ static void frankenphp_cleanup_worker_state(void) { #undef FREE_HANDLER - efree(worker_session_handlers_snapshot); + free(worker_session_handlers_snapshot); worker_session_handlers_snapshot = NULL; } } From 71f5b56ea873e4f4e80dff7d5dd1e4053704f827 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 19:21:10 +0100 Subject: [PATCH 53/64] Revert "use malloc/free because I don't want to mess with linking against php8embed dll yet" This reverts commit 3697e0cf937addf2a0b6afabc33ddb82d70bb767. --- frankenphp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index baee472507..a7981dcb6c 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -269,7 +269,7 @@ static void frankenphp_restore_ini(void) { * it calls zend_hash_del() on EG(modified_ini_directives). */ uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives)); zend_string **entries_to_restore = - max_entries ? malloc(max_entries * sizeof(zend_string *)) : NULL; + max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL; size_t restore_count = 0; ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name, @@ -295,7 +295,7 @@ static void frankenphp_restore_ini(void) { zend_string_release(entries_to_restore[i]); } if (entries_to_restore) { - free(entries_to_restore); + efree(entries_to_restore); } } @@ -317,7 +317,7 @@ static void frankenphp_snapshot_session_handlers(void) { return; /* No user handlers to snapshot */ } - worker_session_handlers_snapshot = malloc(sizeof(session_user_handlers)); + worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers)); /* Copy each handler zval with incremented reference count */ #define SNAPSHOT_HANDLER(h) \ @@ -372,7 +372,7 @@ static void frankenphp_cleanup_worker_state(void) { #undef FREE_HANDLER - free(worker_session_handlers_snapshot); + efree(worker_session_handlers_snapshot); worker_session_handlers_snapshot = NULL; } } From 247de714b8628013ac1097165925e073cc233512 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 19:21:43 +0100 Subject: [PATCH 54/64] maybe? --- types.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types.go b/types.go index 30764ecba5..96898cde33 100644 --- a/types.go +++ b/types.go @@ -15,6 +15,8 @@ package frankenphp #cgo noescape __zval_double__ #cgo noescape __zval_string__ #cgo noescape __zval_arr__ +#cgo noescape __emalloc__ +#cgo noescape __efree__ #include "types.h" */ import "C" From 158c689df64eb7a8b5cd773fb2f1902c074787db Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 19:33:19 +0100 Subject: [PATCH 55/64] use malloc/free instead @dunglas revert this --- frankenphp.c | 14 +++++++------- types.c | 4 ++-- types.h | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index a7981dcb6c..cd4b89465f 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -231,14 +231,14 @@ static void frankenphp_snapshot_ini(void) { if (EG(modified_ini_directives) == NULL) { /* Allocate empty table to mark as snapshotted */ - ALLOC_HASHTABLE(worker_ini_snapshot); + worker_ini_snapshot = malloc(sizeof(HashTable)); zend_hash_init(worker_ini_snapshot, 0, NULL, frankenphp_ini_snapshot_dtor, 0); return; } uint32_t num_modified = zend_hash_num_elements(EG(modified_ini_directives)); - ALLOC_HASHTABLE(worker_ini_snapshot); + worker_ini_snapshot = malloc(sizeof(HashTable)); zend_hash_init(worker_ini_snapshot, num_modified, NULL, frankenphp_ini_snapshot_dtor, 0); @@ -269,7 +269,7 @@ static void frankenphp_restore_ini(void) { * it calls zend_hash_del() on EG(modified_ini_directives). */ uint32_t max_entries = zend_hash_num_elements(EG(modified_ini_directives)); zend_string **entries_to_restore = - max_entries ? emalloc(max_entries * sizeof(zend_string *)) : NULL; + max_entries ? malloc(max_entries * sizeof(zend_string *)) : NULL; size_t restore_count = 0; ZEND_HASH_FOREACH_STR_KEY_PTR(EG(modified_ini_directives), entry_name, @@ -295,7 +295,7 @@ static void frankenphp_restore_ini(void) { zend_string_release(entries_to_restore[i]); } if (entries_to_restore) { - efree(entries_to_restore); + free(entries_to_restore); } } @@ -317,7 +317,7 @@ static void frankenphp_snapshot_session_handlers(void) { return; /* No user handlers to snapshot */ } - worker_session_handlers_snapshot = emalloc(sizeof(session_user_handlers)); + worker_session_handlers_snapshot = malloc(sizeof(session_user_handlers)); /* Copy each handler zval with incremented reference count */ #define SNAPSHOT_HANDLER(h) \ @@ -357,7 +357,7 @@ static void frankenphp_cleanup_worker_state(void) { /* Free INI snapshot */ if (worker_ini_snapshot != NULL) { zend_hash_destroy(worker_ini_snapshot); - FREE_HASHTABLE(worker_ini_snapshot); + free(worker_ini_snapshot); worker_ini_snapshot = NULL; } @@ -372,7 +372,7 @@ static void frankenphp_cleanup_worker_state(void) { #undef FREE_HANDLER - efree(worker_session_handlers_snapshot); + free(worker_session_handlers_snapshot); worker_session_handlers_snapshot = NULL; } } diff --git a/types.c b/types.c index 5ab842a42a..441992e970 100644 --- a/types.c +++ b/types.c @@ -14,9 +14,9 @@ Bucket *get_ht_bucket_data(HashTable *ht, uint32_t index) { return NULL; } -void *__emalloc__(size_t size) { return emalloc(size); } +void *__emalloc__(size_t size) { return malloc(size); } -void __efree__(void *ptr) { efree(ptr); } +void __efree__(void *ptr) { free(ptr); } void __zend_hash_init__(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, bool persistent) { diff --git a/types.h b/types.h index 619603ab97..feae3d0fbe 100644 --- a/types.h +++ b/types.h @@ -2,6 +2,7 @@ #define TYPES_H #include "frankenphp.h" +#include #include #include #include From c2aab049ed925cf12c52a7b89a64f10947a0e93d Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:00:40 +0100 Subject: [PATCH 56/64] explicitly utf8 no bom --- .github/workflows/windows.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 93eeccc2f9..e0df17f468 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -147,17 +147,17 @@ jobs: run: | go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest - $maj = 0; $min = 0; $pat = 0; $bld = 0 - if ($env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?

\d+)$') { - $maj = [int]$Matches['M'] - $min = [int]$Matches['m'] - $pat = [int]$Matches['p'] + $major = 0; $minor = 0; $patch = 0; $bld = 0 + if ($env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?\d+)$') { + $major = [int]$Matches['major'] + $minor = [int]$Matches['minor'] + $patch = [int]$Matches['patch'] } $json = @{ FixedFileInfo = @{ - FileVersion = @{ Major = $maj; Minor = $min; Patch = $pat; Build = $bld } - ProductVersion = @{ Major = $maj; Minor = $min; Patch = $pat; Build = $bld } + FileVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $bld } + ProductVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $bld } } StringFileInfo = @{ CompanyName = "FrankenPHP" @@ -171,10 +171,11 @@ jobs: Comments = "https://frankenphp.dev/" } VarFileInfo = @{ - Translation = @{ LangID = 1033; CharsetID = 1200 } + Translation = @{ LangID = 9; CharsetID = 1200 } } } | ConvertTo-Json -Depth 10 - [System.IO.File]::WriteAllText("versioninfo.json", $json) + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf8NoBom) goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso From 376635fa0f8565c11fa998215415eff5e141471d Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:03:41 +0100 Subject: [PATCH 57/64] use cover as icon --- frankenphp.ico | Bin 3230 -> 261950 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frankenphp.ico b/frankenphp.ico index 379e94d2ba196d0b8e62a9e11bd31a9a03f17d2c..d98a76330f49f7b2b5ffa40d23838abd0c2db73a 100644 GIT binary patch literal 261950 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 literal 3230 zcmcK62~bq`9S86~2*DhY@@BMCLS>!89SLwP18&}bvjL*rk&PSzx}=KcBZzC*mnAu5C8xE-|_ps z|NP(YJ;u!FZ_^u$`_F2ZF}9X5ww-Juj)WUGZnW?Jx#^gY#d^lF0_N(YW^M(ioKeckriYq`8IjY^qbrF zs%dOFwbo<}*-YLb+;J-@zB%Yx>LsGcDA~S8VWG)To4GX+ImPCaSBV)}M_wlz$a2EP z?$ z4=#^V(kQ$;yFj=(=p&QDd$R-b?*vx+DO7)Pyg3fRCv)%~=|YxG;OQP(`V=F9oF|>6 zi5wzBWR4i<8t)P(Q_IbV1=&Ejbznp6$tJRnyctqhCYJQH?&3dqZ!7(_jZIcaxXS(-o4aXSyO?dB>b;?`g)F+PX?+;Y#rf3oE= zCu_~pogeC!`JUD>2b&~^&<{neb&#HFgsfeUonxmFE-(06!}wy&D_&8$Wj?>8m=Z4T zf3t01)-09rnUc90^LJp(ymnjEXm>)<6RnlUaBy_`@4Svx!LPXi(#|IM^q;~WZRwLd z=3L$^%lfo!SI)1pnEe(melA@jn%Tc}8XD%z)iOHo74sryzrKX+*jT|feCxP$Aydbu2)<*yHXe0_8Qqp3BYC$Tqe1{qt@!d|mbtq-F6 zaAo;Sv}hhjn1Jb+;H|9Jy-(5_`!vQU{`1t$ffs7tiFokSOSe!Ln-5-VHF!T7g5drf z=vg#!{$L#05jpts<^xR0kA0=EaH&7IF<>1E=tq4TDF^VDz{ zY=GTR0|d?02u{vMu`nJ%nM&A>>EU>F6!c7j*0B+ni+^h)8Uqzj24z5+br?c@6$CBS z*p-(3G}Fd^RL7jwEtUYyzbrT={ZIB6yTQLcjve3Y!S;z}_$pNh2~UOHa047J7$CSl z0l{60P52*AgMZ^31h=OUo}`3?VsD#hg57i*-u=ND*e@91pPqv}E02SV#iyF;7P>d} zG4IL{g!gEE>H4l??TGT*56AXecy^scMPn!Gnhe-I-fMjJGdem^-)z9X-qY})wGbVW z3c(pY5_I-m7GLGO&zAIyD_2CW2~|Z(xFZS7gr#ttkPH)1s_dO9^(j$On*E3U#f%LKCT`odhbVCq6$6g1|*l)A|N&U zNj`H8d2w{cs781N$7TGUcXb3#SLxkK&z8?XI}*I&kZu=>gs==`*oGmUABlaz=}43& zBF!lpDdKn>kR=)W340DA#U&P^OCu1^^+R}N1itwN$h8z_EFRxckQ6EZN_1-y{QI-; zcrcHM^COVI9sH2$=a+ew9#At8eQUXc>(e)%u}|M!#=gqj9l2JXh@pEH{bU}FR3CYF zrVykpgxW%!wpe^6dwY4IUDTAeyASfqvnXipe0szy<)d5+5%)fGA(iCV3)JPAi%M0y z<)10Wh7oaR0ZBI(P!N^-D4z)yEx;P%_k*)t@moQC<@Pa`k1Ur|sF?lc>{UMh_FYbS z{L6Uu$^d$Y3EYY|?m1T(qWHQ}CjX7jT2heBwpeoa5Bw3p&*<}YliBn0VaD*7?Zj_Q YO4$lJXvs7Brus)T_T2C1_F19quMq5b(EtDd From b084121b3a8575ab338636e6a51175b1706d7c0e Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:13:48 +0100 Subject: [PATCH 58/64] =?UTF-8?q?attempt=20to=20encode=20=C3=A9=20characte?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/windows.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index e0df17f468..96753cfad2 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -147,8 +147,8 @@ jobs: run: | go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest - $major = 0; $minor = 0; $patch = 0; $bld = 0 - if ($env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?\d+)$') { + $major = 0; $minor = 0; $patch = 0; $build = 0 + $env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?\d+)$') { $major = [int]$Matches['major'] $minor = [int]$Matches['minor'] $patch = [int]$Matches['patch'] @@ -156,8 +156,8 @@ jobs: $json = @{ FixedFileInfo = @{ - FileVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $bld } - ProductVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $bld } + FileVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build } + ProductVersion = @{ Major = $major; Minor = $minor; Patch = $patch; Build = $build } } StringFileInfo = @{ CompanyName = "FrankenPHP" @@ -165,7 +165,7 @@ jobs: FileVersion = $env:FRANKENPHP_VERSION InternalName = "frankenphp" OriginalFilename = "frankenphp.exe" - LegalCopyright = "Kévin Dunglas" + LegalCopyright = "K\u00e9vin Dunglas" ProductName = "FrankenPHP" ProductVersion = $env:FRANKENPHP_VERSION Comments = "https://frankenphp.dev/" From 590658a78225dde5b783b7e5d663cc4089ed3b42 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:20:10 +0100 Subject: [PATCH 59/64] hail mary --- .github/workflows/windows.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 96753cfad2..05bc3150ce 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -165,7 +165,7 @@ jobs: FileVersion = $env:FRANKENPHP_VERSION InternalName = "frankenphp" OriginalFilename = "frankenphp.exe" - LegalCopyright = "K\u00e9vin Dunglas" + LegalCopyright = "(c) 2022 Kévin Dunglas, MIT License" ProductName = "FrankenPHP" ProductVersion = $env:FRANKENPHP_VERSION Comments = "https://frankenphp.dev/" @@ -174,8 +174,7 @@ jobs: Translation = @{ LangID = 9; CharsetID = 1200 } } } | ConvertTo-Json -Depth 10 - $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) - [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf8NoBom) + $json | Out-File -FilePath "versioninfo.json" -Encoding unicode goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso From c1e5a2170f64872eb5243bad2f63f644368246e1 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:24:31 +0100 Subject: [PATCH 60/64] oops --- .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 05bc3150ce..4508f8ca75 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -148,7 +148,7 @@ jobs: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest $major = 0; $minor = 0; $patch = 0; $build = 0 - $env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?\d+)$') { + if ($env:FRANKENPHP_VERSION -match '^(?\d+)\.(?\d+)\.(?\d+)$') { $major = [int]$Matches['major'] $minor = [int]$Matches['minor'] $patch = [int]$Matches['patch'] From 06322ace9e3826306e4bf1cafd354d492bd74bf2 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:30:00 +0100 Subject: [PATCH 61/64] god I hate windows BOM --- .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 4508f8ca75..371160ef3f 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -174,7 +174,8 @@ jobs: Translation = @{ LangID = 9; CharsetID = 1200 } } } | ConvertTo-Json -Depth 10 - $json | Out-File -FilePath "versioninfo.json" -Encoding unicode + $Utf16NoBom = New-Object System.Text.UnicodeEncoding($false, $false) + [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf16NoBom) goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso From 77794852e1020f8922803194750836203a144d6b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:40:45 +0100 Subject: [PATCH 62/64] at least it doesn't fail the job, even though it doesn't correctly encode it in the rc... --- .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 371160ef3f..4e70a2227b 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -174,8 +174,8 @@ jobs: Translation = @{ LangID = 9; CharsetID = 1200 } } } | ConvertTo-Json -Depth 10 - $Utf16NoBom = New-Object System.Text.UnicodeEncoding($false, $false) - [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf16NoBom) + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf8NoBom) goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso From db995d29cd3621d34e911ac96832b54ae9ad3b9b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 6 Feb 2026 20:42:56 +0100 Subject: [PATCH 63/64] try windows-1252 --- .github/workflows/windows.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 4e70a2227b..2d67640884 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -174,8 +174,7 @@ jobs: Translation = @{ LangID = 9; CharsetID = 1200 } } } | ConvertTo-Json -Depth 10 - $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) - [System.IO.File]::WriteAllText("versioninfo.json", $json, $Utf8NoBom) + [System.IO.File]::WriteAllText("versioninfo.json", $json, [System.Text.Encoding]::Default) goversioninfo -64 -icon ..\..\frankenphp.ico versioninfo.json -o resource.syso From cce64a692becd1ff584e4503103e582a9804eab5 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Sat, 7 Feb 2026 10:29:50 +0100 Subject: [PATCH 64/64] linter --- .github/workflows/windows.yaml | 2 +- types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 2d67640884..c1bfad3fad 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -161,7 +161,7 @@ jobs: } StringFileInfo = @{ CompanyName = "FrankenPHP" - FileDescription = "The Modern PHP Webserver" + FileDescription = "The modern PHP app server" FileVersion = $env:FRANKENPHP_VERSION InternalName = "frankenphp" OriginalFilename = "frankenphp.exe" diff --git a/types.h b/types.h index feae3d0fbe..a610c25b8b 100644 --- a/types.h +++ b/types.h @@ -2,11 +2,11 @@ #define TYPES_H #include "frankenphp.h" -#include #include #include #include #include +#include zval *get_ht_packed_data(HashTable *, uint32_t index); Bucket *get_ht_bucket_data(HashTable *, uint32_t index);