mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-11 19:05:35 +08:00
fix(windows): survive cmd.exe's 8191 character command line limit (#1209)
This commit is contained in:
@@ -25,13 +25,13 @@ class imagemagick
|
|||||||
* msbuild. The resulting CORE_RL_*.lib static libraries + MagickWand/MagickCore headers are
|
* msbuild. The resulting CORE_RL_*.lib static libraries + MagickWand/MagickCore headers are
|
||||||
* installed into the build root for ext-imagick to link.
|
* installed into the build root for ext-imagick to link.
|
||||||
*
|
*
|
||||||
* A short working directory is used (VisualMagick's tree is deeply nested and otherwise exceeds
|
* The VisualMagick tree lives under source/ so bin/spc reset cleans it up like everything
|
||||||
* MAX_PATH); override with SPC_IMAGEMAGICK_BUILD_DIR.
|
* else; override the location with SPC_IMAGEMAGICK_BUILD_DIR.
|
||||||
*/
|
*/
|
||||||
#[BuildFor('Windows')]
|
#[BuildFor('Windows')]
|
||||||
public function buildWin(LibraryPackage $lib): void
|
public function buildWin(LibraryPackage $lib): void
|
||||||
{
|
{
|
||||||
$work = getenv('SPC_IMAGEMAGICK_BUILD_DIR') ?: 'C:\im';
|
$work = getenv('SPC_IMAGEMAGICK_BUILD_DIR') ?: SOURCE_PATH . '\imagemagick-win';
|
||||||
$configure_release = '2026.05.30.2033';
|
$configure_release = '2026.05.30.2033';
|
||||||
$configure_url = "https://github.com/ImageMagick/Configure/releases/download/{$configure_release}/Configure.Release.x64.exe";
|
$configure_url = "https://github.com/ImageMagick/Configure/releases/download/{$configure_release}/Configure.Release.x64.exe";
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,55 @@ use ZM\Logger\ConsoleColor;
|
|||||||
|
|
||||||
trait frankenphp
|
trait frankenphp
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Source of the -mthreads stripping shim, see windowsCgoCompilers(). The %s
|
||||||
|
* placeholder receives the wrapped compiler path as a C string literal.
|
||||||
|
*/
|
||||||
|
private const string MTHREADS_SHIM_C = <<<'C'
|
||||||
|
// Generated by static-php-cli. Drops the MinGW-only -mthreads flag Go passes to
|
||||||
|
// cgo (golang/go#80290) and forwards everything else to clang.
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static const wchar_t compiler[] = L"%s";
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const wchar_t *args = GetCommandLineW();
|
||||||
|
// skip our own (possibly quoted) argv[0]
|
||||||
|
if (*args == L'"') {
|
||||||
|
args++;
|
||||||
|
while (*args && *args != L'"') args++;
|
||||||
|
if (*args) args++;
|
||||||
|
} else {
|
||||||
|
while (*args && *args != L' ' && *args != L'\t') args++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "<compiler>" + args + trailing space so a flag at the very end still matches
|
||||||
|
size_t len = wcslen(compiler) + wcslen(args) + 8;
|
||||||
|
wchar_t *cmd = calloc(len, sizeof(wchar_t));
|
||||||
|
if (cmd == NULL) return 1;
|
||||||
|
swprintf(cmd, len, L"\"%%ls\"%%ls ", compiler, args);
|
||||||
|
|
||||||
|
// remove every space-delimited -mthreads token
|
||||||
|
wchar_t *p;
|
||||||
|
while ((p = wcsstr(cmd, L" -mthreads ")) != NULL) {
|
||||||
|
wmemmove(p, p + 10, wcslen(p + 10) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
STARTUPINFOW si = {sizeof(si)};
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
if (!CreateProcessW(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
|
||||||
|
return 127;
|
||||||
|
}
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
DWORD code = 1;
|
||||||
|
GetExitCodeProcess(pi.hProcess, &code);
|
||||||
|
return (int) code;
|
||||||
|
}
|
||||||
|
C;
|
||||||
|
|
||||||
#[Stage]
|
#[Stage]
|
||||||
public function buildFrankenphpForUnix(TargetPackage $package, PackageInstaller $installer, ToolchainInterface $toolchain, PackageBuilder $builder): void
|
public function buildFrankenphpForUnix(TargetPackage $package, PackageInstaller $installer, ToolchainInterface $toolchain, PackageBuilder $builder): void
|
||||||
{
|
{
|
||||||
@@ -382,8 +431,12 @@ trait frankenphp
|
|||||||
* Return the [CC, CXX] cgo should use to build FrankenPHP on Windows.
|
* 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;
|
* Go passes the MinGW-only `-mthreads` flag to the C compiler for cgo builds;
|
||||||
* Clang >= 20 rejects it for the MSVC target. When it does, wrap Clang with
|
* Clang >= 20 rejects it for the MSVC target. When it does, wrap Clang with a
|
||||||
* a tiny .bat that strips the flag before forwarding.
|
* small compiled shim that strips the flag before forwarding. An executable and
|
||||||
|
* not a .bat on purpose: batch files run through cmd.exe, which caps the command
|
||||||
|
* line at 8191 characters, and cgo link steps with big extension stacks (imagick)
|
||||||
|
* blow past that. A real process gets the full ~32K limit, and beyond that Go
|
||||||
|
* switches to a response file by itself.
|
||||||
*
|
*
|
||||||
* Workaround for https://github.com/golang/go/issues/80290, remove once Go
|
* Workaround for https://github.com/golang/go/issues/80290, remove once Go
|
||||||
* stops passing the flag.
|
* stops passing the flag.
|
||||||
@@ -403,14 +456,17 @@ trait frankenphp
|
|||||||
|
|
||||||
logger()->info('Clang rejects -mthreads; wrapping it to strip the flag (golang/go#80290)');
|
logger()->info('Clang rejects -mthreads; wrapping it to strip the flag (golang/go#80290)');
|
||||||
$dir = dirname($clang);
|
$dir = dirname($clang);
|
||||||
$cc = $package->getSourceDir() . '\cc-nothreads.bat';
|
$wrappers = [];
|
||||||
$cxx = $package->getSourceDir() . '\cxx-nothreads.bat';
|
foreach (['cc-nothreads' => 'clang.exe', 'cxx-nothreads' => 'clang++.exe'] as $name => $compiler) {
|
||||||
// %*: the whole command line; the substring replace drops the -mthreads token,
|
$src = $package->getSourceDir() . "\\{$name}.c";
|
||||||
// preserving quoting of paths that a for-loop would mangle.
|
$exe = $package->getSourceDir() . "\\{$name}.exe";
|
||||||
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($src, sprintf(self::MTHREADS_SHIM_C, str_replace('\\', '\\\\', "{$dir}\\{$compiler}")));
|
||||||
file_put_contents($cxx, "@echo off\r\nset \"a=%*\"\r\nset \"a=%a: -mthreads = %\"\r\n\"{$dir}\\clang++.exe\" %a%\r\n");
|
cmd()->cd($package->getSourceDir())
|
||||||
|
->exec('"' . $clang . '" -O2 ' . escapeshellarg($src) . ' -o ' . escapeshellarg($exe));
|
||||||
|
$wrappers[] = $exe;
|
||||||
|
}
|
||||||
|
|
||||||
return [$cc, $cxx];
|
return $wrappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getFrankenPHPVersion(TargetPackage $package): string
|
protected function getFrankenPHPVersion(TargetPackage $package): string
|
||||||
|
|||||||
@@ -175,8 +175,9 @@ trait windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$libs_cli = $this->makeLibsResponseFile($package, 'cli', "ws2_32.lib shell32.lib {$extra_libs}");
|
||||||
cmd()->cd($package->getSourceDir())
|
cmd()->cd($package->getSourceDir())
|
||||||
->exec("nmake /nologo {$debug_overrides}LIBS_CLI=\"ws2_32.lib shell32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= php.exe");
|
->exec("nmake /nologo {$debug_overrides}LIBS_CLI=\"{$libs_cli}\" EXTRA_LD_FLAGS_PROGRAM= php.exe");
|
||||||
|
|
||||||
$this->deployWindowsBinary($builder, $package, 'php-cli');
|
$this->deployWindowsBinary($builder, $package, 'php-cli');
|
||||||
}
|
}
|
||||||
@@ -245,8 +246,9 @@ trait windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$libs_cgi = $this->makeLibsResponseFile($package, 'cgi', "ws2_32.lib kernel32.lib advapi32.lib {$extra_libs}");
|
||||||
cmd()->cd($package->getSourceDir())
|
cmd()->cd($package->getSourceDir())
|
||||||
->exec("nmake /nologo {$debug_overrides}LIBS_CGI=\"ws2_32.lib kernel32.lib advapi32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= php-cgi.exe");
|
->exec("nmake /nologo {$debug_overrides}LIBS_CGI=\"{$libs_cgi}\" EXTRA_LD_FLAGS_PROGRAM= php-cgi.exe");
|
||||||
|
|
||||||
$this->deployWindowsBinary($builder, $package, 'php-cgi');
|
$this->deployWindowsBinary($builder, $package, 'php-cgi');
|
||||||
}
|
}
|
||||||
@@ -331,8 +333,9 @@ trait windows
|
|||||||
|
|
||||||
$fake_cli = $package->getBuildOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI' : '';
|
$fake_cli = $package->getBuildOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI' : '';
|
||||||
|
|
||||||
|
$libs_micro = $this->makeLibsResponseFile($package, 'micro', "ws2_32.lib shell32.lib {$extra_libs}");
|
||||||
cmd()->cd($package->getSourceDir())
|
cmd()->cd($package->getSourceDir())
|
||||||
->exec("nmake /nologo {$debug_overrides}LIBS_MICRO=\"ws2_32.lib shell32.lib {$extra_libs}\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" EXTRA_LD_FLAGS_PROGRAM= micro");
|
->exec("nmake /nologo {$debug_overrides}LIBS_MICRO=\"{$libs_micro}\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" EXTRA_LD_FLAGS_PROGRAM= micro");
|
||||||
|
|
||||||
$this->deployWindowsBinary($builder, $package, 'php-micro');
|
$this->deployWindowsBinary($builder, $package, 'php-micro');
|
||||||
}
|
}
|
||||||
@@ -884,4 +887,17 @@ C_CODE;
|
|||||||
|
|
||||||
$package->setOutput('PHP headers path for embed SAPI', $php_include_dir);
|
$package->setOutput('PHP headers path for embed SAPI', $php_include_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the collected lib list into a linker response file and return the @file
|
||||||
|
* token for the nmake command line. With big extension stacks (imagick alone pulls
|
||||||
|
* in ~60 static libs) the inline list can push the whole command past cmd.exe's
|
||||||
|
* 8191 character limit; link.exe reads @file without any length limit.
|
||||||
|
*/
|
||||||
|
private function makeLibsResponseFile(TargetPackage $package, string $sapi, string $libs): string
|
||||||
|
{
|
||||||
|
$file = "{$package->getSourceDir()}\\spc-libs-{$sapi}.rsp";
|
||||||
|
FileSystem::writeFile($file, $libs);
|
||||||
|
return "@{$file}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user