Merge pull request #673 from crazywhalecc/feat/xdebug-dynamic

add xdebug dynamic extension
This commit is contained in:
Jerry Ma 2025-04-19 16:01:07 +08:00 committed by GitHub
commit f0e634a4fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 497 additions and 163 deletions

View File

@ -197,5 +197,5 @@ jobs:
run: php src/globals/test-extensions.php build_cmd ${{ matrix.os }} ${{ matrix.php }}
- name: "Run Build Tests (build - embed for non-windows)"
if: matrix.os != 'windows-latest'
if: ${{ !startsWith(matrix.os, 'windows-') }}
run: php src/globals/test-extensions.php build_embed_cmd ${{ matrix.os }} ${{ matrix.php }}

View File

@ -122,6 +122,20 @@ MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot"
# shellcheck disable=SC2086
# shellcheck disable=SC2090
if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* Debug mode enabled, run docker in interactive mode."
echo "* You can use 'exit' to exit the docker container."
echo "* You can use 'bin/spc' like normal builds."
echo "*"
echo "* Mounted directories:"
echo "* ./config: $(pwd)/config"
echo "* ./src: $(pwd)/src"
echo "* ./buildroot: $(pwd)/buildroot"
echo "* ./source: $(pwd)/source"
echo "* ./dist: $(pwd)/dist"
echo "* ./downloads: $(pwd)/downloads"
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2
else
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2 bin/spc $@

View File

@ -12,7 +12,7 @@ DOCKER_EXECUTABLE="docker"
# shellcheck disable=SC2046
if [ $(id -u) -ne 0 ]; then
if ! docker info > /dev/null 2>&1; then
if [ "$SPC_USE_SUDO" != "yes" ]; then
if [ "$SPC_USE_SUDO" != "yes" ] && [ "$SPC_DOCKER_DEBUG" != "yes" ]; then
echo "Docker command requires sudo"
# shellcheck disable=SC2039
echo -n 'To use sudo to run docker, run "export SPC_USE_SUDO=yes" and run command again'
@ -86,7 +86,7 @@ COPY ./composer.* /app/
ADD ./bin/setup-runtime /app/bin/setup-runtime
ADD ./bin/spc /app/bin/spc
RUN /app/bin/setup-runtime
RUN /app/bin/php /app/bin/composer install --no-dev --classmap-authoritative
RUN /app/bin/php /app/bin/composer install --no-dev
ENV PATH="/app/bin:/cmake/bin:$PATH"
ENV SPC_LIBC=glibc
@ -146,6 +146,20 @@ echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"'
# shellcheck disable=SC2090
if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* Debug mode enabled, run docker in interactive mode."
echo "* You can use 'exit' to exit the docker container."
echo "* You can use 'bin/spc' like normal builds."
echo "*"
echo "* Mounted directories:"
echo "* ./config: $(pwd)/config"
echo "* ./src: $(pwd)/src"
echo "* ./buildroot: $(pwd)/buildroot"
echo "* ./source: $(pwd)/source"
echo "* ./dist: $(pwd)/dist"
echo "* ./downloads: $(pwd)/downloads"
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
$DOCKER_EXECUTABLE run --rm -it --privileged $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH
else
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH bin/spc $@

View File

@ -28,7 +28,6 @@
; PATH: static-php-cli will add `$BUILD_BIN_PATH` to PATH.
; PKG_CONFIG: static-php-cli will set `$BUILD_BIN_PATH/pkg-config` to PKG_CONFIG.
; PKG_CONFIG_PATH: static-php-cli will set `$BUILD_LIB_PATH/pkgconfig` to PKG_CONFIG_PATH.
; SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS: the default optimization CFLAGS for compiling php. (if --no-strip option is set: `-g -O0`, else: `-g -Os`)
;
; * These vars are only be defined in LinuxBuilder and cannot be changed anywhere:
; SPC_LINUX_DEFAULT_CC: the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`)
@ -98,9 +97,9 @@ SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
; EXTRA_CFLAGS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS} -fno-ident -fPIE -fPIC"
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -fno-ident -fPIE -fPIC"
; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS=""
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
; EXTRA_LDFLAGS_PROGRAM for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie"
@ -132,7 +131,7 @@ SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}"
; LDFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; EXTRA_CFLAGS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS}"
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv"
; embed type for php, static (libphp.a) or shared (libphp.dylib)

View File

@ -119,6 +119,10 @@
"Linux": "partial",
"BSD": "wip"
},
"target": [
"static",
"shared"
],
"notes": true,
"arg-type": "custom",
"type": "builtin",
@ -772,6 +776,9 @@
"Windows": "no",
"BSD": "wip"
},
"target": [
"static"
],
"notes": true,
"type": "external",
"source": "swoole",
@ -919,12 +926,16 @@
]
},
"xdebug": {
"type": "builtin",
"type": "external",
"source": "xdebug",
"target": [
"shared"
],
"support": {
"Windows": "wip",
"BSD": "no",
"Darwin": "no",
"Linux": "no"
"Darwin": "partial",
"Linux": "partial"
},
"notes": true
},
@ -1040,6 +1051,9 @@
"support": {
"BSD": "wip"
},
"target": [
"static"
],
"type": "builtin",
"arg-type": "with-prefix",
"arg-type-windows": "enable",

View File

@ -281,8 +281,7 @@
"headers-unix": [
"ares.h",
"ares_dns.h",
"ares_nameser.h",
"ares_rules.h"
"ares_nameser.h"
]
},
"libde265": {

View File

@ -913,6 +913,15 @@
"path": "COPYING"
}
},
"xdebug": {
"type": "url",
"url": "https://pecl.php.net/get/xdebug",
"filename": "xdebug.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"xhprof": {
"type": "url",
"url": "https://pecl.php.net/get/xhprof",

View File

@ -80,10 +80,9 @@ and this extension cannot be compiled into php by static linking, so it cannot b
## xdebug
1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc and then compile php-xdebug from source with the option `--with-php-config=/path/to/buildroot/bin/php-config`.
2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform,
extract the `xdebug.so` file, and then use the `--no-strip` parameter in static-php-cli to retain the debug symbol table and add the `ffi` extension.
The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`.
1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc.
2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug".
The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`.
## xml
@ -126,8 +125,8 @@ For details on the solution, see [FAQ - Unable to use ssl](../faq/#unable-to-use
## ffi
1. Due to the limitation of Linux system, you cannot use it to load other `so` extensions in the purely static compiled state (spc defaults to pure static compilation).
Linux supports loading so extensions only if they are non-statically compiled. If you need to use the ffi extension, see [Compile PHP with GNU libc](./build-with-glibc).
1. Due to the limitation of musl libc's static linkage, you cannot use ffi because dynamic libraries cannot be loaded.
If you need to use the ffi extension, see [Compile PHP with GNU libc](./build-with-glibc).
2. macOS supports the ffi extension, but errors will occur when some kernels do not contain debugging symbols.
3. Windows x64 supports the ffi extension.

View File

@ -314,6 +314,7 @@ You can try to use the following commands:
- `--with-suggested-exts`: Add `ext-suggests` as dependencies when compiling
- `--with-suggested-libs`: Add `lib-suggests` as dependencies when compiling
- `--with-upx-pack`: Use UPX to reduce the size of the binary file after compilation (you need to use `bin/spc install-pkg upx` to install upx first)
- `--build-shared=XXX,YYY`: compile the specified extension into a shared library (the default is to compile into a static library)
For hardcoding INI options, it works for cli, micro, embed sapi. Here is a simple example where we preset a larger `memory_limit` and disable the `system` function:

View File

@ -74,9 +74,9 @@ bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
## xdebug
1. Xdebug 只能作为共享扩展构建。在 Linux 上,您需要使用带有 `SPC_LIBC=glibc` 的 static-php-cli然后使用选项 `--with-php-config=/path/to/buildroot/bin/php-config` 从源代码编译 php-xdebug
2. macOS 平台可以通过在相同平台编译的 PHP 下编译一个 xdebug 扩展,并提取其中的 `xdebug.so` 文件,再在 static-php-cli 中使用 `--no-strip` 参数保留调试符号表,同时加入 `ffi` 扩展。
编译`./php` 二进制可以通过指定 INI 配置并运行,例如`./php -d 'zend_extension=xdebug.so' your-code.php`。
1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc
2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`。
## xml
@ -117,9 +117,10 @@ pgsql 16.2 修复了这个 Bug现在正常工作了。
## ffi
1. 因为 Linux 系统的限制纯静态编译的状态下spc 默认编译结果为纯静态)无法使用它加载其他 `so` 扩展。Linux 支持加载 so 扩展的前提是非静态编译。如果你需要使用 ffi 扩展,请参见 [编译 GNU libc 的 PHP](./build-with-glibc)。
2. macOS 支持 ffi 扩展,但是部分内核下不包含调试符号时会出现错误。
3. Windows 支持 ffi 扩展。
1. 由于 musl libc 静态链接的限制,无法加载动态库,因此无法使用 ffi。
如果您需要使用 ffi 扩展,请参阅 [使用 GNU libc 编译 PHP](./build-with-glibc)。
2. macOS 支持 ffi 扩展,但某些内核不包含调试符号时会出现错误。
3. Windows x64 支持 ffi 扩展。
## xhprof

View File

@ -272,6 +272,7 @@ bin/spc build mysqlnd,pdo_mysql --build-all --debug
- `--with-suggested-exts`: 编译时将 `ext-suggests` 也作为编译依赖加入
- `--with-suggested-libs`: 编译时将 `lib-suggests` 也作为编译依赖加入
- `--with-upx-pack`: 编译后使用 UPX 减小二进制文件体积(需先使用 `bin/spc install-pkg upx` 安装 upx
- `--build-shared=XXX,YYY`: 编译时将指定的扩展编译为共享库(默认编译为静态库)
硬编码 INI 选项适用于 cli、micro、embed。有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 `memory_limit`,并且禁用 `system` 函数:

View File

@ -122,9 +122,12 @@ abstract class BuilderBase
*
* @return Extension[]
*/
public function getExts(): array
public function getExts(bool $including_shared = true): array
{
return $this->exts;
if ($including_shared) {
return $this->exts;
}
return array_filter($this->exts, fn ($ext) => !$ext->isBuildShared());
}
/**
@ -136,7 +139,7 @@ abstract class BuilderBase
public function hasCpp(): bool
{
// judge cpp-extension
$exts = array_keys($this->getExts());
$exts = array_keys($this->getExts(false));
foreach ($exts as $ext) {
if (Config::getExt($ext, 'cpp-extension', false) === true) {
return true;
@ -170,23 +173,46 @@ abstract class BuilderBase
* @throws \Throwable|WrongUsageException
* @internal
*/
public function proveExts(array $extensions, bool $skip_check_deps = false): void
public function proveExts(array $static_extensions, array $shared_extensions = [], bool $skip_check_deps = false, bool $skip_extract = false): void
{
CustomExt::loadCustomExt();
$this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: ['php-src'], source_only: true);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
SourceManager::initSource(sources: ['micro'], source_only: true);
$this->emitPatchPoint('after-micro-extract');
// judge ext
foreach ($static_extensions as $ext) {
// if extension does not support static build, throw exception
if (!in_array('static', Config::getExtTarget($ext))) {
throw new WrongUsageException('Extension [' . $ext . '] does not support static build!');
}
}
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: $extensions);
$this->emitPatchPoint('after-exts-extract');
foreach ($extensions as $extension) {
foreach ($shared_extensions as $ext) {
// if extension does not support shared build, throw exception
if (!in_array('shared', Config::getExtTarget($ext)) && !in_array($ext, $shared_extensions)) {
throw new WrongUsageException('Extension [' . $ext . '] does not support shared build!');
}
}
if (!$skip_extract) {
$this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: ['php-src'], source_only: true);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
SourceManager::initSource(sources: ['micro'], source_only: true);
$this->emitPatchPoint('after-micro-extract');
}
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract');
}
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
$class = CustomExt::getExtClass($extension);
/** @var Extension $ext */
$ext = new $class($extension, $this);
if (in_array($extension, $static_extensions)) {
$ext->setBuildStatic();
}
if (in_array($extension, $shared_extensions)) {
$ext->setBuildShared();
}
$this->addExt($ext);
}
@ -194,10 +220,10 @@ abstract class BuilderBase
return;
}
foreach ($this->exts as $ext) {
foreach ($this->getExts() as $ext) {
$ext->checkDependency();
}
$this->ext_list = $extensions;
$this->ext_list = [...$static_extensions, ...$shared_extensions];
}
/**
@ -207,6 +233,17 @@ abstract class BuilderBase
*/
abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE);
public function buildSharedExts(): void
{
foreach ($this->getExts() as $ext) {
if (!$ext->isBuildShared()) {
continue;
}
logger()->info('Building extension [' . $ext->getName() . '] as shared extension (' . $ext->getName() . '.so)');
$ext->buildShared();
}
}
/**
* Generate extension enable arguments for configure.
* e.g. --enable-mbstring
@ -214,10 +251,10 @@ abstract class BuilderBase
* @throws FileSystemException
* @throws WrongUsageException
*/
public function makeExtensionArgs(): string
public function makeStaticExtensionArgs(): string
{
$ret = [];
foreach ($this->exts as $ext) {
foreach ($this->getExts(false) as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg());
}
@ -396,7 +433,7 @@ abstract class BuilderBase
foreach ($this->libs as $lib) {
$lib->validate();
}
foreach ($this->exts as $ext) {
foreach ($this->getExts() as $ext) {
$ext->validate();
}
}
@ -441,7 +478,7 @@ abstract class BuilderBase
{
$php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n";
foreach ($this->getExts() as $ext) {
foreach ($this->getExts(false) as $ext) {
$ext_name = $ext->getDistName();
if (!empty($ext_name)) {
$php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n";

View File

@ -9,11 +9,18 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil;
class Extension
{
protected array $dependencies = [];
protected bool $build_shared = false;
protected bool $build_static = false;
protected string $source_dir;
/**
* @throws FileSystemException
* @throws RuntimeException
@ -30,6 +37,18 @@ class Extension
if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform");
}
// set source_dir for builtin
if ($ext_type === 'builtin') {
$this->source_dir = SOURCE_PATH . '/php-src/ext/' . $this->name;
} else {
$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;
}
}
/**
@ -132,7 +151,7 @@ class Extension
// Windows is not supported yet
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '';
}
@ -167,6 +186,21 @@ class Extension
return false;
}
/**
* Run shared extension check when cli is enabled
* @throws RuntimeException
*/
public function runSharedExtensionCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n -d "extension=' . BUILD_LIB_PATH . '/' . $this->getName() . '.so" --ri ' . $this->getName());
if ($ret !== 0) {
throw new RuntimeException($this->getName() . '.so failed to load');
}
if ($this->isBuildStatic()) {
logger()->warning($this->getName() . '.so test succeeded, but has little significance since it is also compiled in statically.');
}
}
/**
* @throws RuntimeException
*/
@ -231,6 +265,53 @@ class Extension
// do nothing, just throw wrong usage exception if not valid
}
/**
* Build shared extension
*
* @throws WrongUsageException
* @throws RuntimeException
*/
public function buildShared(): void
{
match (PHP_OS_FAMILY) {
'Darwin', 'Linux' => $this->buildUnixShared(),
default => throw new WrongUsageException(PHP_OS_FAMILY . ' build shared extensions is not supported yet'),
};
}
/**
* 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()]);
$env = [
'CFLAGS' => $config['cflags'],
'LDFLAGS' => $config['ldflags'],
'LIBS' => $config['libs'],
];
// prepare configure args
shell()->cd($this->source_dir)
->setEnv($env)
->execWithEnv(BUILD_BIN_PATH . '/phpize')
->execWithEnv('./configure ' . $this->getUnixConfigureArg(true) . ' --with-php-config=' . BUILD_BIN_PATH . '/php-config --enable-shared --disable-static')
->execWithEnv('make clean')
->execWithEnv('make -j' . $this->builder->concurrency);
// copy shared library
copy($this->source_dir . '/modules/' . $this->getDistName() . '.so', BUILD_LIB_PATH . '/' . $this->getDistName() . '.so');
// check shared extension with php-cli
if (file_exists(BUILD_BIN_PATH . '/php')) {
$this->runSharedExtensionCheckUnix();
}
}
/**
* Get current extension version
*
@ -241,6 +322,32 @@ class Extension
return null;
}
public function setBuildStatic(): void
{
if (!in_array('static', Config::getExtTarget($this->name))) {
throw new WrongUsageException("Extension [{$this->name}] does not support static build!");
}
$this->build_static = true;
}
public function setBuildShared(): void
{
if (!in_array('shared', Config::getExtTarget($this->name))) {
throw new WrongUsageException("Extension [{$this->name}] does not support shared build!");
}
$this->build_shared = true;
}
public function isBuildShared(): bool
{
return $this->build_shared;
}
public function isBuildStatic(): bool
{
return $this->build_static;
}
/**
* @throws RuntimeException
*/

View File

@ -23,7 +23,7 @@ class amqp extends Extension
return false;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-amqp --with-librabbitmq-dir=' . BUILD_ROOT_PATH;
}

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('dba')]
class dba extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . BUILD_ROOT_PATH) : '';
return '--enable-dba' . $qdbm;

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('enchant')]
class enchant extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$glibs = [
'/Users/jerry/project/git-project/static-php-cli/buildroot/lib/libgio-2.0.a',

View File

@ -13,7 +13,7 @@ use SPC\util\CustomExt;
#[CustomExt('event')]
class event extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--with-event-core --with-event-extra --with-event-libevent-dir=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl')) {

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('ffi')]
class ffi extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-ffi --enable-zend-signals';
}

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('gd')]
class gd extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--enable-gd';
$arg .= $this->builder->getLib('freetype') ? ' --with-freetype' : '';

View File

@ -30,7 +30,7 @@ class glfw extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH;
}

View File

@ -44,7 +44,7 @@ class grpc extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-grpc=' . BUILD_ROOT_PATH . '/grpc GRPC_LIB_SUBDIR=' . BUILD_LIB_PATH;
}

View File

@ -21,7 +21,7 @@ class imagick extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$disable_omp = getenv('SPC_LIBC') === 'musl' ? '' : ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . BUILD_ROOT_PATH . $disable_omp;

View File

@ -33,7 +33,7 @@ class imap extends Extension
}
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--with-imap=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl') !== null) {

View File

@ -12,7 +12,7 @@ use SPC\util\CustomExt;
#[CustomExt('memcache')]
class memcache extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-memcache --with-zlib-dir=' . BUILD_ROOT_PATH;
}

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('memcached')]
class memcached extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$rootdir = BUILD_ROOT_PATH;
$zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : "--with-zlib-dir={$rootdir}";

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('mongodb')]
class mongodb extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = ' --enable-mongodb ';
$arg .= ' --with-mongodb-system-libs=no --with-mongodb-client-side-encryption=no ';

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('odbc')]
class odbc extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-unixODBC=' . BUILD_ROOT_PATH;
}

View File

@ -42,7 +42,7 @@ class opcache extends Extension
return file_put_contents(SOURCE_PATH . '/php-src/.opcache_patched', '1') !== false;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-opcache';
}

View File

@ -23,7 +23,7 @@ class openssl extends Extension
return false;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$openssl_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH;
return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir;

View File

@ -17,7 +17,7 @@ class pdo_odbc extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-pdo-odbc=unixODBC,' . BUILD_ROOT_PATH;
}

View File

@ -33,7 +33,7 @@ class pgsql extends Extension
* @throws WrongUsageException
* @throws RuntimeException
*/
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
if ($this->builder->getPHPVersionID() >= 80400) {
return '--with-pgsql PGSQL_CFLAGS=-I' . BUILD_INCLUDE_PATH . ' PGSQL_LIBS="-L' . BUILD_LIB_PATH . ' -lpq -lpgport -lpgcommon"';

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('redis')]
class redis extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--enable-redis';
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';

View File

@ -26,7 +26,7 @@ class snappy extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-snappy --with-snappy-includedir="' . BUILD_ROOT_PATH . '"';
}

View File

@ -21,7 +21,7 @@ class spx extends Extension
}
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--enable-spx';
if ($this->builder->getExt('zlib') === null) {

View File

@ -35,7 +35,7 @@ class swoole extends Extension
return null;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// enable swoole
$arg = '--enable-swoole';
@ -49,7 +49,9 @@ class swoole extends Extension
// additional feature: c-ares, brotli, nghttp2 (can be disabled, but we enable it by default in config to support full network feature)
$arg .= $this->builder->getLib('libcares') ? ' --enable-cares' : '';
$arg .= $this->builder->getLib('brotli') ? (' --with-brotli-dir=' . BUILD_ROOT_PATH) : '';
if (!$shared) {
$arg .= $this->builder->getLib('brotli') ? (' --enable-brotli --with-brotli-dir=' . BUILD_ROOT_PATH) : '';
}
$arg .= $this->builder->getLib('nghttp2') ? (' --with-nghttp2-dir=' . BUILD_ROOT_PATH) : '';
// additional feature: swoole-pgsql, it should depend on lib [postgresql], but it will lack of CFLAGS etc.

View File

@ -16,7 +16,7 @@ class swoole_hook_mysql extends Extension
return 'swoole';
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// pdo_mysql doesn't need to be disabled
// enable swoole-hook-mysql will enable mysqli, pdo, pdo_mysql, we don't need to add any additional options

View File

@ -25,7 +25,7 @@ class swoole_hook_pgsql extends Extension
}
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// enable swoole pgsql hook
return '--enable-swoole-pgsql';

View File

@ -25,7 +25,7 @@ class swoole_hook_sqlite extends Extension
}
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// enable swoole pgsql hook
return '--enable-swoole-sqlite';

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\exception\RuntimeException;
use SPC\util\CustomExt;
#[CustomExt('xdebug')]
class xdebug extends Extension
{
public function runSharedExtensionCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n -d "zend_extension=' . BUILD_LIB_PATH . '/xdebug.so" --ri xdebug');
if ($ret !== 0) {
throw new RuntimeException('xdebug.so failed to load.');
}
}
}

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('xlswriter')]
class xlswriter extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--with-xlswriter --enable-reader';
if ($this->builder->getLib('openssl')) {

View File

@ -20,7 +20,7 @@ class xml extends Extension
/**
* @throws RuntimeException
*/
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = match ($this->name) {
'xml' => '--enable-xml',

View File

@ -19,7 +19,7 @@ class yac extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-yac --enable-igbinary --enable-json';
}

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zlib')]
class zlib extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-zlib-dir=' . BUILD_ROOT_PATH;
return '--with-zlib' . $zlib_dir;

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zstd')]
class zstd extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-zstd --with-libzstd="' . BUILD_ROOT_PATH . '"';
}

View File

@ -118,7 +118,7 @@ class BSDBuilder extends UnixBuilderBase
$config_file_scan_dir .
$json_74 .
$zts .
$this->makeExtensionArgs()
$this->makeStaticExtensionArgs()
);
$this->emitPatchPoint('before-php-make');

View File

@ -182,7 +182,7 @@ class LinuxBuilder extends UnixBuilderBase
$json_74 .
$zts .
$maxExecutionTimers .
$this->makeExtensionArgs() .
$this->makeStaticExtensionArgs() .
' ' . $envs_build_php . ' '
);
@ -311,15 +311,7 @@ class LinuxBuilder extends UnixBuilderBase
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
// move mimalloc to the beginning of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
// move lstdc++ to the end of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
$this->patchPhpScripts();
}
private function getMakeExtraVars(): array

View File

@ -176,7 +176,7 @@ class MacOSBuilder extends UnixBuilderBase
$config_file_scan_dir .
$json_74 .
$zts .
$this->makeExtensionArgs() . ' ' .
$this->makeStaticExtensionArgs() . ' ' .
$envs_build_php
);
@ -300,13 +300,7 @@ class MacOSBuilder extends UnixBuilderBase
->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o')
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
// move mimalloc to the beginning of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
$this->patchPhpScripts();
}
private function getMakeExtraVars(): array

View File

@ -146,7 +146,7 @@ abstract class UnixBuilderBase extends BuilderBase
throw new RuntimeException("cli failed sanity check: ret[{$ret}]. out[{$raw_output}]");
}
foreach ($this->exts as $ext) {
foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckUnix();
}
@ -238,4 +238,29 @@ abstract class UnixBuilderBase extends BuilderBase
logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean');
}
/**
* Patch phpize and php-config if needed
* @throws FileSystemException
*/
protected function patchPhpScripts(): void
{
// patch phpize
if (file_exists(BUILD_BIN_PATH . '/phpize')) {
logger()->debug('Patching phpize prefix');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
}
// patch php-config
if (file_exists(BUILD_BIN_PATH . '/php-config')) {
logger()->debug('Patching php-config prefix and libs order');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
$php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
// move mimalloc to the beginning of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
// move lstdc++ to the end of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
}
}
}

View File

@ -26,9 +26,9 @@ trait libcares
{
shell()->cd($this->source_dir)
->setEnv(['CFLAGS' => $this->getLibExtraCFlags(), 'LDFLAGS' => $this->getLibExtraLdFlags(), 'LIBS' => $this->getLibExtraLibs()])
->execWithEnv('./configure --prefix= --enable-static --disable-shared --disable-tests')
->execWithEnv('./configure --prefix= --enable-static --disable-shared --disable-tests --with-pic')
->execWithEnv("make -j {$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
->execWithEnv('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libcares.pc'], PKGCONF_PATCH_PREFIX);
}

View File

@ -119,7 +119,7 @@ class WindowsBuilder extends BuilderBase
($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
$config_file_scan_dir .
"{$this->makeExtensionArgs()} " .
"{$this->makeStaticExtensionArgs()} " .
$zts .
'"'
);
@ -286,7 +286,7 @@ class WindowsBuilder extends BuilderBase
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows();
}

View File

@ -165,7 +165,7 @@ abstract class BaseCommand extends Command
return SPC_EXTENSION_ALIAS[$lower];
}
return $lower;
}, is_array($ext_list) ? $ext_list : explode(',', $ext_list));
}, is_array($ext_list) ? $ext_list : array_filter(explode(',', $ext_list)));
// filter internals
return array_values(array_filter($ls, function ($x) {

View File

@ -27,6 +27,7 @@ class BuildPHPCommand extends BuildCommand
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('build-shared', 'D', InputOption::VALUE_REQUIRED, 'Shared extensions to build, comma separated', '');
$this->addOption('build-micro', null, null, 'Build micro SAPI');
$this->addOption('build-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)');
@ -52,13 +53,31 @@ class BuildPHPCommand extends BuildCommand
// transform string to array
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs'))));
// transform string to array
$extensions = $this->parseExtensionList($this->getArgument('extensions'));
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
// transform string to array
$static_extensions = $this->parseExtensionList($this->getArgument('extensions'));
// parse rule with options
$rule = $this->parseRules();
$rule = $this->parseRules($shared_extensions);
// check dynamic extension build env
// macOS must use --no-strip option
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Darwin' && !$this->getOption('no-strip')) {
$this->output->writeln('MacOS does not support dynamic extension loading with stripped binary, please use --no-strip option!');
return static::FAILURE;
}
// linux must build with glibc
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') {
$this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!');
return static::FAILURE;
}
$static_and_shared = array_intersect($static_extensions, $shared_extensions);
if (!empty($static_and_shared)) {
$this->output->writeln('<comment>Building extensions [' . implode(',', $static_and_shared) . '] as both static and shared\, tests may not be accurate or fail.</comment>');
}
if ($rule === BUILD_TARGET_NONE) {
$this->output->writeln('<error>Please add at least one build target!</error>');
$this->output->writeln('<error>Please add at least one build SAPI!</error>');
$this->output->writeln("<comment>\t--build-cli\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
@ -107,18 +126,26 @@ class BuildPHPCommand extends BuildCommand
$builder = BuilderProvider::makeBuilderByInput($this->input);
$include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs(array_merge($static_extensions, $shared_extensions), $libraries, $include_suggest_ext, $include_suggest_lib);
$display_libs = array_filter($libraries, fn ($lib) => in_array(Config::getLib($lib, 'type', 'lib'), ['lib', 'package']));
$display_extensions = array_map(fn ($ext) => in_array($ext, $shared_extensions) ? "*{$ext}" : $ext, $extensions);
// separate static and shared extensions from $extensions
// filter rule: including shared extensions if they are in $static_extensions or $shared_extensions
$static_extensions = array_filter($extensions, fn ($ext) => !in_array($ext, $shared_extensions) || in_array($ext, $static_extensions));
// print info
$indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(',', $extensions),
'Extensions (' . count($extensions) . ')' => implode(',', $display_extensions),
'Libraries (' . count($libraries) . ')' => implode(',', $display_libs),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no',
];
if (!empty($shared_extensions) || ($rule & BUILD_TARGET_EMBED)) {
$indent_texts['Build Dev'] = 'yes';
}
if (!empty($this->input->getOption('with-config-file-path'))) {
$indent_texts['Config File Path'] = $this->input->getOption('with-config-file-path');
}
@ -152,8 +179,8 @@ class BuildPHPCommand extends BuildCommand
// compile libraries
$builder->proveLibs($libraries);
// check extensions
$builder->proveExts($extensions);
// validate libs and exts
$builder->proveExts($static_extensions, $shared_extensions);
// validate libs and extensions
$builder->validateLibsAndExts();
// clean builds and sources
@ -183,6 +210,12 @@ class BuildPHPCommand extends BuildCommand
// start to build
$builder->buildPHP($rule);
// build dynamic extensions if needed
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
$builder->buildSharedExts();
}
// compile stopwatch :P
$time = round(microtime(true) - START_TIME, 3);
logger()->info('');
@ -211,6 +244,12 @@ class BuildPHPCommand extends BuildCommand
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}");
}
if (!empty($shared_extensions)) {
foreach ($shared_extensions as $ext) {
$path = FileSystem::convertPath("{$build_root_path}/lib/{$ext}.so");
logger()->info("Shared extension [{$ext}] path{$fixed}: {$path}");
}
}
// export metadata
file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@ -239,13 +278,13 @@ class BuildPHPCommand extends BuildCommand
/**
* Parse build options to rule int.
*/
private function parseRules(): int
private function parseRules(array $shared_extensions = []): int
{
$rule = BUILD_TARGET_NONE;
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-embed') ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
return $rule;
}

View File

@ -37,7 +37,7 @@ class SPCConfigCommand extends BuildCommand
$include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs');
$util = new SPCConfigUtil(null, $this->input);
$util = new SPCConfigUtil();
$config = $util->config($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
if ($this->getOption('includes')) {

View File

@ -6,7 +6,6 @@ namespace SPC\command\dev;
use SPC\builder\BuilderProvider;
use SPC\command\BaseCommand;
use SPC\store\Config;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -31,8 +30,7 @@ class ExtVerCommand extends BaseCommand
// Get lib object
$builder = BuilderProvider::makeBuilderByInput($this->input);
$ext_conf = Config::getExt($this->getArgument('extension'));
$builder->proveExts([$this->getArgument('extension')], true);
$builder->proveExts([$this->getArgument('extension')], [], true);
// Check whether lib is extracted
// if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) {

View File

@ -125,6 +125,21 @@ class Config
return self::$lib;
}
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public static function getExtTarget(string $name): ?array
{
if (self::$ext === null) {
self::$ext = FileSystem::loadConfigArray('ext');
}
if (!isset(self::$ext[$name])) {
throw new WrongUsageException('ext [' . $name . '] is not supported yet');
}
return self::$ext[$name]['target'] ?? ['static', 'shared'];
}
/**
* @throws FileSystemException
* @throws WrongUsageException

View File

@ -43,7 +43,7 @@ class SourcePatcher
*/
public static function patchBeforeBuildconf(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeBuildconf() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
}
@ -86,7 +86,7 @@ class SourcePatcher
*/
public static function patchBeforeConfigure(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeConfigure() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before configure');
}
@ -253,7 +253,7 @@ class SourcePatcher
// }
// call extension patch before make
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeMake() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before make');
}

View File

@ -40,9 +40,6 @@ class GlobalEnvManager
self::putenv('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('PATH'));
self::putenv('PKG_CONFIG=' . BUILD_BIN_PATH . '/pkg-config');
self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig');
if ($builder instanceof BuilderBase) {
self::putenv('SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS=' . ($builder->getOption('no-strip') ? '-g -O0' : '-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64'));
}
}
// Define env vars for linux

View File

@ -7,26 +7,51 @@ namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
class SPCConfigUtil
{
public function __construct(private ?BuilderBase $builder = null, ?InputInterface $input = null)
private ?BuilderBase $builder = null;
public function __construct(?BuilderBase $builder = null)
{
if ($builder === null) {
$this->builder = BuilderProvider::makeBuilderByInput($input ?? new ArgvInput());
if ($builder !== null) {
$this->builder = $builder; // BuilderProvider::makeBuilderByInput($input ?? new ArgvInput());
}
}
/**
* Generate configuration for building PHP extensions.
*
* @param array $extensions Extension name list
* @param array $libraries Additional library name list
* @param bool $include_suggest_ext Include suggested extensions
* @param bool $include_suggest_lib Include suggested libraries
* @return array{
* cflags: string,
* ldflags: string,
* libs: string
* }
* @throws \ReflectionException
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
* @throws \Throwable
*/
public function config(array $extensions = [], array $libraries = [], bool $include_suggest_ext = false, bool $include_suggest_lib = false): array
{
[$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
ob_start();
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions);
if ($this->builder === null) {
$this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions, skip_extract: true);
}
ob_get_clean();
$ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries);
@ -47,9 +72,9 @@ class SPCConfigUtil
$libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $libs);
}
return [
'cflags' => $cflags,
'ldflags' => $ldflags,
'libs' => $libs,
'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags),
'libs' => trim(getenv('LIBS') . ' ' . $libs),
];
}

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
// test php version (8.1 ~ 8.4 available, multiple for matrix)
$test_php_version = [
// '8.1',
// '8.2',
// '8.3',
'8.1',
'8.2',
'8.3',
'8.4',
];
@ -25,12 +25,13 @@ $test_os = [
'macos-14',
'ubuntu-latest',
'ubuntu-22.04',
'ubuntu-24.04',
'ubuntu-22.04-arm',
'ubuntu-24.04-arm',
];
// whether enable thread safe
$zts = false;
$zts = true;
$no_strip = false;
@ -42,10 +43,16 @@ $prefer_pre_built = false;
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'imagick',
'Linux', 'Darwin' => 'phar',
'Windows' => 'pgsql,pdo_pgsql',
};
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
$shared_extensions = match (PHP_OS_FAMILY) {
'Linux' => 'xdebug',
'Windows', 'Darwin' => '',
};
// If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`).
$with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '',
@ -56,7 +63,7 @@ $with_libs = match (PHP_OS_FAMILY) {
// You can use `common`, `bulk`, `minimal` or `none`.
// note: combination is only available for *nix platform. Windows must use `none` combination
$base_combination = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'minimal',
'Linux', 'Darwin' => 'common',
'Windows' => 'none',
};
@ -87,6 +94,7 @@ if (!isset($argv[1])) {
$trim_value = "\r\n \t,";
$final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value);
$download_extensions = trim($final_extensions . ',' . $shared_extensions, $trim_value);
$final_libs = trim($with_libs, $trim_value);
if (PHP_OS_FAMILY === 'Windows') {
@ -107,7 +115,7 @@ function quote2(string $param): string
// generate download command
if ($argv[1] === 'download_cmd') {
$down_cmd = 'download ';
$down_cmd .= '--for-extensions=' . quote2($final_extensions) . ' ';
$down_cmd .= '--for-extensions=' . quote2($download_extensions) . ' ';
$down_cmd .= '--for-libs=' . quote2($final_libs) . ' ';
$down_cmd .= '--with-php=' . quote2($argv[3]) . ' ';
$down_cmd .= '--ignore-cache-sources=php-src ';
@ -124,10 +132,39 @@ if ($argv[1] === 'install_upx_cmd') {
$install_upx_cmd = 'install-pkg upx';
}
$prefix = match ($argv[2] ?? null) {
'windows-latest', 'windows-2022', 'windows-2019', 'windows-2025' => 'powershell.exe -file .\bin\spc.ps1 ',
'ubuntu-latest' => 'bin/spc-alpine-docker ',
'ubuntu-24.04', 'ubuntu-24.04-arm' => './bin/spc ',
'ubuntu-22.04', 'ubuntu-22.04-arm' => 'bin/spc-gnu-docker ',
default => 'bin/spc ',
};
// shared_extension build
if ($shared_extensions) {
switch ($argv[2] ?? null) {
case 'ubuntu-22.04':
case 'ubuntu-22.04-arm':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
break;
case 'macos-13':
case 'macos-14':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
$no_strip = true;
break;
default:
$shared_cmd = '';
break;
}
} else {
$shared_cmd = '';
}
// generate build command
if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') {
$build_cmd = 'build ';
$build_cmd .= quote2($final_extensions) . ' ';
$build_cmd .= $shared_cmd;
$build_cmd .= $zts ? '--enable-zts ' : '';
$build_cmd .= $no_strip ? '--no-strip ' : '';
$build_cmd .= $upx ? '--with-upx-pack ' : '';
@ -155,31 +192,25 @@ echo match ($argv[1]) {
default => '',
};
$prefix = match ($argv[2] ?? null) {
'windows-latest', 'windows-2022', 'windows-2019', 'windows-2025' => 'powershell.exe -file .\bin\spc.ps1 ',
'ubuntu-latest', 'ubuntu-24.04', 'ubuntu-24.04-arm' => './bin/spc ',
'ubuntu-22.04', 'ubuntu-22.04-arm' => 'bin/spc-gnu-docker ',
'ubuntu-20.04' => 'bin/spc-alpine-docker ',
default => 'bin/spc ',
};
if ($argv[1] === 'download_cmd') {
passthru($prefix . $down_cmd, $retcode);
} elseif ($argv[1] === 'build_cmd') {
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
} elseif ($argv[1] === 'build_embed_cmd') {
if (str_starts_with($argv[2], 'windows-')) {
// windows does not accept embed SAPI
passthru($prefix . $build_cmd . ' --build-cli', $retcode);
} else {
passthru($prefix . $build_cmd . ' --build-embed', $retcode);
}
} elseif ($argv[1] === 'doctor_cmd') {
passthru($prefix . $doctor_cmd, $retcode);
} elseif ($argv[1] === 'install_upx_cmd') {
passthru($prefix . $install_upx_cmd, $retcode);
} else {
$retcode = 0;
switch ($argv[1] ?? null) {
case 'download_cmd':
passthru($prefix . $down_cmd, $retcode);
break;
case 'build_cmd':
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
break;
case 'build_embed_cmd':
passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode);
break;
case 'doctor_cmd':
passthru($prefix . $doctor_cmd, $retcode);
break;
case 'install_upx_cmd':
passthru($prefix . $install_upx_cmd, $retcode);
break;
default:
$retcode = 0;
break;
}
exit($retcode);

View File

@ -72,7 +72,7 @@ class BuilderTest extends TestCase
public function testMakeExtensionArgs()
{
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeExtensionArgs());
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());
}
public function testIsLibsOnly()