Skip to content

Commit f8c8300

Browse files
authored
Add Windows CGI SAPI build support (#927)
2 parents 5476385 + 09198b4 commit f8c8300

File tree

8 files changed

+95
-30
lines changed

8 files changed

+95
-30
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ jobs:
175175
key: php-dependencies-${{ matrix.os }}
176176

177177
- name: "Install Dependencies"
178-
run: composer update -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
178+
run: composer update -vvv --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --no-plugins
179179

180180
- name: "Run Build Tests (doctor)"
181181
run: php src/globals/test-extensions.php doctor_cmd ${{ matrix.os }} ${{ matrix.php }}

config/env.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools"
6565
; upx executable path
6666
UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe"
6767
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
68-
SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static
68+
SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static,win32_api
6969

7070
[linux]
7171
; Linux can use different build toolchains.

config/source.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@
762762
"micro": {
763763
"type": "git",
764764
"path": "php-src/sapi/micro",
765-
"rev": "php-85-win",
765+
"rev": "master",
766766
"url": "https://github.com/static-php/phpmicro",
767767
"license": {
768768
"type": "file",

src/SPC/ConsoleApplication.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
*/
3535
final class ConsoleApplication extends Application
3636
{
37-
public const string VERSION = '2.7.5';
37+
public const string VERSION = '2.7.6';
3838

3939
public function __construct()
4040
{

src/SPC/builder/windows/WindowsBuilder.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
5757
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
5858
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
5959
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
60+
$enableCgi = ($build_target & BUILD_TARGET_CGI) === BUILD_TARGET_CGI;
6061

6162
SourcePatcher::patchBeforeBuildconf($this);
6263

@@ -102,13 +103,13 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
102103
->exec(
103104
"{$this->sdk_prefix} configure.bat --task-args \"" .
104105
'--disable-all ' .
105-
'--disable-cgi ' .
106106
'--with-php-build=' . BUILD_ROOT_PATH . ' ' .
107107
'--with-extra-includes=' . BUILD_INCLUDE_PATH . ' ' .
108108
'--with-extra-libs=' . BUILD_LIB_PATH . ' ' .
109-
($enableCli ? '--enable-cli=yes ' : '--enable-cli=no ') .
110-
($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
111-
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
109+
($enableCli ? '--enable-cli ' : '--disable-cli ') .
110+
($enableMicro ? ('--enable-micro ' . $micro_logo . $micro_w32) : '--disable-micro ') .
111+
($enableEmbed ? '--enable-embed ' : '--disable-embed ') .
112+
($enableCgi ? '--enable-cgi ' : '--disable-cgi ') .
112113
$config_file_scan_dir .
113114
$opcache_jit_arg .
114115
"{$this->makeStaticExtensionArgs()} " .
@@ -127,6 +128,10 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
127128
if ($enableFpm) {
128129
logger()->warning('Windows does not support fpm SAPI, I will skip it.');
129130
}
131+
if ($enableCgi) {
132+
logger()->info('building cgi');
133+
$this->buildCgi();
134+
}
130135
if ($enableMicro) {
131136
logger()->info('building micro');
132137
$this->buildMicro();
@@ -159,6 +164,20 @@ public function buildCli(): void
159164
$this->deployBinary(BUILD_TARGET_CLI);
160165
}
161166

167+
public function buildCgi(): void
168+
{
169+
SourcePatcher::patchWindowsCGITarget();
170+
171+
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
172+
173+
// add nmake wrapper
174+
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cgi_wrapper.bat', "nmake /nologo LIBS_CGI=\"ws2_32.lib kernel32.lib advapi32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*");
175+
176+
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cgi_wrapper.bat --task-args php-cgi.exe");
177+
178+
$this->deployBinary(BUILD_TARGET_CGI);
179+
}
180+
162181
public function buildEmbed(): void
163182
{
164183
// TODO: add embed support for windows
@@ -265,7 +284,7 @@ public function sanityCheck(mixed $build_target): void
265284
// sanity check for php-cli
266285
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
267286
logger()->info('running cli sanity check');
268-
[$ret, $output] = cmd()->execWithResult(BUILD_ROOT_PATH . '\bin\php.exe -n -r "echo \"hello\";"');
287+
[$ret, $output] = cmd()->execWithResult(BUILD_BIN_PATH . '\php.exe -n -r "echo \"hello\";"');
269288
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
270289
throw new ValidationException('cli failed sanity check', validation_module: 'php-cli function check');
271290
}
@@ -284,7 +303,7 @@ public function sanityCheck(mixed $build_target): void
284303
if (file_exists($test_file)) {
285304
@unlink($test_file);
286305
}
287-
file_put_contents($test_file, file_get_contents(BUILD_ROOT_PATH . '\bin\micro.sfx') . $task['content']);
306+
file_put_contents($test_file, file_get_contents(BUILD_BIN_PATH . '\micro.sfx') . $task['content']);
288307
chmod($test_file, 0755);
289308
[$ret, $out] = cmd()->execWithResult($test_file);
290309
foreach ($task['conditions'] as $condition => $closure) {
@@ -298,6 +317,17 @@ public function sanityCheck(mixed $build_target): void
298317
}
299318
}
300319
}
320+
321+
// sanity check for php-cgi
322+
if (($build_target & BUILD_TARGET_CGI) === BUILD_TARGET_CGI) {
323+
logger()->info('running cgi sanity check');
324+
FileSystem::writeFile(SOURCE_PATH . '\php-cgi-test.php', '<?php echo "<h1>Hello, World!</h1>"; ?>');
325+
[$ret, $output] = cmd()->execWithResult(BUILD_BIN_PATH . '\php-cgi.exe -n -f ' . SOURCE_PATH . '\php-cgi-test.php');
326+
$raw_output = implode("\n", $output);
327+
if ($ret !== 0 || !str_contains($raw_output, 'Hello, World!')) {
328+
throw new ValidationException("cgi failed sanity check. code: {$ret}, output: {$raw_output}", validation_module: 'php-cgi sanity check');
329+
}
330+
}
301331
}
302332

303333
/**
@@ -311,20 +341,21 @@ public function deployBinary(int $type): bool
311341
$src = match ($type) {
312342
BUILD_TARGET_CLI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php.exe",
313343
BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx",
344+
BUILD_TARGET_CGI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php-cgi.exe",
314345
default => throw new SPCInternalException("Deployment does not accept type {$type}"),
315346
};
316347

317348
// with-upx-pack for cli and micro
318349
if ($this->getOption('with-upx-pack', false)) {
319-
if ($type === BUILD_TARGET_CLI || ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0)) {
350+
if ($type === BUILD_TARGET_CLI || $type === BUILD_TARGET_CGI || ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0)) {
320351
cmd()->exec(getenv('UPX_EXEC') . ' --best ' . escapeshellarg($src));
321352
}
322353
}
323354

324355
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
325-
FileSystem::createDir(BUILD_ROOT_PATH . '\bin');
356+
FileSystem::createDir(BUILD_BIN_PATH);
326357

327-
cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '\bin\\'));
358+
cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\'));
328359
return true;
329360
}
330361

src/SPC/command/BuildPHPCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function configure(): void
3333
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)');
3434
$this->addOption('build-embed', null, null, 'Build embed SAPI (not available on Windows)');
3535
$this->addOption('build-frankenphp', null, null, 'Build FrankenPHP SAPI (not available on Windows)');
36-
$this->addOption('build-cgi', null, null, 'Build cgi SAPI (not available on Windows)');
36+
$this->addOption('build-cgi', null, null, 'Build cgi SAPI');
3737
$this->addOption('build-all', null, null, 'Build all SAPI');
3838
$this->addOption('no-strip', null, null, 'build without strip, keep symbols to debug');
3939
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');

src/SPC/store/SourcePatcher.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,39 @@ public static function patchWindowsCLITarget(): void
549549
FileSystem::writeFile(SOURCE_PATH . '/php-src/Makefile', implode("\r\n", $lines));
550550
}
551551

552+
/**
553+
* Patch cgi SAPI Makefile for Windows.
554+
*/
555+
public static function patchWindowsCGITarget(): void
556+
{
557+
// search Makefile code line contains "$(BUILD_DIR)\php-cgi.exe:"
558+
$content = FileSystem::readFile(SOURCE_PATH . '/php-src/Makefile');
559+
$lines = explode("\r\n", $content);
560+
$line_num = 0;
561+
$found = false;
562+
foreach ($lines as $v) {
563+
if (str_contains($v, '$(BUILD_DIR)\php-cgi.exe:')) {
564+
$found = $line_num;
565+
break;
566+
}
567+
++$line_num;
568+
}
569+
if ($found === false) {
570+
throw new PatchException('Windows Makefile patching for php-cgi.exe target', 'Cannot patch windows CGI Makefile, Makefile does not contain "$(BUILD_DIR)\php-cgi.exe:" line');
571+
}
572+
// cli: $(BUILD_DIR)\php.exe: $(DEPS_CLI) $(CLI_GLOBAL_OBJS) $(BUILD_DIR)\$(PHPLIB) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest
573+
// $lines[$line_num] = '$(BUILD_DIR)\php.exe: generated_files $(DEPS_CLI) $(CLI_GLOBAL_OBJS) $(PHP_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest';
574+
// cgi: $(BUILD_DIR)\php-cgi.exe: $(DEPS_CGI) $(CGI_GLOBAL_OBJS) $(BUILD_DIR)\$(PHPLIB) $(BUILD_DIR)\php-cgi.exe.res $(BUILD_DIR)\php-cgi.exe.manifest
575+
$lines[$line_num] = '$(BUILD_DIR)\php-cgi.exe: $(DEPS_CGI) $(CGI_GLOBAL_OBJS) $(PHP_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php-cgi.exe.res $(BUILD_DIR)\php-cgi.exe.manifest';
576+
577+
// cli: @"$(LINK)" /nologo $(CGI_GLOBAL_OBJS_RESP) $(BUILD_DIR)\$(PHPLIB) $(LIBS_CGI) $(BUILD_DIR)\php-cgi.exe.res /out:$(BUILD_DIR)\php-cgi.exe $(LDFLAGS) $(LDFLAGS_CGI)
578+
$lines[$line_num + 1] = "\t" . '@"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CGI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CGI) $(BUILD_DIR)\php-cgi.exe.res /out:$(BUILD_DIR)\php-cgi.exe $(LDFLAGS) $(LDFLAGS_CGI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286';
579+
FileSystem::writeFile(SOURCE_PATH . '/php-src/Makefile', implode("\r\n", $lines));
580+
581+
// Patch cgi-static, comment ZEND_TSRMLS_CACHE_DEFINE()
582+
FileSystem::replaceFileRegex(SOURCE_PATH . '\php-src\sapi\cgi\cgi_main.c', '/^ZEND_TSRMLS_CACHE_DEFINE\(\)/m', '// ZEND_TSRMLS_CACHE_DEFINE()');
583+
}
584+
552585
public static function patchPhpLibxml212(): bool
553586
{
554587
$file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h');

src/globals/test-extensions.php

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,28 @@
1414
// test php version (8.1 ~ 8.4 available, multiple for matrix)
1515
$test_php_version = [
1616
'8.1',
17-
// '8.2',
18-
// '8.3',
19-
// '8.4',
17+
'8.2',
18+
'8.3',
19+
'8.4',
2020
'8.5',
2121
// 'git',
2222
];
2323

2424
// test os (macos-15-intel, macos-15, ubuntu-latest, windows-latest are available)
2525
$test_os = [
26-
'macos-15-intel', // bin/spc for x86_64
27-
'macos-15', // bin/spc for arm64
28-
'ubuntu-latest', // bin/spc-alpine-docker for x86_64
29-
'ubuntu-22.04', // bin/spc-gnu-docker for x86_64
30-
'ubuntu-24.04', // bin/spc for x86_64
31-
'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64
32-
'ubuntu-24.04-arm', // bin/spc for arm64
33-
// 'windows-latest', // .\bin\spc.ps1
26+
// 'macos-15-intel', // bin/spc for x86_64
27+
// 'macos-15', // bin/spc for arm64
28+
// 'ubuntu-latest', // bin/spc-alpine-docker for x86_64
29+
// 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64
30+
// 'ubuntu-24.04', // bin/spc for x86_64
31+
// 'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64
32+
// 'ubuntu-24.04-arm', // bin/spc for arm64
33+
// 'windows-2022', // .\bin\spc.ps1
34+
'windows-2025',
3435
];
3536

3637
// whether enable thread safe
37-
$zts = false;
38+
$zts = true;
3839

3940
$no_strip = false;
4041

@@ -49,8 +50,8 @@
4950

5051
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
5152
$extensions = match (PHP_OS_FAMILY) {
52-
'Linux', 'Darwin' => 'gettext',
53-
'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip',
53+
'Linux', 'Darwin' => 'pdo_pgsql',
54+
'Windows' => 'bcmath,bz2,calendar,ctype,curl,dba,dom,exif,ffi,fileinfo,filter,ftp,iconv,libxml,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_mysql,pdo_sqlite,pdo_sqlsrv,phar,session,shmop,simdjson,simplexml,soap,sockets,sqlite3,sqlsrv,ssh2,sysvshm,tokenizer,xml,xmlreader,xmlwriter,yaml,zip,zlib',
5455
};
5556

5657
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
@@ -61,7 +62,7 @@
6162
};
6263

6364
// If you want to test lib-suggests for all extensions and libraries, set it to true.
64-
$with_suggested_libs = true;
65+
$with_suggested_libs = false;
6566

6667
// If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true.
6768
$with_libs = match (PHP_OS_FAMILY) {
@@ -208,7 +209,7 @@ function quote2(string $param): string
208209
passthru($prefix . $down_cmd, $retcode);
209210
break;
210211
case 'build_cmd':
211-
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
212+
passthru($prefix . $build_cmd . ' --build-cli --build-micro --build-cgi', $retcode);
212213
break;
213214
case 'build_embed_cmd':
214215
if ($frankenphp) {

0 commit comments

Comments
 (0)