Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 114 additions & 3 deletions src/Package/Target/php/frankenphp.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use StaticPHP\Toolchain\GccNativeToolchain;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\InteractiveTerm;
use StaticPHP\Util\SPCConfigUtil;
use StaticPHP\Util\System\LinuxUtil;
Expand All @@ -26,6 +27,67 @@

trait frankenphp
{
/** Clang wrapper dropping the MinGW-only `-mthreads` flag Go passes on Windows (golang/go#16932). */
private const WINDOWS_CCWRAP_SOURCE = <<<'C'
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main(void)
{
wchar_t *cmd = GetCommandLineW();

/* skip our own argv[0] */
if (*cmd == L'"') {
cmd++;
while (*cmd && *cmd != L'"') cmd++;
if (*cmd) cmd++;
} else {
while (*cmd && *cmd != L' ' && *cmd != L'\t') cmd++;
}

wchar_t self[MAX_PATH];
GetModuleFileNameW(NULL, self, MAX_PATH);
wchar_t *base = wcsrchr(self, L'\\');
base = base ? base + 1 : self;

wchar_t dir[MAX_PATH];
DWORD n = GetEnvironmentVariableW(L"SPC_CCWRAP_DIR", dir, MAX_PATH);
if (n == 0 || n >= MAX_PATH) {
fwprintf(stderr, L"ccwrap: SPC_CCWRAP_DIR is not set\n");
return 111;
}

size_t len = wcslen(dir) + wcslen(base) + wcslen(cmd) + 8;
wchar_t *out = malloc(len * sizeof(wchar_t));
if (!out) return 111;
swprintf(out, len, L"\"%s\\%s\"", dir, base);

wchar_t *w = out + wcslen(out);
wchar_t *p = cmd;
while (*p) {
if (wcsncmp(p, L" -mthreads", 10) == 0 && (p[10] == L' ' || p[10] == L'\t' || p[10] == L'\0')) {
p += 10;
continue;
}
*w++ = *p++;
}
*w = L'\0';

STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (!CreateProcessW(NULL, out, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
fwprintf(stderr, L"ccwrap: failed to run %s\n", out);
return 112;
}
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD code = 113;
GetExitCodeProcess(pi.hProcess, &code);
return (int) code;
}
C;

#[Stage]
public function buildFrankenphpForUnix(TargetPackage $package, PackageInstaller $installer, ToolchainInterface $toolchain, PackageBuilder $builder): void
{
Expand Down Expand Up @@ -233,13 +295,18 @@ public function buildFrankenphpForWindows(TargetPackage $package, PackageInstall
$dep_libs = array_unique($dep_libs);
$lib_dir = str_replace('\\', '/', BUILD_LIB_PATH);
$php_embed_lib = "-lphp{$major}embed";
$win_sys_libs = '-lkernel32 -lole32 -luser32 -ladvapi32 -lshell32 -lws2_32 -ldnsapi -lpsapi -lbcrypt';
// secur32: InitSecurityInterfaceA (SSPI); pathcch: PathCchCanonicalizeEx (php8embed, PHP 8.5+).
$win_sys_libs = '-lkernel32 -lole32 -luser32 -ladvapi32 -lshell32 -lws2_32 -ldnsapi -lpsapi -lbcrypt -lsecur32 -lpathcch';
// Force in the zend_atomic objects: FrankenPHP only references them via __imp_, so they
// would otherwise not be pulled from php8embed.lib.
$force_include = '-Wl,/INCLUDE:zend_atomic_bool_store -Wl,/INCLUDE:zend_atomic_bool_load';
$cgo_ldflags = clean_spaces(implode(' ', array_filter([
"-L{$lib_dir}",
$php_embed_lib,
implode(' ', $dep_libs),
$win_sys_libs,
'-llibcmt',
$force_include,
'-Wl,/NODEFAULTLIB:msvcrt',
'-Wl,/NODEFAULTLIB:msvcrtd',
'-Wl,/FORCE:MULTIPLE',
Expand All @@ -262,10 +329,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->prepareWindowsCgoCompilers($package, $clang_info);
$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,
Expand Down Expand Up @@ -366,6 +434,49 @@ public function smokeTestFrankenphpForWindows(PackageBuilder $builder): void
}
}

/**
* Return the [CC, CXX] cgo should use to build FrankenPHP on Windows.
*
* Clang >= 20 rejects the MinGW-only `-mthreads` flag Go passes for cgo builds
* on Windows (golang/go#16932). When it does, wrap Clang to strip the flag.
*
* @return array{0: string, 1: string}
*/
protected function prepareWindowsCgoCompilers(TargetPackage $package, array $clang_info): array
{
$clang = $clang_info['clang'];

// Probe whether this Clang accepts -mthreads for the MSVC target.
$work = $package->getSourceDir() . '\spc-ccwrap';
FileSystem::createDir($work);
$probe_c = "{$work}\\probe.c";
$probe_o = "{$work}\\probe.o";
file_put_contents($probe_c, "int main(void){return 0;}\n");
[$ret] = cmd()->execWithResult('"' . $clang . '" -mthreads -c ' . escapeshellarg($probe_c) . ' -o ' . escapeshellarg($probe_o), false);
FileSystem::removeFileIfExists($probe_c);
FileSystem::removeFileIfExists($probe_o);
if ($ret === 0) {
// Clang accepts -mthreads; use it as-is (plain names, clang dir is on PATH).
return ['clang.exe', 'clang++.exe'];
}

logger()->info('Clang rejects -mthreads; building a wrapper that strips it (golang/go#16932)');

// The wrapper forwards to the real clang.exe / clang++.exe via SPC_CCWRAP_DIR.
$wrapper_c = "{$work}\\ccwrap.c";
file_put_contents($wrapper_c, self::WINDOWS_CCWRAP_SOURCE);
$cc = "{$work}\\clang.exe";
$cxx = "{$work}\\clang++.exe";
[$ret, $out] = cmd()->execWithResult('"' . $clang . '" -O2 ' . escapeshellarg($wrapper_c) . ' -o ' . escapeshellarg($cc), false);
if ($ret !== 0) {
throw new EnvironmentException('Failed to build the Clang -mthreads wrapper: ' . implode("\n", $out));
}
copy($cc, $cxx);
GlobalEnvManager::putenv('SPC_CCWRAP_DIR=' . dirname($clang));

return [$cc, $cxx];
}

protected function getFrankenPHPVersion(TargetPackage $package): string
{
if ($version = getenv('FRANKENPHP_VERSION')) {
Expand Down
4 changes: 3 additions & 1 deletion src/Package/Target/php/windows.php
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,9 @@ public function smokeTestEmbedForWindows(PackageInstaller $installer, TargetPack
$include_flags,
BUILD_ROOT_PATH,
$config['libs'],
'kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib dnsapi.lib psapi.lib bcrypt.lib' // Windows system libs (match Makefile LIBS)
// Windows system libs (match Makefile LIBS). pathcch.lib provides
// PathCchCanonicalizeEx, referenced by win32/ioutil since PHP 8.5.
'kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib dnsapi.lib psapi.lib bcrypt.lib pathcch.lib'
);

// Log command explicitly (workaround for cmd() not logging complex commands properly)
Expand Down
Loading