From 5a5c784c4f936b7f7b3eb2e4add658b13e56882d Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:30:52 +0200 Subject: [PATCH] fix(frankenphp): strip Go's -mthreads so the Windows build survives Clang >= 20 Go passes the MinGW-only -mthreads flag to the C compiler for cgo builds (golang/go#16932); Clang >= 20 rejects it for the MSVC target, so the final frankenphp go build fails on current windows-latest (VS 18 / LLVM 20+). Wrap Clang with a generated .bat that drops the -mthreads token and forwards the rest, used only when the detected Clang refuses the flag. --- src/Package/Target/php/frankenphp.php | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Package/Target/php/frankenphp.php b/src/Package/Target/php/frankenphp.php index 399943b3b..7692b860d 100644 --- a/src/Package/Target/php/frankenphp.php +++ b/src/Package/Target/php/frankenphp.php @@ -273,10 +273,11 @@ public function buildFrankenphpForWindows(TargetPackage $package, PackageInstall // Fix: prepend clang's directory to PATH and use plain executable names instead, // which matches FrankenPHP's official CI approach (CC=clang, CXX=clang++). $clang_dir = dirname($clang_info['clang']); + [$cc, $cxx] = $this->windowsCgoCompilers($package, $clang_info['clang']); $env = [ 'CGO_ENABLED' => '1', - 'CC' => 'clang.exe', - 'CXX' => 'clang++.exe', + 'CC' => $cc, + 'CXX' => $cxx, 'PATH' => $clang_dir . ';' . getenv('PATH'), 'CGO_CFLAGS' => clean_spaces($cgo_cflags), 'CGO_LDFLAGS' => $cgo_ldflags, @@ -377,6 +378,38 @@ public function smokeTestFrankenphpForWindows(PackageBuilder $builder): void } } + /** + * Return the [CC, CXX] cgo should use to build FrankenPHP on Windows. + * + * Go passes the MinGW-only `-mthreads` flag to the C compiler for cgo builds + * (golang/go#16932); Clang >= 20 rejects it for the MSVC target. When it does, + * wrap Clang with a tiny .bat that strips the flag before forwarding. + * + * @return array{0: string, 1: string} + */ + protected function windowsCgoCompilers(TargetPackage $package, string $clang): array + { + $probe = $package->getSourceDir() . '\mthreads-probe.c'; + file_put_contents($probe, "int main(void){return 0;}\n"); + [$ret] = cmd()->execWithResult('"' . $clang . '" -mthreads -c ' . escapeshellarg($probe) . ' -o ' . escapeshellarg($probe . '.o'), false); + FileSystem::removeFileIfExists($probe); + FileSystem::removeFileIfExists($probe . '.o'); + if ($ret === 0) { + return ['clang.exe', 'clang++.exe']; + } + + logger()->info('Clang rejects -mthreads; wrapping it to strip the flag (golang/go#16932)'); + $dir = dirname($clang); + $cc = $package->getSourceDir() . '\cc-nothreads.bat'; + $cxx = $package->getSourceDir() . '\cxx-nothreads.bat'; + // %*: the whole command line; the substring replace drops the -mthreads token, + // preserving quoting of paths that a for-loop would mangle. + file_put_contents($cc, "@echo off\r\nset \"a=%*\"\r\nset \"a=%a: -mthreads = %\"\r\n\"{$dir}\\clang.exe\" %a%\r\n"); + file_put_contents($cxx, "@echo off\r\nset \"a=%*\"\r\nset \"a=%a: -mthreads = %\"\r\n\"{$dir}\\clang++.exe\" %a%\r\n"); + + return [$cc, $cxx]; + } + protected function getFrankenPHPVersion(TargetPackage $package): string { if ($version = getenv('FRANKENPHP_VERSION')) {