mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-05 15:55:39 +08:00
feat(windows): replace php-sdk-binary-tools with MSYS2 + 7za-win (#1193)
This commit is contained in:
93
src/Package/Artifact/msys2_build_essentials.php
Normal file
93
src/Package/Artifact/msys2_build_essentials.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Artifact;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\BinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinary;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
|
||||
class msys2_build_essentials
|
||||
{
|
||||
// MSYS subsystem packages required for autotools-based builds.
|
||||
private const REQUIRED_PACKAGES = ['make', 'autoconf', 'automake', 'libtool', 'pkgconf', 'perl', 'bison', 're2c'];
|
||||
|
||||
#[CustomBinary('msys2-build-essentials', ['windows-x86_64'])]
|
||||
public function downBinary(ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
// MSYS2 nightly self-extracting archive; running it with `-y -oTARGET` extracts to TARGET\msys64\.
|
||||
$url = 'https://github.com/msys2/msys2-installer/releases/download/nightly-x86_64/msys2-base-x86_64-latest.sfx.exe';
|
||||
$filename = 'msys2-base-x86_64-latest.sfx.exe';
|
||||
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
|
||||
|
||||
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
|
||||
|
||||
return DownloadResult::file(
|
||||
$filename,
|
||||
['url' => $url, 'version' => 'nightly'],
|
||||
version: 'nightly',
|
||||
extract: '{pkg_root_path}/msys2-build-essentials',
|
||||
);
|
||||
}
|
||||
|
||||
#[BinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
|
||||
public function extractBinary(string $source_file, string $target_path): void
|
||||
{
|
||||
$target_path = FileSystem::convertPath($target_path);
|
||||
$source_file = FileSystem::convertPath($source_file);
|
||||
|
||||
// Guard: skip re-extraction if already initialized (marker written at end of this method).
|
||||
$marker = "{$target_path}\\.spc-msys2-initialized";
|
||||
if (file_exists($marker)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($target_path)) {
|
||||
FileSystem::createDir($target_path);
|
||||
}
|
||||
|
||||
cmd()->exec("\"{$source_file}\" -y -o\"{$target_path}\"");
|
||||
|
||||
$msys2_bin = "{$target_path}\\msys64\\usr\\bin";
|
||||
if (!file_exists("{$msys2_bin}\\bash.exe")) {
|
||||
throw new DownloaderException("MSYS2 extraction failed: bash.exe not found at {$msys2_bin}\\bash.exe");
|
||||
}
|
||||
|
||||
// Add MSYS2 usr\bin to PATH so pacman.exe can load msys-2.0.dll.
|
||||
GlobalEnvManager::addPathIfNotExists($msys2_bin);
|
||||
GlobalEnvManager::putenv('CHERE_INVOKING=yes');
|
||||
GlobalEnvManager::putenv('MSYSTEM=MSYS');
|
||||
|
||||
// Disable PGP signature checking: pacman-key --init requires a pseudo-TTY which is unavailable
|
||||
// from PHP. Patching pacman.conf is the standard approach for CI pipelines.
|
||||
$pacman_conf = "{$target_path}\\msys64\\etc\\pacman.conf";
|
||||
FileSystem::replaceFileRegex($pacman_conf, '/^SigLevel\s*=.*$/m', 'SigLevel = Never');
|
||||
|
||||
$pacman = "{$target_path}\\msys64\\usr\\bin\\pacman.exe";
|
||||
|
||||
// Two-pass update as recommended by MSYS2 CI docs.
|
||||
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
|
||||
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
|
||||
|
||||
$pkgs = implode(' ', self::REQUIRED_PACKAGES);
|
||||
cmd()->exec("\"{$pacman}\" --noconfirm -S --needed {$pkgs}");
|
||||
|
||||
FileSystem::writeFile($marker, date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
#[AfterBinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
|
||||
public function afterExtract(string $target_path): void
|
||||
{
|
||||
$target_path = FileSystem::convertPath($target_path);
|
||||
$msys2_root = "{$target_path}\\msys64";
|
||||
|
||||
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_root}");
|
||||
GlobalEnvManager::addPathIfNotExists("{$msys2_root}\\usr\\bin");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,18 @@ class gmssl extends PhpExtensionPackage
|
||||
);
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
|
||||
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
|
||||
#[PatchDescription('Fix ext-gmssl v1.1.1: pbkdf2_hmac_sm3_genkey was renamed to sm3_pbkdf2 in GmSSL >= 3.2.0')]
|
||||
public function patchPbkdf2Rename(): void
|
||||
{
|
||||
FileSystem::replaceFileStr(
|
||||
"{$this->getSourceDir()}/gmssl.c",
|
||||
'pbkdf2_hmac_sm3_genkey',
|
||||
'sm3_pbkdf2'
|
||||
);
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
|
||||
#[PatchDescription('Add CHECK_LIB to config.w32 for static Windows builds')]
|
||||
public function patchBeforeBuildconfWin(): bool
|
||||
|
||||
@@ -45,13 +45,13 @@ class gmssl
|
||||
->toStep(1)
|
||||
->build();
|
||||
|
||||
// fix cmake_install.cmake install prefix (GmSSL overrides it internally)
|
||||
$installCmake = "{$buildDir}\\cmake_install.cmake";
|
||||
FileSystem::writeFile(
|
||||
$installCmake,
|
||||
'set(CMAKE_INSTALL_PREFIX "' . str_replace('\\', '/', $lib->getBuildRootPath()) . '")' . PHP_EOL . FileSystem::readFile($installCmake)
|
||||
);
|
||||
cmd()->cd($buildDir)->exec('nmake gmssl XCFLAGS=/MT');
|
||||
|
||||
cmd()->cd($buildDir)->exec('nmake install XCFLAGS=/MT');
|
||||
$libPath = "{$lib->getBuildRootPath()}/lib";
|
||||
$incPath = "{$lib->getBuildRootPath()}/include/gmssl";
|
||||
FileSystem::createDir($libPath);
|
||||
FileSystem::createDir($incPath);
|
||||
FileSystem::copy("{$buildDir}\\bin\\gmssl.lib", "{$libPath}/gmssl.lib");
|
||||
FileSystem::copyDir("{$lib->getSourceDir()}\\include\\gmssl", $incPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class libssh2
|
||||
{
|
||||
WindowsCMakeExecutor::create($lib)
|
||||
->addConfigureArgs(
|
||||
'-DCRYPTO_BACKEND=WinCNG',
|
||||
'-DENABLE_ZLIB_COMPRESSION=ON',
|
||||
'-DBUILD_TESTING=OFF'
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ class openssl
|
||||
{
|
||||
if (SystemTarget::getTargetOS() === 'Windows') {
|
||||
global $argv;
|
||||
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
|
||||
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl\perl\bin\perl.exe';
|
||||
$perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe');
|
||||
if ($perl === null) {
|
||||
throw new EnvironmentException(
|
||||
|
||||
@@ -40,7 +40,7 @@ class curl
|
||||
->optionalPackage('brotli', ...cmake_boolean_args('CURL_BROTLI'))
|
||||
->addConfigureArgs(
|
||||
'-DBUILD_CURL_EXE=ON',
|
||||
'-DZSTD_LIBRARY=zstd_static.lib',
|
||||
'-DZSTD_LIBRARY=' . BUILD_LIB_PATH . '/zstd_static.lib',
|
||||
'-DBUILD_TESTING=OFF',
|
||||
'-DBUILD_EXAMPLES=OFF',
|
||||
'-DUSE_LIBIDN2=OFF',
|
||||
|
||||
@@ -255,6 +255,11 @@ class php extends TargetPackage
|
||||
$installer->addBuildPackage('php-embed');
|
||||
}
|
||||
|
||||
// UPX compression: ensure the upx binary package is installed when requested
|
||||
if ($package->getBuildOption('with-upx-pack')) {
|
||||
$additional_packages[] = 'upx';
|
||||
}
|
||||
|
||||
return [...$extensions_pkg, ...$additional_packages];
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,13 @@ trait windows
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
|
||||
cmd()->cd($package->getSourceDir())->exec('.\buildconf.bat');
|
||||
|
||||
// Bypass the phpsdk_version check in configure.js: we use MSVC + msys2 instead of PHP SDK, so phpsdk_version is not available and the check would always fail.
|
||||
FileSystem::replaceFileStr(
|
||||
"{$package->getSourceDir()}\\configure.js",
|
||||
'check_binary_tools_sdk();',
|
||||
'/* check_binary_tools_sdk(); skipped: using MSVC + msys2 without PHP SDK */'
|
||||
);
|
||||
|
||||
if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) {
|
||||
SourcePatcher::patchMicroWin32();
|
||||
} else {
|
||||
@@ -88,6 +95,17 @@ trait windows
|
||||
cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}");
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
|
||||
#[PatchDescription('Patch Makefile to ensure buildroot/include comes before extension CFLAGS (fixes zip.h conflict with minizip)')]
|
||||
public function patchMakefileIncludeOrder(TargetPackage $package): void
|
||||
{
|
||||
FileSystem::replaceFileStr(
|
||||
"{$package->getSourceDir()}\\Makefile",
|
||||
'$(CFLAGS_PHP_OBJ) $(CFLAGS)',
|
||||
'$(CFLAGS) $(CFLAGS_PHP_OBJ)'
|
||||
);
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
|
||||
#[PatchDescription('Patch Windows Makefile for CLI target')]
|
||||
public function patchCLITarget(TargetPackage $package): void
|
||||
|
||||
@@ -644,7 +644,7 @@ class Artifact
|
||||
'{artifact_name}' => $this->name,
|
||||
'{pkg_root_path}' => PKG_ROOT_PATH,
|
||||
'{build_root_path}' => BUILD_ROOT_PATH,
|
||||
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: WORKING_DIR . '/php-sdk-binary-tools',
|
||||
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH'),
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
|
||||
@@ -614,7 +614,7 @@ class ArtifactExtractor
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
|
||||
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: '',
|
||||
];
|
||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||
}
|
||||
|
||||
@@ -76,9 +76,10 @@ class DownloadResult
|
||||
?string $version = null,
|
||||
array $metadata = [],
|
||||
?string $downloader = null,
|
||||
mixed $extract = null,
|
||||
): DownloadResult {
|
||||
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
|
||||
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,15 @@ class CraftCommand extends BaseCommand
|
||||
// set verbosity
|
||||
$this->output->setVerbosity($craft['verbosity']);
|
||||
|
||||
// sync logger level and ApplicationContext debug mode to match the new verbosity
|
||||
$level = match ($this->output->getVerbosity()) {
|
||||
OutputInterface::VERBOSITY_VERBOSE => 'info',
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG => 'debug',
|
||||
default => 'warning',
|
||||
};
|
||||
logger()->setLevel($level);
|
||||
ApplicationContext::setDebug($this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG);
|
||||
|
||||
// apply env
|
||||
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
|
||||
|
||||
|
||||
@@ -253,6 +253,13 @@ class TestBotCommand extends BaseCommand
|
||||
$fmt($targets),
|
||||
);
|
||||
|
||||
$available_labels = implode(', ', [
|
||||
'`need-test` (gate)',
|
||||
'`test/linux` `test/windows` `test/macos` (platform)',
|
||||
'`test/tier2` (extra arch)',
|
||||
'`test/php-83` `test/php-84` (PHP version)',
|
||||
]);
|
||||
|
||||
// Case 1: need-test absent → invite the author to add it
|
||||
if (!$need_test) {
|
||||
return implode("\n", [
|
||||
@@ -261,11 +268,9 @@ class TestBotCommand extends BaseCommand
|
||||
'',
|
||||
$detected,
|
||||
'',
|
||||
'To trigger extension build tests on this PR, add the `need-test` label:',
|
||||
'To trigger extension build tests on this PR, add the `need-test` label.',
|
||||
'',
|
||||
'**Gate**: `need-test`',
|
||||
'**Platform filter** (optional, default all): `test/linux` `test/windows` `test/macos` · `test/tier2`',
|
||||
'**PHP version** (optional, default 8.5): `test/php-83` `test/php-84`',
|
||||
'**Available labels**: ' . $available_labels,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -307,6 +312,7 @@ class TestBotCommand extends BaseCommand
|
||||
'',
|
||||
$detected,
|
||||
'**Active labels**: ' . $labels_str,
|
||||
'**Available labels**: ' . $available_labels,
|
||||
'**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -54,13 +54,24 @@ class WindowsToolCheck
|
||||
return CheckResult::ok();
|
||||
}
|
||||
|
||||
#[CheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 996)]
|
||||
public function checkSDK(): ?CheckResult
|
||||
#[CheckItem('if msys2-build-essentials is installed', limit_os: 'Windows', level: 996)]
|
||||
public function checkMsys2(): ?CheckResult
|
||||
{
|
||||
if (!file_exists(getenv('PHP_SDK_PATH') . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) {
|
||||
return CheckResult::fail('php-sdk-binary-tools not downloaded', 'install-php-sdk');
|
||||
$marker = PKG_ROOT_PATH . '\msys2-build-essentials\.spc-msys2-initialized';
|
||||
if (!file_exists($marker)) {
|
||||
return CheckResult::fail('msys2-build-essentials not installed', 'install-msys2-build-essentials');
|
||||
}
|
||||
return CheckResult::ok(getenv('PHP_SDK_PATH'));
|
||||
return CheckResult::ok(PKG_ROOT_PATH . '\msys2-build-essentials\msys64');
|
||||
}
|
||||
|
||||
#[CheckItem('if 7za.exe is installed', limit_os: 'Windows', level: 999)]
|
||||
public function check7zaWin(): ?CheckResult
|
||||
{
|
||||
$path = FileSystem::convertPath(PKG_ROOT_PATH . '\bin\7za.exe');
|
||||
if (!file_exists($path)) {
|
||||
return CheckResult::fail('7za.exe not found', 'install-7za-win');
|
||||
}
|
||||
return CheckResult::ok($path);
|
||||
}
|
||||
|
||||
#[CheckItem('if nasm installed', level: 995)]
|
||||
@@ -112,12 +123,20 @@ class WindowsToolCheck
|
||||
return true;
|
||||
}
|
||||
|
||||
#[FixItem('install-php-sdk')]
|
||||
public function installSDK(): bool
|
||||
#[FixItem('install-msys2-build-essentials')]
|
||||
public function installMsys2(): bool
|
||||
{
|
||||
FileSystem::removeDir(getenv('PHP_SDK_PATH'));
|
||||
$installer = new PackageInstaller(interactive: false);
|
||||
$installer->addInstallPackage('php-sdk-binary-tools');
|
||||
$installer->addInstallPackage('msys2-build-essentials');
|
||||
$installer->run(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
#[FixItem('install-7za-win')]
|
||||
public function install7zaWin(): bool
|
||||
{
|
||||
$installer = new PackageInstaller(interactive: false);
|
||||
$installer->addInstallPackage('7za-win');
|
||||
$installer->run(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -184,12 +184,14 @@ class DefaultShell extends Shell
|
||||
*/
|
||||
public function execute7zExtract(string $archive_path, string $target_path): bool
|
||||
{
|
||||
$sdk_path = getenv('PHP_SDK_PATH');
|
||||
if ($sdk_path === false) {
|
||||
throw new SPCInternalException('PHP_SDK_PATH environment variable is not set');
|
||||
// 7za.exe is installed by the 7za-win target package into PKG_ROOT_PATH\bin,
|
||||
// which is added to PATH by MSVCToolchain::initEnv().
|
||||
$_7z_path = FileSystem::convertPath(PKG_ROOT_PATH . '\bin\7za.exe');
|
||||
if (!file_exists($_7z_path)) {
|
||||
throw new SPCInternalException('7za.exe not found. Please install the 7za-win target package.');
|
||||
}
|
||||
|
||||
$_7z = escapeshellarg(FileSystem::convertPath($sdk_path . '/bin/7za.exe'));
|
||||
$_7z = escapeshellarg(FileSystem::convertPath($_7z_path));
|
||||
$archive_arg = escapeshellarg(FileSystem::convertPath($archive_path));
|
||||
$target_arg = escapeshellarg(FileSystem::convertPath($target_path));
|
||||
|
||||
|
||||
@@ -14,10 +14,14 @@ class MSVCToolchain implements ToolchainInterface
|
||||
public function initEnv(): void
|
||||
{
|
||||
GlobalEnvManager::addPathIfNotExists(PKG_ROOT_PATH . '\bin');
|
||||
$sdk = getenv('PHP_SDK_PATH');
|
||||
if ($sdk !== false) {
|
||||
GlobalEnvManager::addPathIfNotExists($sdk . '\bin');
|
||||
GlobalEnvManager::addPathIfNotExists($sdk . '\msys2\usr\bin');
|
||||
// msys2-build-essentials: add MSYS2 usr\bin to PATH so that 7za.exe, make, autoconf, etc. are available.
|
||||
// This must be done here because msys2-build-essentials is not a dependency of any library package,
|
||||
// so its path@windows entries are not automatically applied by the package installer at runtime.
|
||||
$msys2_path = getenv('SPC_MSYS2_PATH') ?: (PKG_ROOT_PATH . '\msys2-build-essentials\msys64');
|
||||
if (is_dir($msys2_path)) {
|
||||
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_path}");
|
||||
GlobalEnvManager::addPathIfNotExists($msys2_path . '\usr\bin');
|
||||
GlobalEnvManager::addPathIfNotExists("{$msys2_path}\\usr\\lib\\p7zip");
|
||||
}
|
||||
// strawberry-perl
|
||||
if (is_dir(PKG_ROOT_PATH . '\strawberry-perl')) {
|
||||
|
||||
@@ -411,7 +411,7 @@ class FileSystem
|
||||
$replacement = [
|
||||
'{build_root_path}' => BUILD_ROOT_PATH,
|
||||
'{pkg_root_path}' => PKG_ROOT_PATH,
|
||||
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ? getenv('PHP_SDK_PATH') : WORKING_DIR . '/php-sdk-binary-tools',
|
||||
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: (PKG_ROOT_PATH . DIRECTORY_SEPARATOR . 'msys2-build-essentials' . DIRECTORY_SEPARATOR . 'msys64'),
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
|
||||
Reference in New Issue
Block a user