add windows support

This commit is contained in:
crazywhalecc
2024-01-10 21:08:25 +08:00
committed by Jerry Ma
parent 5f8641f417
commit 104778d17a
39 changed files with 1166 additions and 230 deletions

View File

@@ -23,7 +23,7 @@ use Symfony\Component\Console\Command\ListCommand;
*/
final class ConsoleApplication extends Application
{
public const VERSION = '2.0.1';
public const VERSION = '2.1.0-beta.1';
public function __construct()
{

View File

@@ -9,10 +9,8 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourceExtractor;
use SPC\util\CustomExt;
use SPC\util\DependencyUtil;
abstract class BuilderBase
{
@@ -43,64 +41,7 @@ abstract class BuilderBase
* @throws WrongUsageException
* @internal
*/
public function buildLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// pkg-config must be compiled first, whether it is specified or not
if (!in_array('pkg-config', $sorted_libraries)) {
array_unshift($sorted_libraries, 'pkg-config');
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// patch point
$this->emitPatchPoint('before-libs-extract');
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
$this->emitPatchPoint('after-libs-extract');
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
abstract public function buildLibs(array $sorted_libraries);
/**
* Add library to build.
@@ -242,6 +183,7 @@ abstract class BuilderBase
{
$ret = [];
foreach ($this->exts as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg());
}
logger()->debug('Using configure: ' . implode(' ', $ret));
@@ -417,4 +359,20 @@ abstract class BuilderBase
);
}
}
/**
* Generate micro extension test php code.
*/
protected function generateMicroExtTests(): string
{
$php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n";
foreach ($this->getExts() as $ext) {
$ext_name = $ext->getDistName();
$php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n";
$php .= "assert(extension_loaded('{$ext_name}'));\n\n";
}
$php .= "echo '[micro-test-end]';\n";
return $php;
}
}

View File

@@ -7,6 +7,7 @@ namespace SPC\builder;
use SPC\builder\freebsd\BSDBuilder;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
@@ -27,11 +28,7 @@ class BuilderProvider
public static function makeBuilderByInput(InputInterface $input): BuilderBase
{
self::$builder = match (PHP_OS_FAMILY) {
// 'Windows' => new WindowsBuilder(
// binary_sdk_dir: $input->getOption('with-sdk-binary-dir'),
// vs_ver: $input->getOption('vs-ver'),
// arch: $input->getOption('arch'),
// ),
'Windows' => new WindowsBuilder($input->getOptions()),
'Darwin' => new MacOSBuilder($input->getOptions()),
'Linux' => new LinuxBuilder($input->getOptions()),
'BSD' => new BSDBuilder($input->getOptions()),

View File

@@ -8,6 +8,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
class Extension
{
@@ -42,7 +43,7 @@ class Extension
$arg = $this->getEnableArg();
switch (PHP_OS_FAMILY) {
case 'Windows':
$arg = $this->getWindowsConfigureArg();
$arg .= $this->getWindowsConfigureArg();
break;
case 'Darwin':
case 'Linux':
@@ -164,14 +165,13 @@ class Extension
}
/**
* Run compile check if build target is cli
* If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
* If check failed, throw RuntimeException
*
* @throws RuntimeException
*/
public function runCliCheck(): void
public function runCliCheckUnix(): void
{
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
// If check failed, throw RuntimeException
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "' . $this->getDistName() . '"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
@@ -192,6 +192,34 @@ class Extension
}
}
/**
* @throws RuntimeException
*/
public function runCliCheckWindows(): void
{
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
// If check failed, throw RuntimeException
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe --ri "' . $this->getDistName() . '"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
}
if (file_exists(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php'))) {
// Trim additional content & escape special characters to allow inline usage
$test = str_replace(
['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
['', '', '', '\"', '\$'],
file_get_contents(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php'))
);
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -r "' . trim($test) . '"');
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
}
}
}
/**
* @throws RuntimeException
*/

View File

@@ -34,4 +34,9 @@ class glfw extends Extension
{
return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH;
}
public function getWindowsConfigureArg(): string
{
return '--enable-glfw=static';
}
}

View File

@@ -19,7 +19,7 @@ class mbregex extends Extension
/**
* mbregex is not an extension, we need to overwrite the default check.
*/
public function runCliCheck(): void
public function runCliCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring" | grep regex', false);
if ($ret !== 0) {

View File

@@ -11,7 +11,7 @@ use SPC\util\CustomExt;
#[CustomExt('password-argon2')]
class password_argon2 extends Extension
{
public function runCliCheck(): void
public function runCliCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "assert(defined(\'PASSWORD_ARGON2I\'));"');
if ($ret !== 0) {

View File

@@ -4,19 +4,15 @@ declare(strict_types=1);
namespace SPC\builder\freebsd;
use SPC\builder\BuilderBase;
use SPC\builder\traits\UnixBuilderTrait;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
class BSDBuilder extends BuilderBase
class BSDBuilder extends UnixBuilderBase
{
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var bool Micro patch phar flag */
private bool $phar_patched = false;

View File

@@ -4,20 +4,16 @@ declare(strict_types=1);
namespace SPC\builder\linux;
use SPC\builder\BuilderBase;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\traits\UnixBuilderTrait;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
class LinuxBuilder extends BuilderBase
class LinuxBuilder extends UnixBuilderBase
{
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var array Tune cflags */
public array $tune_c_flags;

View File

@@ -4,20 +4,16 @@ declare(strict_types=1);
namespace SPC\builder\macos;
use SPC\builder\BuilderBase;
use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\builder\traits\UnixBuilderTrait;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
class MacOSBuilder extends BuilderBase
class MacOSBuilder extends UnixBuilderBase
{
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var bool Micro patch phar flag */
private bool $phar_patched = false;

View File

@@ -1,7 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
trait LibraryTrait {}

View File

@@ -12,8 +12,6 @@ use SPC\store\FileSystem;
trait UnixLibraryTrait
{
use LibraryTrait;
/**
* @throws RuntimeException
* @throws FileSystemException

View File

@@ -2,15 +2,19 @@
declare(strict_types=1);
namespace SPC\builder\traits;
namespace SPC\builder\unix;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourceExtractor;
use SPC\util\DependencyUtil;
trait UnixBuilderTrait
abstract class UnixBuilderBase extends BuilderBase
{
/** @var string cflags */
public string $arch_c_flags;
@@ -86,6 +90,65 @@ trait UnixBuilderTrait
return $extra;
}
public function buildLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// pkg-config must be compiled first, whether it is specified or not
if (!in_array('pkg-config', $sorted_libraries)) {
array_unshift($sorted_libraries, 'pkg-config');
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// patch point
$this->emitPatchPoint('before-libs-extract');
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
$this->emitPatchPoint('after-libs-extract');
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
/**
* Sanity check after build complete
*
@@ -103,7 +166,7 @@ trait UnixBuilderTrait
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheck();
$ext->runCliCheckUnix();
}
}

View File

@@ -4,17 +4,25 @@ declare(strict_types=1);
namespace SPC\builder\windows;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
class SystemUtil
{
/**
* @param string $name 命令名称
* @param array $paths 寻找的目标路径(如果不传入,则使用环境变量 PATH
* @return null|string 找到了返回命令路径,找不到返回 null
* Find windows program using executable name.
*
* @param string $name command name (xxx.exe)
* @param array $paths search path (default use env path)
* @return null|string null if not found, string is absolute path
*/
public static function findCommand(string $name, array $paths = []): ?string
public static function findCommand(string $name, array $paths = [], bool $include_sdk_bin = false): ?string
{
if (!$paths) {
$paths = explode(PATH_SEPARATOR, getenv('Path'));
if ($include_sdk_bin) {
$paths[] = PHP_SDK_PATH . '\bin';
}
}
foreach ($paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
@@ -23,4 +31,74 @@ class SystemUtil
}
return null;
}
/**
* Find Visual Studio installation.
*
* @return array<string, string>|false False if not installed, array contains 'version' and 'dir'
*/
public static function findVisualStudio(): array|false
{
$check_path = [
'C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
];
foreach ($check_path as $path => $vs_version) {
if (file_exists($path)) {
$vs_ver = $vs_version;
$d_dir = dirname($path, 4);
return [
'version' => $vs_ver,
'dir' => $d_dir,
];
}
}
return false;
}
/**
* Get CPU count for concurrency.
*/
public static function getCpuCount(): int
{
$result = f_exec('echo %NUMBER_OF_PROCESSORS%', $out, $code);
if ($code !== 0 || !$result) {
return 1;
}
return intval($result);
}
/**
* Create CMake toolchain file.
*
* @param null|string $cflags CFLAGS for cmake, default use '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP='
* @param null|string $ldflags LDFLAGS for cmake, default use '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt'
* @throws FileSystemException
*/
public static function makeCmakeToolchainFile(?string $cflags = null, ?string $ldflags = null): string
{
if ($cflags === null) {
$cflags = '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP=';
}
if ($ldflags === null) {
$ldflags = '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt';
}
$buildroot = str_replace('\\', '\\\\', BUILD_ROOT_PATH);
$toolchain = <<<CMAKE
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_FLAGS "{$cflags}")
SET(CMAKE_C_FLAGS_DEBUG "{$cflags}")
SET(CMAKE_CXX_FLAGS "{$cflags}")
SET(CMAKE_CXX_FLAGS_DEBUG "{$cflags}")
SET(CMAKE_EXE_LINKER_FLAGS "{$ldflags}")
SET(CMAKE_FIND_ROOT_PATH "{$buildroot}")
SET(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
CMAKE;
FileSystem::writeFile(SOURCE_PATH . '\toolchain.cmake', $toolchain);
return realpath(SOURCE_PATH . '\toolchain.cmake');
}
}

View File

@@ -0,0 +1,343 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows;
use SPC\builder\BuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourceExtractor;
use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil;
class WindowsBuilder extends BuilderBase
{
/** @var string cmake toolchain file */
public string $cmake_toolchain_file;
public string $sdk_prefix;
private bool $zts;
/** @var bool Micro patch phar flag */
private bool $phar_patched = false;
/**
* @throws FileSystemException
*/
public function __construct(array $options = [])
{
$this->options = $options;
// ---------- set necessary options ----------
// set sdk (require visual studio 16 or 17)
$vs = SystemUtil::findVisualStudio()['version'];
$this->sdk_prefix = PHP_SDK_PATH . "\\phpsdk-{$vs}-x64.bat -t";
// set zts
$this->zts = $this->getOption('enable-zts', false);
// set concurrency
$this->concurrency = SystemUtil::getCpuCount();
// make cmake toolchain
$this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile();
}
/**
* @throws RuntimeException
* @throws WrongUsageException
* @throws FileSystemException
*/
public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
{
// ---------- Update extra-libs ----------
$extra_libs = $this->getOption('extra-libs', '');
$extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', $this->getAllStaticLibFiles());
$this->setOption('extra-libs', $extra_libs);
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
SourcePatcher::patchBeforeBuildconf($this);
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} buildconf.bat");
SourcePatcher::patchBeforeConfigure($this);
$zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no ';
cmd()->cd(SOURCE_PATH . '\php-src')
->exec(
"{$this->sdk_prefix} configure.bat --task-args \"" .
'--disable-all ' .
'--disable-cgi ' .
'--with-php-build=' . BUILD_ROOT_PATH . ' ' .
'--with-extra-includes=' . BUILD_INCLUDE_PATH . ' ' .
'--with-extra-libs=' . BUILD_LIB_PATH . ' ' .
($enableCli ? '--enable-cli=yes ' : '--enable-cli=no ') .
($enableMicro ? '--enable-micro=yes ' : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
"{$this->makeExtensionArgs()} " .
$zts .
'"'
);
SourcePatcher::patchBeforeMake($this);
$this->cleanMake();
if ($enableCli) {
logger()->info('building cli');
$this->buildCli();
}
if ($enableFpm) {
logger()->warning('Windows does not support fpm SAPI, I will skip it.');
}
if ($enableMicro) {
logger()->info('building micro');
$this->buildMicro();
}
if ($enableEmbed) {
logger()->warning('Windows does not currently support embed SAPI.');
// logger()->info('building embed');
$this->buildEmbed();
}
$this->sanityCheck($build_target);
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public function buildCli(): void
{
SourcePatcher::patchWindowsCLITarget();
// add nmake wrapper
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo LIBS_CLI=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" EXTRA_LD_FLAGS_PROGRAM= %*");
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe");
$this->deployBinary(BUILD_TARGET_CLI);
}
public function buildEmbed(): void
{
// TODO: add embed support for windows
/*
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_embed_wrapper.bat', 'nmake /nologo %*');
cmd()->cd(SOURCE_PATH . '\php-src')
->exec("{$this->sdk_prefix} nmake_embed_wrapper.bat --task-args php8embed.lib");
*/
}
/**
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
public function buildMicro(): void
{
// workaround for fiber (originally from https://github.com/dixyes/lwmbs/blob/master/windows/MicroBuild.php)
$makefile = FileSystem::readFile(SOURCE_PATH . '\php-src\Makefile');
if ($this->getPHPVersionID() >= 80200 && str_contains($makefile, 'FIBER_ASM_ARCH')) {
$makefile .= "\r\n" . '$(MICRO_SFX): $(BUILD_DIR)\Zend\jump_$(FIBER_ASM_ARCH)_ms_pe_masm.obj $(BUILD_DIR)\Zend\make_$(FIBER_ASM_ARCH)_ms_pe_masm.obj' . "\r\n\r\n";
}
FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile);
// add nmake wrapper
$fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI" ' : '';
$wrapper = "nmake /nologo LIBS_MICRO=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*";
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_micro_wrapper.bat', $wrapper);
// phar patch for micro
if ($this->getExt('phar')) {
$this->phar_patched = true;
SourcePatcher::patchMicro(['phar']);
}
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_micro_wrapper.bat --task-args micro");
if ($this->phar_patched) {
SourcePatcher::patchMicro(['phar'], true);
}
$this->deployBinary(BUILD_TARGET_MICRO);
}
public function buildLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '\src\SPC\builder\\' . osfamily2dir() . '\\library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public function cleanMake(): void
{
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_clean_wrapper.bat', 'nmake /nologo %*');
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_clean_wrapper.bat --task-args \"clean\"");
}
/**
* Run extension and PHP cli and micro check
*
* @throws RuntimeException
*/
public function sanityCheck(mixed $build_target): void
{
// sanity check for php-cli
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('running cli sanity check');
[$ret, $output] = cmd()->execWithResult(BUILD_ROOT_PATH . '\bin\php.exe -r "echo \"hello\";"');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows();
}
}
// sanity check for phpmicro
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if (file_exists(SOURCE_PATH . '\hello.exe')) {
@unlink(SOURCE_PATH . '\hello.exe');
}
file_put_contents(
SOURCE_PATH . '\hello.exe',
file_get_contents(BUILD_ROOT_PATH . '\bin\micro.sfx') .
($this->getOption('with-micro-ext-test') ? $this->generateMicroExtTests() : '<?php echo "[micro-test-start][micro-test-end]";')
);
chmod(SOURCE_PATH . '\hello.exe', 0755);
[$ret, $output2] = cmd()->execWithResult(SOURCE_PATH . '\hello.exe');
$raw_out = trim(implode('', $output2));
$condition[0] = $ret === 0;
$condition[1] = str_starts_with($raw_out, '[micro-test-start]') && str_ends_with($raw_out, '[micro-test-end]');
foreach ($condition as $k => $v) {
if (!$v) {
throw new RuntimeException("micro failed sanity check with condition[{$k}], ret[{$ret}], out[{$raw_out}]");
}
}
}
}
/**
* 将编译好的二进制文件发布到 buildroot
*
* @param int $type 发布类型
* @throws RuntimeException
* @throws FileSystemException
*/
public function deployBinary(int $type): bool
{
$ts = $this->zts ? '_TS' : '';
$src = match ($type) {
BUILD_TARGET_CLI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php.exe",
BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx",
default => throw new RuntimeException('Deployment does not accept type ' . $type),
};
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '\bin');
cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '\bin\\'));
return true;
}
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public function getAllStaticLibFiles(): array
{
$libs = [];
// reorder libs
foreach ($this->libs as $lib) {
foreach ($lib->getDependencies() as $dep) {
$libs[] = $dep;
}
$libs[] = $lib;
}
$libFiles = [];
$libNames = [];
// merge libs
foreach ($libs as $lib) {
if (!in_array($lib::NAME, $libNames, true)) {
$libNames[] = $lib::NAME;
array_unshift($libFiles, ...$lib->getStaticLibs());
}
}
return $libFiles;
}
/**
* Generate command wrapper prefix for php-sdk internal commands.
*
* @param string $internal_cmd command in php-sdk-tools\bin
* @return string Example: C:\php-sdk-tools\phpsdk-vs17-x64.bat -t source\cmake_wrapper.bat --task-args
*/
public function makeSimpleWrapper(string $internal_cmd): string
{
$wrapper_bat = SOURCE_PATH . '\\' . crc32($internal_cmd) . '_wrapper.bat';
if (!file_exists($wrapper_bat)) {
file_put_contents($wrapper_bat, $internal_cmd . ' %*');
}
return "{$this->sdk_prefix} {$wrapper_bat} --task-args";
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\BuilderBase;
use SPC\builder\LibraryBase;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
abstract class WindowsLibraryBase extends LibraryBase
{
public function __construct(protected WindowsBuilder $builder)
{
parent::__construct();
}
public function getBuilder(): BuilderBase
{
return $this->builder;
}
/**
* Create a nmake wrapper file.
*
* @param string $content nmake wrapper content
* @param string $default_filename default nmake wrapper filename
* @throws FileSystemException
*/
public function makeNmakeWrapper(string $content, string $default_filename = ''): string
{
if ($default_filename === '') {
$default_filename = $this->source_dir . '\nmake_wrapper.bat';
}
FileSystem::writeFile($default_filename, $content);
return 'nmake_wrapper.bat';
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\windows\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
class openssl extends WindowsLibraryBase
{
public const NAME = 'openssl';
protected function build(): void
{
$perl = file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') ? (BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') : SystemUtil::findCommand('perl.exe');
if ($perl === null) {
throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")');
}
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper($perl),
'Configure zlib VC-WIN64A ' .
'no-shared ' .
'--prefix=' . quote(BUILD_ROOT_PATH) . ' ' .
'--with-zlib-lib=' . quote(BUILD_LIB_PATH) . ' ' .
'--with-zlib-include=' . quote(BUILD_INCLUDE_PATH) . ' ' .
'--release ' .
'no-legacy '
);
// patch zlib
FileSystem::replaceFileStr($this->source_dir . '\Makefile', 'ZLIB1', 'zlibstatic.lib');
// patch debug: https://stackoverflow.com/questions/18486243/how-do-i-build-openssl-statically-linked-against-windows-runtime
FileSystem::replaceFileStr($this->source_dir . '\Makefile', '/debug', '/incremental:no /opt:icf /dynamicbase /nxcompat /ltcg /nodefaultlib:msvcrt');
cmd()->cd($this->source_dir)->execWithWrapper(
$this->builder->makeSimpleWrapper('nmake'),
'install_dev ' .
'CNF_LDFLAGS="/NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:msvcrt /NODEFAULTLIB:msvcrtd /DEFAULTLIB:libcmt /LIBPATH:' . BUILD_LIB_PATH . ' zlibstatic.lib"'
);
copy($this->source_dir . '\ms\applink.c', BUILD_INCLUDE_PATH . '\openssl\applink.c');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class zlib extends WindowsLibraryBase
{
public const NAME = 'zlib';
protected function build(): void
{
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DSKIP_INSTALL_FILES=ON ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
copy(BUILD_LIB_PATH . '\zlibstatic.lib', BUILD_LIB_PATH . '\zlib_a.lib');
unlink(BUILD_ROOT_PATH . '\bin\zlib.dll');
unlink(BUILD_LIB_PATH . '\zlib.lib');
}
}

View File

@@ -36,6 +36,7 @@ class BuildCliCommand extends BuildCommand
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');
$this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside');
$this->addOption('with-micro-ext-test', null, null, 'Enable phpmicro with extension test code');
}
public function handle(): int
@@ -120,13 +121,17 @@ class BuildCliCommand extends BuildCommand
$fixed = ' (host system)';
}
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('Static php binary path' . $fixed . ': ' . $build_root_path . '/bin/php');
$win_suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
$path = FileSystem::convertPath("{$build_root_path}/bin/php{$win_suffix}");
logger()->info("Static php binary path{$fixed}: {$path}");
}
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
logger()->info('phpmicro binary path' . $fixed . ': ' . $build_root_path . '/bin/micro.sfx');
$path = FileSystem::convertPath("{$build_root_path}/bin/micro.sfx");
logger()->info("phpmicro binary path{$fixed}: {$path}");
}
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
logger()->info('Static php-fpm binary path' . $fixed . ': ' . $build_root_path . '/bin/php-fpm');
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM && PHP_OS_FAMILY !== 'Windows') {
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}");
}
// export metadata
@@ -135,7 +140,8 @@ class BuildCliCommand extends BuildCommand
// export licenses
$dumper = new LicenseDumper();
$dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
logger()->info('License path' . $fixed . ': ' . $build_root_path . '/license/');
$path = FileSystem::convertPath("{$build_root_path}/license/");
logger()->info("License path{$fixed}: {$path}");
return static::SUCCESS;
} catch (WrongUsageException $e) {
// WrongUsageException is not an exception, it's a user error, so we just print the error message

View File

@@ -116,6 +116,7 @@ class DownloadCommand extends BaseCommand
// get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) {
logger()->warning('Downloading with --all option will take more times to download, we recommend you to download with --for-extensions option !');
$sources = array_keys(Config::getSources());
}
}

View File

@@ -87,6 +87,10 @@ class MicroCombineCommand extends BaseCommand
// 8. Combine !
$output = FileSystem::isRelativePath($output) ? (WORKING_DIR . '/' . $output) : $output;
$file_target = file_get_contents($micro_file) . $ini_part . file_get_contents($file);
if (PHP_OS_FAMILY === 'Windows' && !str_ends_with(strtolower($output), '.exe')) {
$output .= '.exe';
}
$output = FileSystem::convertPath($output);
$result = file_put_contents($output, $file_target);
if ($result === false) {
$this->output->writeln('<error>Combine failed.</error>');

View File

@@ -9,10 +9,22 @@ use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
use SPC\store\Downloader;
use SPC\store\FileSystem;
class WindowsToolCheckList
{
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 999)]
#[AsCheckItem('if Visual Studio are installed', limit_os: 'Windows', level: 999)]
public function checkVS(): ?CheckResult
{
$vs_ver = SystemUtil::findVisualStudio();
if ($vs_ver === false) {
return CheckResult::fail('Visual Studio not installed, please install VS 2022/2019.');
}
return CheckResult::ok($vs_ver['version'] . ' ' . $vs_ver['dir']);
}
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 998)]
public function checkGit(): ?CheckResult
{
if (SystemUtil::findCommand('git.exe') === null) {
@@ -22,7 +34,7 @@ class WindowsToolCheckList
return CheckResult::ok();
}
#[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 998)]
#[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 997)]
public function checkSDK(): ?CheckResult
{
if (!file_exists(PHP_SDK_PATH . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) {
@@ -31,14 +43,80 @@ class WindowsToolCheckList
return CheckResult::ok(PHP_SDK_PATH);
}
#[AsCheckItem('if git associated command exists', limit_os: 'Windows', level: 996)]
public function checkGitPatch(): ?CheckResult
{
if (($path = SystemUtil::findCommand('patch.exe')) === null) {
return CheckResult::fail('Git patch (minGW command) not found in path. You need to add "C:\Program Files\Git\usr\bin" in Path.');
}
return CheckResult::ok();
}
#[AsCheckItem('if nasm installed', limit_os: 'Windows', level: 995)]
public function checkNasm(): ?CheckResult
{
if (SystemUtil::findCommand('nasm.exe', include_sdk_bin: true) === null) {
return CheckResult::fail('nasm.exe not found in path.', 'install-nasm');
}
return CheckResult::ok();
}
#[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)]
public function checkPerl(): ?CheckResult
{
if (file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe')) {
return CheckResult::ok(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe');
}
if (($path = SystemUtil::findCommand('perl.exe')) === null) {
return CheckResult::fail('perl not found in path.', 'install-perl');
}
if (!str_contains(implode('', cmd()->execWithResult(quote($path) . ' -v')[1]), 'MSWin32')) {
return CheckResult::fail($path . ' is not built for msvc.', 'install-perl');
}
return CheckResult::ok();
}
#[AsFixItem('install-php-sdk')]
public function installPhpSdk(): bool
{
try {
cmd(true)->exec('git clone https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH);
FileSystem::removeDir(PHP_SDK_PATH);
cmd(true)->exec('git.exe clone --depth 1 https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH);
} catch (RuntimeException) {
return false;
}
return true;
}
#[AsFixItem('install-nasm')]
public function installNasm(): bool
{
// The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`.
$nasm_ver = '2.16.01';
$nasm_dist = "nasm-{$nasm_ver}";
$source = [
'type' => 'url',
'url' => "https://www.nasm.us/pub/nasm/releasebuilds/{$nasm_ver}/win64/{$nasm_dist}-win64.zip",
];
logger()->info('Downloading ' . $source['url']);
Downloader::downloadSource('nasm', $source);
FileSystem::extractSource('nasm', DOWNLOAD_PATH . "\\{$nasm_dist}-win64.zip");
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\nasm.exe", PHP_SDK_PATH . '\bin\nasm.exe');
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\ndisasm.exe", PHP_SDK_PATH . '\bin\ndisasm.exe');
return true;
}
#[AsFixItem('install-perl')]
public function installPerl(): bool
{
$url = 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip';
$source = [
'type' => 'url',
'url' => $url,
];
logger()->info("Downloading {$url}");
Downloader::downloadSource('strawberry-perl', $source);
FileSystem::extractSource('strawberry-perl', DOWNLOAD_PATH . '\strawberry-perl-5.38.0.1-64bit-portable.zip', '../buildroot/perl');
return true;
}
}

View File

@@ -168,14 +168,15 @@ class Downloader
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null): void
{
logger()->debug("Downloading {$url}");
pcntl_signal(SIGINT, function () use ($filename) {
if (file_exists(DOWNLOAD_PATH . '/' . $filename)) {
$cancel_func = function () use ($filename) {
if (file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename))) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
unlink(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename));
}
});
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}");
pcntl_signal(SIGINT, SIG_IGN);
};
self::registerCancelEvent($cancel_func);
self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"));
self::unregisterCancelEvent();
logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]);
}
@@ -187,7 +188,7 @@ class Downloader
*/
public static function lockSource(string $name, array $data): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
@@ -204,24 +205,25 @@ class Downloader
*/
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void
{
$download_path = DOWNLOAD_PATH . "/{$name}";
$download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}");
if (file_exists($download_path)) {
FileSystem::removeDir($download_path);
}
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
pcntl_signal(SIGINT, function () use ($download_path) {
$cancel_func = function () use ($download_path) {
if (is_dir($download_path)) {
logger()->warning('Removing path ' . $download_path);
FileSystem::removeDir($download_path);
}
});
};
self::registerCancelEvent($cancel_func);
f_passthru(
'git clone' . $check .
SPC_GIT_EXEC . ' clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
pcntl_signal(SIGINT, SIG_IGN);
self::unregisterCancelEvent();
// Lock
logger()->debug("Locking git source {$name}");
@@ -251,7 +253,6 @@ class Downloader
* @param null|array $source source meta info: [type, path, rev, url, filename, regex, license]
* @throws DownloaderException
* @throws FileSystemException
* @throws RuntimeException
*/
public static function downloadSource(string $name, ?array $source = null, bool $force = false): void
{
@@ -359,12 +360,12 @@ class Downloader
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$cmd = "curl -sfSL {$methodArg} {$headerArg} \"{$url}\"";
$cmd = SPC_CURL_EXEC . " -sfSL {$methodArg} {$headerArg} \"{$url}\"";
if (getenv('CACHE_API_EXEC') === 'yes') {
if (!file_exists(DOWNLOAD_PATH . '/.curl_exec_cache')) {
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'))) {
$cache = [];
} else {
$cache = json_decode(file_get_contents(DOWNLOAD_PATH . '/.curl_exec_cache'), true);
$cache = json_decode(file_get_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache')), true);
}
if (isset($cache[$cmd]) && $cache[$cmd]['expire'] >= time()) {
return $cache[$cmd]['cache'];
@@ -375,7 +376,7 @@ class Downloader
}
$cache[$cmd]['cache'] = implode("\n", $output);
$cache[$cmd]['expire'] = time() + 3600;
file_put_contents(DOWNLOAD_PATH . '/.curl_exec_cache', json_encode($cache));
file_put_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'), json_encode($cache));
return $cache[$cmd]['cache'];
}
f_exec($cmd, $output, $ret);
@@ -404,7 +405,35 @@ class Downloader
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$check = !defined('DEBUG_MODE') ? 's' : '#';
$cmd = "curl -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
$cmd = SPC_CURL_EXEC . " -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
f_passthru($cmd);
}
/**
* Register CTRL+C event for different OS.
*
* @param callable $callback callback function
*/
private static function registerCancelEvent(callable $callback): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler($callback);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, $callback);
} else {
logger()->debug('You have not enabled `pcntl` extension, cannot prevent download file corruption when Ctrl+C');
}
}
/**
* Unegister CTRL+C event for different OS.
*/
private static function unregisterCancelEvent(): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler(null);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, SIG_IGN);
}
}
}

View File

@@ -160,78 +160,35 @@ class FileSystem
}
logger()->info("extracting {$name} source to " . ($move_path ?? SOURCE_PATH . "/{$name}") . ' ...');
try {
$target = $move_path ?? (SOURCE_PATH . "/{$name}");
$target = self::convertPath($move_path ?? (SOURCE_PATH . "/{$name}"));
// Git source, just move
if (is_dir($filename)) {
self::copyDir($filename, $target);
if (is_dir(self::convertPath($filename))) {
self::copyDir(self::convertPath($filename), $target);
self::emitSourceExtractHook($name);
return;
}
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $name . 'source dir failed');
}
if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $name . 'source dir failed');
}
switch (self::extname($filename)) {
case 'xz':
case 'txz':
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
// f_passthru("cat {$filename} | xz -d | tar -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'gz':
case 'tgz':
f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1");
break;
case 'bz2':
f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("unzip {$filename} -d {$target}");
break;
// case 'zstd':
// case 'zst':
// passthru('cat ' . $filename . ' | zstd -d | tar -x -C ".SOURCE_PATH . "/' . $name . ' --strip-components 1', $ret);
// break;
case 'tar':
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
default:
throw new FileSystemException('unknown archive format: ' . $filename);
}
match (self::extname($filename)) {
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
'zip' => f_passthru("unzip {$filename} -d {$target}"),
default => throw new FileSystemException('unknown archive format: ' . $filename),
};
} elseif (PHP_OS_FAMILY === 'Windows') {
// find 7z
$_7zExe = self::findCommandPath('7z', [
'C:\Program Files\7-Zip-Zstandard',
'C:\Program Files (x86)\7-Zip-Zstandard',
'C:\Program Files\7-Zip',
'C:\Program Files (x86)\7-Zip',
]);
if (!$_7zExe) {
throw new FileSystemException('windows needs 7z to unpack');
}
// use php-sdk-binary-tools/bin/7za.exe
$_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
f_mkdir(SOURCE_PATH . "/{$name}", recursive: true);
switch (self::extname($filename)) {
case 'zstd':
case 'zst':
if (!str_contains($_7zExe, 'Zstandard')) {
throw new FileSystemException("zstd is not supported: {$filename}");
}
// no break
case 'xz':
case 'txz':
case 'gz':
case 'tgz':
case 'bz2':
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1");
break;
case 'tar':
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("\"{$_7zExe}\" x {$filename} -o{$target}");
break;
default:
throw new FileSystemException("unknown archive format: {$filename}");
}
match (self::extname($filename)) {
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'xz', 'txz', 'gz', 'tgz', 'bz2' => f_passthru("\"{$_7z}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1"),
'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
default => throw new FileSystemException("unknown archive format: {$filename}"),
};
}
self::emitSourceExtractHook($name);
} catch (RuntimeException $e) {
@@ -398,7 +355,7 @@ class FileSystem
public static function createDir(string $path): void
{
if (!is_dir($path) && !f_mkdir($path, 0755, true) && !is_dir($path)) {
throw new FileSystemException(sprintf('无法建立目录:%s', $path));
throw new FileSystemException(sprintf('Unable to create dir: %s', $path));
}
}
@@ -408,7 +365,7 @@ class FileSystem
*/
public static function writeFile(string $path, mixed $content, ...$args): bool|int|string
{
$dir = pathinfo($path, PATHINFO_DIRNAME);
$dir = pathinfo(self::convertPath($path), PATHINFO_DIRNAME);
if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
throw new FileSystemException('Write file failed, cannot create parent directory: ' . $dir);
}

View File

@@ -6,6 +6,7 @@ namespace SPC\store;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
@@ -31,6 +32,20 @@ class SourcePatcher
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
}
}
// patch windows php 8.1 bug
if (PHP_OS_FAMILY === 'Windows' && $builder->getPHPVersionID() >= 80100 && $builder->getPHPVersionID() < 80200) {
logger()->info('Patching PHP 8.1 windows Fiber bug');
FileSystem::replaceFileStr(
SOURCE_PATH . '\php-src\win32\build\config.w32',
"ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');",
"ADD_FLAG('ASM_OBJS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj $(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');"
);
FileSystem::replaceFileStr(
SOURCE_PATH . '\php-src\win32\build\config.w32',
"ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');",
''
);
}
}
/**
@@ -181,9 +196,10 @@ class SourcePatcher
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCPY 1$/m', '');
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCAT 1$/m', '');
}
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', '');
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'install-micro', '');
if ($builder instanceof UnixBuilderBase) {
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', '');
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'install-micro', '');
}
// call extension patch before make
foreach ($builder->getExts() as $ext) {
@@ -252,4 +268,32 @@ class SourcePatcher
@unlink($embed_c_bak);
return $result;
}
/**
* Patch cli SAPI Makefile for Windows.
*
* @throws FileSystemException
* @throws RuntimeException
*/
public static function patchWindowsCLITarget(): void
{
// search Makefile code line contains "$(BUILD_DIR)\php.exe:"
$content = FileSystem::readFile(SOURCE_PATH . '/php-src/Makefile');
$lines = explode("\r\n", $content);
$line_num = 0;
$found = false;
foreach ($lines as $v) {
if (strpos($v, '$(BUILD_DIR)\php.exe:') !== false) {
$found = $line_num;
break;
}
++$line_num;
}
if ($found === false) {
throw new RuntimeException('Cannot patch windows CLI Makefile!');
}
$lines[$line_num] = '$(BUILD_DIR)\php.exe: generated_files $(DEPS_CLI) $(PHP_GLOBAL_OBJS) $(CLI_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest';
$lines[$line_num + 1] = "\t" . '"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CLI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CLI) $(BUILD_DIR)\php.exe.res /out:$(BUILD_DIR)\php.exe $(LDFLAGS) $(LDFLAGS_CLI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286';
FileSystem::writeFile(SOURCE_PATH . '/php-src/Makefile', implode("\r\n", $lines));
}
}

View File

@@ -50,6 +50,11 @@ class WindowsCmd
return $this;
}
public function execWithWrapper(string $wrapper, string $args): WindowsCmd
{
return $this->exec($wrapper . ' "' . str_replace('"', '^"', $args) . '"');
}
public function execWithResult(string $cmd, bool $with_log = true): array
{
if ($with_log) {

View File

@@ -2,6 +2,7 @@
declare(strict_types=1);
use SPC\store\FileSystem;
use ZM\Logger\ConsoleLogger;
define('WORKING_DIR', getcwd());
@@ -10,12 +11,12 @@ define('ROOT_DIR', dirname(__DIR__, 2));
// CLI start time
define('START_TIME', microtime(true));
define('BUILD_ROOT_PATH', is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot'));
define('SOURCE_PATH', is_string($a = getenv('SOURCE_PATH')) ? $a : (WORKING_DIR . '/source'));
define('DOWNLOAD_PATH', is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads'));
define('BUILD_BIN_PATH', is_string($a = getenv('INSTALL_BIN_PATH')) ? $a : (BUILD_ROOT_PATH . '/bin'));
define('BUILD_LIB_PATH', is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib'));
define('BUILD_INCLUDE_PATH', is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include'));
define('BUILD_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot')));
define('SOURCE_PATH', FileSystem::convertPath(is_string($a = getenv('SOURCE_PATH')) ? $a : (WORKING_DIR . '/source')));
define('DOWNLOAD_PATH', FileSystem::convertPath(is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads')));
define('BUILD_BIN_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_BIN_PATH')) ? $a : (BUILD_ROOT_PATH . '/bin')));
define('BUILD_LIB_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib')));
define('BUILD_INCLUDE_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include')));
define('SEPARATED_PATH', [
'/' . pathinfo(BUILD_LIB_PATH)['basename'], // lib
'/' . pathinfo(BUILD_INCLUDE_PATH)['basename'], // include
@@ -26,6 +27,10 @@ if (PHP_OS_FAMILY === 'Windows') {
define('PHP_SDK_PATH', is_string($a = getenv('PHP_SDK_PATH')) ? $a : (WORKING_DIR . DIRECTORY_SEPARATOR . 'php-sdk-binary-tools'));
}
// for windows, prevent calling Invoke-WebRequest and wsl command
const SPC_CURL_EXEC = PHP_OS_FAMILY === 'Windows' ? 'curl.exe' : 'curl';
const SPC_GIT_EXEC = PHP_OS_FAMILY === 'Windows' ? 'git.exe' : 'git';
// dangerous command
const DANGER_CMD = [
'rm',

View File

@@ -1,5 +1,7 @@
<?php
/** @noinspection ALL */
declare(strict_types=1);
/**
@@ -10,14 +12,24 @@ declare(strict_types=1);
// --------------------------------- edit area ---------------------------------
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = 'ldap';
$extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '',
'Windows' => 'mbstring,openssl',
};
// If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`).
$with_libs = '';
$with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '',
'Windows' => '',
};
// Please change your test base combination. We recommend testing with `common`.
// You can use `common`, `bulk`, `minimal` or `none`.
$base_combination = 'minimal';
// note: combination is only available for *nix platform. Windows must use `none` combination
$base_combination = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'minimal',
'Windows' => 'none',
};
// -------------------------- code area, do not modify --------------------------
@@ -51,6 +63,6 @@ $final_libs = trim($with_libs, $trim_value);
echo match ($argv[1]) {
'extensions' => $final_extensions,
'libs' => $final_libs,
'cmd' => $final_extensions . ($final_libs === '' ? '' : (' --with-libs=' . $final_libs)),
'cmd' => '"' . $final_extensions . '"' . ($final_libs === '' ? '' : (' --with-libs="' . $final_libs . '"')),
default => '',
};

View File

@@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
assert(function_exists('openssl_digest'));
assert(openssl_digest('123456', 'md5') === 'e10adc3949ba59abbe56e057f20f883e');

View File

@@ -3,3 +3,4 @@
declare(strict_types=1);
assert(function_exists('gzcompress'));
assert(gzdecode(gzencode('aaa')) === 'aaa');