feat(windows): replace php-sdk-binary-tools with MSYS2 + 7za-win

- Add msys2-build-essentials target: downloads the MSYS2 nightly sfx,
  extracts it, disables PGP keyring (CI-safe), runs two-pass pacman
  update, and installs autotools build essentials (make, autoconf,
  automake, libtool, pkgconf, perl).
- Add 7za-win target: downloads 7za.exe to PKG_ROOT_PATH\bin.
- Remove php-sdk-binary-tools target and all PHP_SDK_PATH references;
  replace with SPC_MSYS2_PATH throughout Artifact, ArtifactExtractor,
  FileSystem, DefaultShell and MSVCToolchain.
- Replace {php_sdk_path} path placeholder with {spc_msys2_path}.
- WindowsToolCheck: replace checkSDK/installSDK with checkMsys2,
  installMsys2 and check7zaWin/install7zaWin fix items.
- nasm.yml: extract nasm.exe/ndisasm.exe to {pkg_root_path}/bin.
- env.ini: rename PHP_SDK_PATH to SPC_MSYS2_PATH.
- Docs: update Windows migration guide and package-model placeholder docs.
This commit is contained in:
crazywhalecc
2026-06-21 16:56:40 +08:00
parent 408d8f755c
commit 06864fc3f6
17 changed files with 172 additions and 33 deletions

View 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'];
#[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");
}
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);
}
/**

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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')) {

View File

@@ -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,