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;
|
2025-04-18 14:38:22 +08:00
|
|
|
use SPC\util\SPCConfigUtil;
|
2023-03-18 17:32:21 +08:00
|
|
|
|
|
|
|
|
class Extension
|
|
|
|
|
{
|
|
|
|
|
protected array $dependencies = [];
|
|
|
|
|
|
2025-03-24 23:50:12 +08:00
|
|
|
protected bool $build_shared = false;
|
|
|
|
|
|
|
|
|
|
protected bool $build_static = false;
|
|
|
|
|
|
|
|
|
|
protected string $source_dir;
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
|
|
|
|
* @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");
|
|
|
|
|
}
|
2025-03-24 23:50:12 +08:00
|
|
|
// set source_dir for builtin
|
|
|
|
|
if ($ext_type === 'builtin') {
|
|
|
|
|
$this->source_dir = SOURCE_PATH . '/php-src/ext/' . $this->name;
|
2025-04-22 20:25:44 +08:00
|
|
|
} elseif ($ext_type === 'external') {
|
2025-03-24 23:50:12 +08:00
|
|
|
$source = Config::getExt($this->name, 'source');
|
|
|
|
|
if ($source === null) {
|
|
|
|
|
throw new RuntimeException("{$ext_type} extension {$name} source not found");
|
|
|
|
|
}
|
|
|
|
|
$source_path = Config::getSource($source)['path'] ?? null;
|
|
|
|
|
$source_path = $source_path === null ? SOURCE_PATH . '/' . $source : SOURCE_PATH . '/' . $source_path;
|
|
|
|
|
$this->source_dir = $source_path;
|
2025-04-22 20:25:44 +08:00
|
|
|
} else {
|
|
|
|
|
$this->source_dir = SOURCE_PATH . '/php-src';
|
2025-03-24 23:50:12 +08:00
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-12 01:16:57 +08:00
|
|
|
public function getFrameworks(): array
|
|
|
|
|
{
|
|
|
|
|
return Config::getExt($this->getName(), 'frameworks', []);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
|
|
|
|
* 获取开启该扩展的 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
|
|
|
*/
|
2025-05-21 12:01:00 +07:00
|
|
|
public function getConfigureArg(bool $shared = false): string
|
2023-03-18 17:32:21 +08:00
|
|
|
{
|
2025-05-21 12:01:00 +07:00
|
|
|
return match (PHP_OS_FAMILY) {
|
|
|
|
|
'Windows' => $this->getWindowsConfigureArg($shared),
|
|
|
|
|
'Darwin',
|
|
|
|
|
'Linux',
|
|
|
|
|
'BSD' => $this->getUnixConfigureArg($shared),
|
|
|
|
|
default => throw new WrongUsageException(PHP_OS_FAMILY . ' build is not supported yet'),
|
|
|
|
|
};
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据 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
|
|
|
*/
|
2025-05-21 12:01:00 +07:00
|
|
|
public function getEnableArg(bool $shared = false): string
|
2023-03-18 17:32:21 +08:00
|
|
|
{
|
|
|
|
|
$_name = str_replace('_', '-', $this->name);
|
|
|
|
|
return match ($arg_type = Config::getExt($this->name, 'arg-type', 'enable')) {
|
2025-05-21 14:10:56 +07:00
|
|
|
'enable' => '--enable-' . $_name . ($shared ? '=shared' : '') . ' ',
|
2025-07-22 18:29:58 +07:00
|
|
|
'enable-path' => '--enable-' . $_name . '=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . ' ',
|
2025-05-21 12:01:00 +07:00
|
|
|
'with' => '--with-' . $_name . ($shared ? '=shared' : '') . ' ',
|
2025-07-22 18:29:58 +07:00
|
|
|
'with-path' => '--with-' . $_name . '=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . ' ',
|
2023-03-18 17:32:21 +08:00
|
|
|
'none', 'custom' => '',
|
2025-07-22 18:29:58 +07:00
|
|
|
default => throw new WrongUsageException("argType does not accept {$arg_type}, use [enable/with/with-path] ."),
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 12:01:00 +07:00
|
|
|
public function getWindowsConfigureArg(bool $shared = false): string
|
2023-04-15 18:45:11 +08:00
|
|
|
{
|
2025-05-21 12:01:00 +07:00
|
|
|
return $this->getEnableArg();
|
2023-04-15 18:45:11 +08:00
|
|
|
// Windows is not supported yet
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-27 11:12:19 +07:00
|
|
|
public function getUnixConfigureArg(bool $shared = false): string
|
2023-04-15 18:45:11 +08:00
|
|
|
{
|
2025-05-21 12:01:00 +07:00
|
|
|
return $this->getEnableArg($shared);
|
2023-04-08 11:49:06 +08:00
|
|
|
}
|
|
|
|
|
|
2023-07-28 23:45:39 +08:00
|
|
|
/**
|
|
|
|
|
* Patch code before ./buildconf
|
2025-03-11 07:44:31 +01:00
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
2023-07-28 23:45:39 +08:00
|
|
|
*/
|
|
|
|
|
public function patchBeforeBuildconf(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Patch code before ./configure
|
2025-03-11 07:44:31 +01:00
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
2023-07-28 23:45:39 +08:00
|
|
|
*/
|
|
|
|
|
public function patchBeforeConfigure(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Patch code before make
|
2025-03-11 07:44:31 +01:00
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
2023-07-28 23:45:39 +08:00
|
|
|
*/
|
|
|
|
|
public function patchBeforeMake(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-18 08:57:07 +07:00
|
|
|
/**
|
2025-05-20 20:00:37 +07:00
|
|
|
* Patch code before shared extension phpize
|
2025-05-18 08:57:07 +07:00
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
|
|
|
|
*/
|
2025-06-20 17:11:52 +07:00
|
|
|
public function patchBeforeSharedPhpize(): bool
|
2025-05-18 08:57:07 +07:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-20 20:00:37 +07:00
|
|
|
/**
|
|
|
|
|
* Patch code before shared extension ./configure
|
|
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
|
|
|
|
*/
|
|
|
|
|
public function patchBeforeSharedConfigure(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-20 15:25:07 +07:00
|
|
|
/**
|
|
|
|
|
* Patch code before shared extension make
|
|
|
|
|
* If you need to patch some code, overwrite this
|
|
|
|
|
* return true if you patched something, false if not
|
|
|
|
|
*/
|
|
|
|
|
public function patchBeforeSharedMake(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 10:52:46 +07:00
|
|
|
/**
|
|
|
|
|
* @return string
|
2025-05-25 11:00:38 +07:00
|
|
|
* returns a command line string with all required shared extensions to load
|
|
|
|
|
* i.e.; pdo_pgsql would return:
|
2025-05-25 10:52:46 +07:00
|
|
|
*
|
|
|
|
|
* `-d "extension=pgsql" -d "extension=pdo_pgsql"`
|
|
|
|
|
* @throws FileSystemException
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
*/
|
2025-05-25 11:07:44 +07:00
|
|
|
public function getSharedExtensionLoadString(): string
|
2025-05-21 17:57:53 +07:00
|
|
|
{
|
|
|
|
|
$loaded = [];
|
|
|
|
|
$order = [];
|
|
|
|
|
|
|
|
|
|
$resolve = function ($extension) use (&$resolve, &$loaded, &$order) {
|
|
|
|
|
if (isset($loaded[$extension->getName()])) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$loaded[$extension->getName()] = true;
|
|
|
|
|
|
|
|
|
|
foreach ($this->dependencies as $dependency) {
|
|
|
|
|
$resolve($dependency);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$order[] = $extension;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$resolve($this);
|
|
|
|
|
|
|
|
|
|
$ret = '';
|
|
|
|
|
foreach ($order as $ext) {
|
|
|
|
|
if ($ext instanceof Extension && $ext->isBuildShared()) {
|
2025-06-06 23:49:58 +07:00
|
|
|
if (Config::getExt($ext->getName(), 'zend-extension', false) === true) {
|
2025-05-22 16:48:22 +07:00
|
|
|
$ret .= " -d \"zend_extension={$ext->getName()}\"";
|
2025-05-21 18:35:48 +07:00
|
|
|
} else {
|
2025-05-22 16:48:22 +07:00
|
|
|
$ret .= " -d \"extension={$ext->getName()}\"";
|
2025-05-21 18:35:48 +07:00
|
|
|
}
|
2025-05-21 17:57:53 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-31 21:01:22 +07:00
|
|
|
if ($ret !== '') {
|
|
|
|
|
$ret = ' -d "extension_dir=' . BUILD_MODULES_PATH . '"' . $ret;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 17:57:53 +07:00
|
|
|
return $ret;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2025-05-25 11:07:44 +07:00
|
|
|
$sharedExtensions = $this->getSharedExtensionLoadString();
|
2025-06-17 14:01:53 +07:00
|
|
|
[$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
|
2023-11-01 01:46:21 +08:00
|
|
|
if ($ret !== 0) {
|
2025-06-17 14:01:53 +07:00
|
|
|
throw new RuntimeException(
|
|
|
|
|
'extension ' . $this->getName() . ' failed runtime check: php-cli returned ' . $ret . "\n" .
|
|
|
|
|
join("\n", $out)
|
|
|
|
|
);
|
2023-11-01 01:46:21 +08:00
|
|
|
}
|
|
|
|
|
|
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(
|
2024-07-19 22:46:40 +08:00
|
|
|
['<?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
|
|
|
);
|
|
|
|
|
|
2025-05-21 18:35:48 +07:00
|
|
|
[$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' -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
|
2025-02-06 23:59:02 +09:00
|
|
|
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -n --ri "' . $this->getDistName() . '"', false);
|
2024-01-10 21:08:25 +08:00
|
|
|
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
|
|
|
);
|
|
|
|
|
|
2025-02-06 23:59:02 +09:00
|
|
|
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -n -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
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-25 13:29:55 +08:00
|
|
|
/**
|
|
|
|
|
* Build shared extension
|
|
|
|
|
*
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
2025-03-24 23:50:12 +08:00
|
|
|
public function buildShared(): void
|
|
|
|
|
{
|
2025-06-12 20:51:17 +07:00
|
|
|
if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) {
|
|
|
|
|
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
|
|
|
|
|
logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (Config::getExt($this->getName(), 'build-with-php') === true) {
|
|
|
|
|
logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)');
|
|
|
|
|
logger()->warning('Try deleting your build and source folders and running `spc build`` again.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-20 00:27:33 +07:00
|
|
|
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
|
|
|
|
|
logger()->info('Shared extension [' . $this->getName() . '] was already built, skipping (' . $this->getName() . '.so)');
|
|
|
|
|
}
|
2025-05-22 12:27:41 +07:00
|
|
|
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
|
2025-05-23 10:00:31 +07:00
|
|
|
foreach ($this->dependencies as $dependency) {
|
|
|
|
|
if (!$dependency instanceof Extension) {
|
|
|
|
|
continue;
|
2025-05-20 23:04:34 +07:00
|
|
|
}
|
2025-05-23 10:00:31 +07:00
|
|
|
if (!$dependency->isBuildStatic()) {
|
|
|
|
|
logger()->info('extension ' . $this->getName() . ' requires extension ' . $dependency->getName());
|
|
|
|
|
$dependency->buildShared();
|
2025-05-22 12:27:41 +07:00
|
|
|
}
|
2025-05-20 23:04:34 +07:00
|
|
|
}
|
2025-03-24 23:50:12 +08:00
|
|
|
match (PHP_OS_FAMILY) {
|
|
|
|
|
'Darwin', 'Linux' => $this->buildUnixShared(),
|
|
|
|
|
default => throw new WrongUsageException(PHP_OS_FAMILY . ' build shared extensions is not supported yet'),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 14:45:09 +07:00
|
|
|
/**
|
|
|
|
|
* Build shared extension for Unix
|
|
|
|
|
*
|
|
|
|
|
* @throws FileSystemException
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
* @throws \ReflectionException
|
|
|
|
|
* @throws \Throwable
|
|
|
|
|
*/
|
|
|
|
|
public function buildUnixShared(): void
|
|
|
|
|
{
|
|
|
|
|
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
|
|
|
|
[$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs();
|
2025-06-13 16:25:31 +07:00
|
|
|
|
|
|
|
|
// macOS ld64 doesn't understand these, while Linux and BSD do
|
|
|
|
|
// use them to make sure that all symbols are picked up, even if a library has already been visited before
|
|
|
|
|
$preStatic = PHP_OS_FAMILY !== 'Darwin' ? '-Wl,-Bstatic -Wl,--start-group ' : '';
|
|
|
|
|
$postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group -Wl,-Bdynamic ' : ' ';
|
2025-04-18 14:38:22 +08:00
|
|
|
$env = [
|
|
|
|
|
'CFLAGS' => $config['cflags'],
|
2025-06-06 23:49:58 +07:00
|
|
|
'CXXFLAGS' => $config['cflags'],
|
2025-06-11 22:43:41 +07:00
|
|
|
'LDFLAGS' => $config['ldflags'],
|
2025-06-13 16:25:31 +07:00
|
|
|
'LIBS' => $preStatic . $staticLibString . $postStatic . $sharedLibString,
|
2025-05-15 19:52:30 +07:00
|
|
|
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
2025-04-18 14:38:22 +08:00
|
|
|
];
|
2025-06-11 21:54:59 +07:00
|
|
|
|
2025-06-20 17:11:52 +07:00
|
|
|
if ($this->patchBeforeSharedPhpize()) {
|
|
|
|
|
logger()->info("Extension [{$this->getName()}] patched before shared phpize");
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 23:50:12 +08:00
|
|
|
// prepare configure args
|
|
|
|
|
shell()->cd($this->source_dir)
|
2025-04-18 14:38:22 +08:00
|
|
|
->setEnv($env)
|
2025-06-09 14:38:45 +07:00
|
|
|
->exec(BUILD_BIN_PATH . '/phpize');
|
2025-05-20 20:00:37 +07:00
|
|
|
|
|
|
|
|
if ($this->patchBeforeSharedConfigure()) {
|
2025-06-20 17:11:52 +07:00
|
|
|
logger()->info("Extension [{$this->getName()}] patched before shared configure");
|
2025-05-20 20:00:37 +07:00
|
|
|
}
|
2025-05-21 14:15:58 +07:00
|
|
|
|
2025-05-20 20:00:37 +07:00
|
|
|
shell()->cd($this->source_dir)
|
|
|
|
|
->setEnv($env)
|
2025-06-11 22:41:55 +07:00
|
|
|
->exec(
|
|
|
|
|
'./configure ' . $this->getUnixConfigureArg(true) .
|
|
|
|
|
' --with-php-config=' . BUILD_BIN_PATH . '/php-config ' .
|
|
|
|
|
'--enable-shared --disable-static'
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-13 16:25:31 +07:00
|
|
|
// some extensions don't define their dependencies well, this patch is only needed for a few
|
2025-06-11 22:41:55 +07:00
|
|
|
FileSystem::replaceFileRegex(
|
|
|
|
|
$this->source_dir . '/Makefile',
|
|
|
|
|
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
|
|
|
|
|
'$1 ' . $staticLibString
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-20 15:25:07 +07:00
|
|
|
if ($this->patchBeforeSharedMake()) {
|
2025-06-20 17:11:52 +07:00
|
|
|
logger()->info("Extension [{$this->getName()}] patched before shared make");
|
2025-06-20 15:25:07 +07:00
|
|
|
}
|
|
|
|
|
|
2025-06-11 22:41:55 +07:00
|
|
|
shell()->cd($this->source_dir)
|
|
|
|
|
->setEnv($env)
|
2025-06-11 22:45:08 +07:00
|
|
|
->exec('make clean')
|
2025-06-09 14:38:45 +07:00
|
|
|
->exec('make -j' . $this->builder->concurrency)
|
|
|
|
|
->exec('make install');
|
2025-03-24 23:50:12 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-30 22:37:01 +08:00
|
|
|
/**
|
|
|
|
|
* Get current extension version
|
|
|
|
|
*
|
|
|
|
|
* @return null|string Version string or null
|
|
|
|
|
*/
|
|
|
|
|
public function getExtVersion(): ?string
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 23:50:12 +08:00
|
|
|
public function setBuildStatic(): void
|
|
|
|
|
{
|
2025-03-26 12:39:15 +08:00
|
|
|
if (!in_array('static', Config::getExtTarget($this->name))) {
|
2025-03-27 11:12:19 +07:00
|
|
|
throw new WrongUsageException("Extension [{$this->name}] does not support static build!");
|
2025-03-26 12:39:15 +08:00
|
|
|
}
|
2025-03-24 23:50:12 +08:00
|
|
|
$this->build_static = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setBuildShared(): void
|
|
|
|
|
{
|
2025-03-27 11:12:19 +07:00
|
|
|
if (!in_array('shared', Config::getExtTarget($this->name))) {
|
|
|
|
|
throw new WrongUsageException("Extension [{$this->name}] does not support shared build!");
|
2025-03-24 23:50:12 +08:00
|
|
|
}
|
|
|
|
|
$this->build_shared = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isBuildShared(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->build_shared;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isBuildStatic(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->build_static;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-08 14:49:06 +07:00
|
|
|
/**
|
|
|
|
|
* Get required static and shared libraries as a pair of strings in format -l{libname} -l{libname2}
|
|
|
|
|
*
|
|
|
|
|
* @return array [staticLibString, sharedLibString]
|
|
|
|
|
*/
|
2025-06-17 18:03:27 +07:00
|
|
|
protected function getStaticAndSharedLibs(): array
|
2025-06-08 14:49:06 +07:00
|
|
|
{
|
|
|
|
|
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
|
|
|
|
$sharedLibString = '';
|
|
|
|
|
$staticLibString = '';
|
|
|
|
|
$staticLibs = $this->getLibFilesString();
|
2025-07-22 11:23:42 +07:00
|
|
|
$staticLibs = str_replace([BUILD_LIB_PATH . '/lib', '.a'], ['-l', ''], $staticLibs);
|
2025-06-08 14:49:06 +07:00
|
|
|
$staticLibs = explode('-l', $staticLibs . ' ' . $config['libs']);
|
|
|
|
|
foreach ($staticLibs as $lib) {
|
|
|
|
|
$lib = trim($lib);
|
|
|
|
|
if ($lib === '') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$static_lib = 'lib' . $lib . '.a';
|
2025-06-19 11:20:57 +07:00
|
|
|
if (file_exists(BUILD_LIB_PATH . '/' . $static_lib) && !str_contains($static_lib, 'libphp')) {
|
2025-06-08 14:49:06 +07:00
|
|
|
if (!str_contains($staticLibString, '-l' . $lib . ' ')) {
|
|
|
|
|
$staticLibString .= '-l' . $lib . ' ';
|
|
|
|
|
}
|
|
|
|
|
} elseif (!str_contains($sharedLibString, '-l' . $lib . ' ')) {
|
|
|
|
|
$sharedLibString .= '-l' . $lib . ' ';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-22 11:49:13 +07:00
|
|
|
// move -lstdc++ to static libraries because centos 7 the shared libstdc++ is incomplete
|
2025-07-22 11:48:12 +07:00
|
|
|
if (str_contains((string) getenv('PATH'), 'rh/devtoolset-10')) {
|
2025-06-29 16:00:17 +08:00
|
|
|
$staticLibString .= ' -lstdc++';
|
|
|
|
|
$sharedLibString = str_replace('-lstdc++', '', $sharedLibString);
|
|
|
|
|
}
|
2025-06-11 22:41:55 +07:00
|
|
|
return [trim($staticLibString), trim($sharedLibString)];
|
2025-06-08 14:49:06 +07:00
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-06 23:49:58 +07:00
|
|
|
if (array_key_exists(0, $deps)) {
|
|
|
|
|
$zero = [0 => $deps[0]];
|
|
|
|
|
unset($deps[0]);
|
|
|
|
|
return $zero + $deps;
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
return $deps;
|
|
|
|
|
}
|
|
|
|
|
}
|