2023-03-18 17:32:21 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace SPC\builder;
|
|
|
|
|
|
|
|
|
|
use SPC\exception\FileSystemException;
|
|
|
|
|
use SPC\exception\RuntimeException;
|
2023-03-29 21:39:36 +08:00
|
|
|
use SPC\exception\WrongUsageException;
|
2023-03-18 17:32:21 +08:00
|
|
|
use SPC\store\Config;
|
2024-01-10 21:08:25 +08:00
|
|
|
use SPC\store\FileSystem;
|
2023-03-18 17:32:21 +08:00
|
|
|
|
|
|
|
|
class Extension
|
|
|
|
|
{
|
|
|
|
|
protected array $dependencies = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws FileSystemException
|
2023-04-15 18:45:11 +08:00
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function __construct(protected string $name, protected BuilderBase $builder)
|
|
|
|
|
{
|
|
|
|
|
$ext_type = Config::getExt($this->name, 'type');
|
|
|
|
|
$unix_only = Config::getExt($this->name, 'unix-only', false);
|
|
|
|
|
$windows_only = Config::getExt($this->name, 'windows-only', false);
|
|
|
|
|
if (PHP_OS_FAMILY !== 'Windows' && $windows_only) {
|
|
|
|
|
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Linux and macOS platform");
|
|
|
|
|
}
|
|
|
|
|
if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
|
|
|
|
|
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取开启该扩展的 PHP 编译添加的参数
|
|
|
|
|
*
|
2023-08-20 19:51:45 +08:00
|
|
|
* @throws FileSystemException
|
2023-04-15 18:45:11 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getConfigureArg(): string
|
|
|
|
|
{
|
|
|
|
|
$arg = $this->getEnableArg();
|
|
|
|
|
switch (PHP_OS_FAMILY) {
|
|
|
|
|
case 'Windows':
|
2024-01-10 21:08:25 +08:00
|
|
|
$arg .= $this->getWindowsConfigureArg();
|
2023-03-18 17:32:21 +08:00
|
|
|
break;
|
|
|
|
|
case 'Darwin':
|
|
|
|
|
case 'Linux':
|
2023-10-15 16:44:56 +08:00
|
|
|
case 'BSD':
|
2023-03-18 17:32:21 +08:00
|
|
|
$arg .= $this->getUnixConfigureArg();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return $arg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据 ext 的 arg-type 获取对应开启的参数,一般都是 --enable-xxx 和 --with-xxx
|
|
|
|
|
*
|
|
|
|
|
* @throws FileSystemException
|
2023-04-15 18:45:11 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getEnableArg(): string
|
|
|
|
|
{
|
|
|
|
|
$_name = str_replace('_', '-', $this->name);
|
|
|
|
|
return match ($arg_type = Config::getExt($this->name, 'arg-type', 'enable')) {
|
2023-04-15 18:45:11 +08:00
|
|
|
'enable' => '--enable-' . $_name . ' ',
|
|
|
|
|
'with' => '--with-' . $_name . ' ',
|
|
|
|
|
'with-prefix' => '--with-' . $_name . '="' . BUILD_ROOT_PATH . '" ',
|
2023-03-18 17:32:21 +08:00
|
|
|
'none', 'custom' => '',
|
2023-04-15 18:45:11 +08:00
|
|
|
default => throw new WrongUsageException("argType does not accept {$arg_type}, use [enable/with/with-prefix] ."),
|
2023-03-18 17:32:21 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出当前扩展依赖的所有 lib 库生成的 .a 静态编译库文件,以字符串形式导出,用空格分割
|
|
|
|
|
*/
|
|
|
|
|
public function getLibFilesString(): string
|
|
|
|
|
{
|
|
|
|
|
$ret = array_map(
|
|
|
|
|
fn ($x) => $x->getStaticLibFiles(),
|
|
|
|
|
$this->getLibraryDependencies(recursive: true)
|
|
|
|
|
);
|
|
|
|
|
return implode(' ', $ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查下依赖就行了,作用是导入依赖给 Extension 对象,今后可以对库依赖进行选择性处理
|
|
|
|
|
*
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws FileSystemException
|
2023-04-15 18:45:11 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function checkDependency(): static
|
|
|
|
|
{
|
|
|
|
|
foreach (Config::getExt($this->name, 'lib-depends', []) as $name) {
|
|
|
|
|
$this->addLibraryDependency($name);
|
|
|
|
|
}
|
|
|
|
|
foreach (Config::getExt($this->name, 'lib-suggests', []) as $name) {
|
|
|
|
|
$this->addLibraryDependency($name, true);
|
|
|
|
|
}
|
|
|
|
|
foreach (Config::getExt($this->name, 'ext-depends', []) as $name) {
|
|
|
|
|
$this->addExtensionDependency($name);
|
|
|
|
|
}
|
|
|
|
|
foreach (Config::getExt($this->name, 'ext-suggests', []) as $name) {
|
|
|
|
|
$this->addExtensionDependency($name, true);
|
|
|
|
|
}
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getExtensionDependency(): array
|
|
|
|
|
{
|
|
|
|
|
return array_filter($this->dependencies, fn ($x) => $x instanceof Extension);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getName(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-08 11:49:06 +08:00
|
|
|
/**
|
|
|
|
|
* returns extension dist name
|
|
|
|
|
*/
|
|
|
|
|
public function getDistName(): string
|
|
|
|
|
{
|
2023-04-15 18:45:11 +08:00
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getWindowsConfigureArg(): string
|
|
|
|
|
{
|
|
|
|
|
return '';
|
|
|
|
|
// Windows is not supported yet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getUnixConfigureArg(): string
|
|
|
|
|
{
|
|
|
|
|
return '';
|
2023-04-08 11:49:06 +08:00
|
|
|
}
|
|
|
|
|
|
2023-07-28 23:45:39 +08:00
|
|
|
/**
|
|
|
|
|
* Patch code before ./buildconf
|
|
|
|
|
* If you need to patch some code, overwrite this and return true
|
|
|
|
|
*/
|
|
|
|
|
public function patchBeforeBuildconf(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Patch code before ./configure
|
|
|
|
|
* If you need to patch some code, overwrite this and return true
|
|
|
|
|
*/
|
|
|
|
|
public function patchBeforeConfigure(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Patch code before make
|
|
|
|
|
* If you need to patch some code, overwrite this and return true
|
|
|
|
|
*/
|
|
|
|
|
public function patchBeforeMake(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-01 01:46:21 +08:00
|
|
|
/**
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
2024-01-10 21:08:25 +08:00
|
|
|
public function runCliCheckUnix(): void
|
2023-11-01 01:46:21 +08:00
|
|
|
{
|
2024-01-10 21:08:25 +08:00
|
|
|
// Run compile check if build target is cli
|
2024-06-03 23:16:15 +08:00
|
|
|
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
|
2024-01-10 21:08:25 +08:00
|
|
|
// If check failed, throw RuntimeException
|
2023-11-01 01:46:21 +08:00
|
|
|
[$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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-03 23:16:15 +08:00
|
|
|
if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) {
|
2023-11-01 01:46:21 +08:00
|
|
|
// Trim additional content & escape special characters to allow inline usage
|
|
|
|
|
$test = str_replace(
|
|
|
|
|
['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
|
|
|
|
|
['', '', '', '\"', '\$'],
|
2024-06-03 23:16:15 +08:00
|
|
|
file_get_contents(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')
|
2023-11-01 01:46:21 +08:00
|
|
|
);
|
|
|
|
|
|
2024-02-16 20:17:34 +08:00
|
|
|
[$ret, $out] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"');
|
2024-01-10 21:08:25 +08:00
|
|
|
if ($ret !== 0) {
|
2024-02-16 20:17:34 +08:00
|
|
|
if ($this->builder->getOption('debug')) {
|
|
|
|
|
var_dump($out);
|
|
|
|
|
}
|
2024-01-10 21:08:25 +08:00
|
|
|
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
public function runCliCheckWindows(): void
|
|
|
|
|
{
|
|
|
|
|
// Run compile check if build target is cli
|
2024-06-03 23:16:15 +08:00
|
|
|
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
|
2024-01-10 21:08:25 +08:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-03 23:16:15 +08:00
|
|
|
if (file_exists(FileSystem::convertPath(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php'))) {
|
2024-01-10 21:08:25 +08:00
|
|
|
// Trim additional content & escape special characters to allow inline usage
|
|
|
|
|
$test = str_replace(
|
|
|
|
|
['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
|
2024-02-23 00:56:28 +08:00
|
|
|
['', '', '', '\"', '$'],
|
2024-06-03 23:16:15 +08:00
|
|
|
file_get_contents(FileSystem::convertPath(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php'))
|
2024-01-10 21:08:25 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -r "' . trim($test) . '"');
|
2023-11-01 01:46:21 +08:00
|
|
|
if ($ret !== 0) {
|
|
|
|
|
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 13:01:11 +08:00
|
|
|
public function validate(): void
|
|
|
|
|
{
|
|
|
|
|
// do nothing, just throw wrong usage exception if not valid
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
protected function addLibraryDependency(string $name, bool $optional = false): void
|
|
|
|
|
{
|
|
|
|
|
$depLib = $this->builder->getLib($name);
|
|
|
|
|
if (!$depLib) {
|
|
|
|
|
if (!$optional) {
|
|
|
|
|
throw new RuntimeException("extension {$this->name} requires library {$name}");
|
|
|
|
|
}
|
|
|
|
|
logger()->info("enabling {$this->name} without library {$name}");
|
|
|
|
|
} else {
|
|
|
|
|
$this->dependencies[] = $depLib;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
protected function addExtensionDependency(string $name, bool $optional = false): void
|
|
|
|
|
{
|
|
|
|
|
$depExt = $this->builder->getExt($name);
|
|
|
|
|
if (!$depExt) {
|
|
|
|
|
if (!$optional) {
|
|
|
|
|
throw new RuntimeException("{$this->name} requires extension {$name}");
|
|
|
|
|
}
|
|
|
|
|
logger()->info("enabling {$this->name} without extension {$name}");
|
|
|
|
|
} else {
|
|
|
|
|
$this->dependencies[] = $depExt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getLibraryDependencies(bool $recursive = false): array
|
|
|
|
|
{
|
|
|
|
|
$ret = array_filter($this->dependencies, fn ($x) => $x instanceof LibraryBase);
|
|
|
|
|
if (!$recursive) {
|
|
|
|
|
return $ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$deps = [];
|
|
|
|
|
|
|
|
|
|
$added = 1;
|
|
|
|
|
while ($added !== 0) {
|
|
|
|
|
$added = 0;
|
|
|
|
|
foreach ($ret as $depName => $dep) {
|
|
|
|
|
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
|
|
|
|
|
if (!in_array($depdepName, array_keys($deps), true)) {
|
|
|
|
|
$deps[$depdepName] = $depdep;
|
|
|
|
|
++$added;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!in_array($depName, array_keys($deps), true)) {
|
|
|
|
|
$deps[$depName] = $dep;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $deps;
|
|
|
|
|
}
|
|
|
|
|
}
|