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 }} run: php src/globals/test-extensions.php build_cmd ${{ matrix.os }} ${{ matrix.php }}
- name: "Run Build Tests (build - embed for non-windows)" - 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 }} 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=SC2086
# shellcheck disable=SC2090 # shellcheck disable=SC2090
if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then 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 $DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2
else else
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2 bin/spc $@ $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 # shellcheck disable=SC2046
if [ $(id -u) -ne 0 ]; then if [ $(id -u) -ne 0 ]; then
if ! docker info > /dev/null 2>&1; 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" echo "Docker command requires sudo"
# shellcheck disable=SC2039 # shellcheck disable=SC2039
echo -n 'To use sudo to run docker, run "export SPC_USE_SUDO=yes" and run command again' 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/setup-runtime /app/bin/setup-runtime
ADD ./bin/spc /app/bin/spc ADD ./bin/spc /app/bin/spc
RUN /app/bin/setup-runtime 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 PATH="/app/bin:/cmake/bin:$PATH"
ENV SPC_LIBC=glibc 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 # shellcheck disable=SC2090
if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then 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 $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 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 $@ $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. ; 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: 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. ; 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: ; * 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`) ; 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 ; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm" SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
; EXTRA_CFLAGS for `make` php ; 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 ; 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 ; EXTRA_LDFLAGS_PROGRAM for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie" 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 ; LDFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}" SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; EXTRA_CFLAGS for `make` php ; 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 ; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv" SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv"
; embed type for php, static (libphp.a) or shared (libphp.dylib) ; embed type for php, static (libphp.a) or shared (libphp.dylib)

View File

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

View File

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

View File

@ -913,6 +913,15 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"xdebug": {
"type": "url",
"url": "https://pecl.php.net/get/xdebug",
"filename": "xdebug.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"xhprof": { "xhprof": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/xhprof", "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 ## 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`. 1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc.
2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform, 2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug".
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`.
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 ## xml
@ -126,8 +125,8 @@ For details on the solution, see [FAQ - Unable to use ssl](../faq/#unable-to-use
## ffi ## 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). 1. Due to the limitation of musl libc's static linkage, you cannot use ffi because dynamic libraries cannot be loaded.
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). 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. 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. 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-exts`: Add `ext-suggests` as dependencies when compiling
- `--with-suggested-libs`: Add `lib-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) - `--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: 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 ## xdebug
1. Xdebug 只能作为共享扩展构建。在 Linux 上,您需要使用带有 `SPC_LIBC=glibc` 的 static-php-cli然后使用选项 `--with-php-config=/path/to/buildroot/bin/php-config` 从源代码编译 php-xdebug 1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc
2. macOS 平台可以通过在相同平台编译的 PHP 下编译一个 xdebug 扩展,并提取其中的 `xdebug.so` 文件,再在 static-php-cli 中使用 `--no-strip` 参数保留调试符号表,同时加入 `ffi` 扩展。 2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译`./php` 二进制可以通过指定 INI 配置并运行,例如`./php -d 'zend_extension=xdebug.so' your-code.php`。 编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`。
## xml ## xml
@ -117,9 +117,10 @@ pgsql 16.2 修复了这个 Bug现在正常工作了。
## ffi ## ffi
1. 因为 Linux 系统的限制纯静态编译的状态下spc 默认编译结果为纯静态)无法使用它加载其他 `so` 扩展。Linux 支持加载 so 扩展的前提是非静态编译。如果你需要使用 ffi 扩展,请参见 [编译 GNU libc 的 PHP](./build-with-glibc)。 1. 由于 musl libc 静态链接的限制,无法加载动态库,因此无法使用 ffi。
2. macOS 支持 ffi 扩展,但是部分内核下不包含调试符号时会出现错误。 如果您需要使用 ffi 扩展,请参阅 [使用 GNU libc 编译 PHP](./build-with-glibc)。
3. Windows 支持 ffi 扩展。 2. macOS 支持 ffi 扩展,但某些内核不包含调试符号时会出现错误。
3. Windows x64 支持 ffi 扩展。
## xhprof ## xhprof

View File

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

View File

@ -122,9 +122,12 @@ abstract class BuilderBase
* *
* @return Extension[] * @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 public function hasCpp(): bool
{ {
// judge cpp-extension // judge cpp-extension
$exts = array_keys($this->getExts()); $exts = array_keys($this->getExts(false));
foreach ($exts as $ext) { foreach ($exts as $ext) {
if (Config::getExt($ext, 'cpp-extension', false) === true) { if (Config::getExt($ext, 'cpp-extension', false) === true) {
return true; return true;
@ -170,23 +173,46 @@ abstract class BuilderBase
* @throws \Throwable|WrongUsageException * @throws \Throwable|WrongUsageException
* @internal * @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(); CustomExt::loadCustomExt();
$this->emitPatchPoint('before-php-extract'); // judge ext
SourceManager::initSource(sources: ['php-src'], source_only: true); foreach ($static_extensions as $ext) {
$this->emitPatchPoint('after-php-extract'); // if extension does not support static build, throw exception
if ($this->getPHPVersionID() >= 80000) { if (!in_array('static', Config::getExtTarget($ext))) {
$this->emitPatchPoint('before-micro-extract'); throw new WrongUsageException('Extension [' . $ext . '] does not support static build!');
SourceManager::initSource(sources: ['micro'], source_only: true); }
$this->emitPatchPoint('after-micro-extract');
} }
$this->emitPatchPoint('before-exts-extract'); foreach ($shared_extensions as $ext) {
SourceManager::initSource(exts: $extensions); // if extension does not support shared build, throw exception
$this->emitPatchPoint('after-exts-extract'); if (!in_array('shared', Config::getExtTarget($ext)) && !in_array($ext, $shared_extensions)) {
foreach ($extensions as $extension) { 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); $class = CustomExt::getExtClass($extension);
/** @var Extension $ext */
$ext = new $class($extension, $this); $ext = new $class($extension, $this);
if (in_array($extension, $static_extensions)) {
$ext->setBuildStatic();
}
if (in_array($extension, $shared_extensions)) {
$ext->setBuildShared();
}
$this->addExt($ext); $this->addExt($ext);
} }
@ -194,10 +220,10 @@ abstract class BuilderBase
return; return;
} }
foreach ($this->exts as $ext) { foreach ($this->getExts() as $ext) {
$ext->checkDependency(); $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); 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. * Generate extension enable arguments for configure.
* e.g. --enable-mbstring * e.g. --enable-mbstring
@ -214,10 +251,10 @@ abstract class BuilderBase
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public function makeExtensionArgs(): string public function makeStaticExtensionArgs(): string
{ {
$ret = []; $ret = [];
foreach ($this->exts as $ext) { foreach ($this->getExts(false) as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg()); logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg()); $ret[] = trim($ext->getConfigureArg());
} }
@ -396,7 +433,7 @@ abstract class BuilderBase
foreach ($this->libs as $lib) { foreach ($this->libs as $lib) {
$lib->validate(); $lib->validate();
} }
foreach ($this->exts as $ext) { foreach ($this->getExts() as $ext) {
$ext->validate(); $ext->validate();
} }
} }
@ -441,7 +478,7 @@ abstract class BuilderBase
{ {
$php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n"; $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(); $ext_name = $ext->getDistName();
if (!empty($ext_name)) { if (!empty($ext_name)) {
$php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n"; $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\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil;
class Extension class Extension
{ {
protected array $dependencies = []; protected array $dependencies = [];
protected bool $build_shared = false;
protected bool $build_static = false;
protected string $source_dir;
/** /**
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
@ -30,6 +37,18 @@ class Extension
if (PHP_OS_FAMILY === 'Windows' && $unix_only) { if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform"); 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 // Windows is not supported yet
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return ''; return '';
} }
@ -167,6 +186,21 @@ class Extension
return false; 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 * @throws RuntimeException
*/ */
@ -231,6 +265,53 @@ class Extension
// do nothing, just throw wrong usage exception if not valid // 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 * Get current extension version
* *
@ -241,6 +322,32 @@ class Extension
return null; 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 * @throws RuntimeException
*/ */

View File

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

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('dba')] #[CustomExt('dba')]
class dba extends Extension 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) : ''; $qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . BUILD_ROOT_PATH) : '';
return '--enable-dba' . $qdbm; return '--enable-dba' . $qdbm;

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('enchant')] #[CustomExt('enchant')]
class enchant extends Extension class enchant extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$glibs = [ $glibs = [
'/Users/jerry/project/git-project/static-php-cli/buildroot/lib/libgio-2.0.a', '/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')] #[CustomExt('event')]
class event extends Extension 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; $arg = '--with-event-core --with-event-extra --with-event-libevent-dir=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl')) { if ($this->builder->getLib('openssl')) {

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ class grpc extends Extension
return true; 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; 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; 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'; $disable_omp = getenv('SPC_LIBC') === 'musl' ? '' : ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . BUILD_ROOT_PATH . $disable_omp; 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; $arg = '--with-imap=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl') !== null) { if ($this->builder->getLib('openssl') !== null) {

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ class openssl extends Extension
return false; 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; $openssl_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH;
return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir; return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir;

View File

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

View File

@ -33,7 +33,7 @@ class pgsql extends Extension
* @throws WrongUsageException * @throws WrongUsageException
* @throws RuntimeException * @throws RuntimeException
*/ */
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
if ($this->builder->getPHPVersionID() >= 80400) { if ($this->builder->getPHPVersionID() >= 80400) {
return '--with-pgsql PGSQL_CFLAGS=-I' . BUILD_INCLUDE_PATH . ' PGSQL_LIBS="-L' . BUILD_LIB_PATH . ' -lpq -lpgport -lpgcommon"'; 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')] #[CustomExt('redis')]
class redis extends Extension class redis extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--enable-redis'; $arg = '--enable-redis';
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session'; $arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';

View File

@ -26,7 +26,7 @@ class snappy extends Extension
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-snappy --with-snappy-includedir="' . BUILD_ROOT_PATH . '"'; 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'; $arg = '--enable-spx';
if ($this->builder->getExt('zlib') === null) { if ($this->builder->getExt('zlib') === null) {

View File

@ -35,7 +35,7 @@ class swoole extends Extension
return null; return null;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// enable swoole // enable swoole
$arg = '--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) // 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('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) : ''; $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. // 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'; return 'swoole';
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// pdo_mysql doesn't need to be disabled // 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 // 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 // enable swoole pgsql hook
return '--enable-swoole-pgsql'; 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 // enable swoole pgsql hook
return '--enable-swoole-sqlite'; 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')] #[CustomExt('xlswriter')]
class xlswriter extends Extension class xlswriter extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--with-xlswriter --enable-reader'; $arg = '--with-xlswriter --enable-reader';
if ($this->builder->getLib('openssl')) { if ($this->builder->getLib('openssl')) {

View File

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

View File

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

View File

@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zlib')] #[CustomExt('zlib')]
class zlib extends Extension 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; $zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-zlib-dir=' . BUILD_ROOT_PATH;
return '--with-zlib' . $zlib_dir; return '--with-zlib' . $zlib_dir;

View File

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

View File

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

View File

@ -182,7 +182,7 @@ class LinuxBuilder extends UnixBuilderBase
$json_74 . $json_74 .
$zts . $zts .
$maxExecutionTimers . $maxExecutionTimers .
$this->makeExtensionArgs() . $this->makeStaticExtensionArgs() .
' ' . $envs_build_php . ' ' ' ' . $envs_build_php . ' '
); );
@ -311,15 +311,7 @@ class LinuxBuilder extends UnixBuilderBase
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install"); ->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 . "'"); $this->patchPhpScripts();
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);
} }
private function getMakeExtraVars(): array private function getMakeExtraVars(): array

View File

@ -176,7 +176,7 @@ class MacOSBuilder extends UnixBuilderBase
$config_file_scan_dir . $config_file_scan_dir .
$json_74 . $json_74 .
$zts . $zts .
$this->makeExtensionArgs() . ' ' . $this->makeStaticExtensionArgs() . ' ' .
$envs_build_php $envs_build_php
); );
@ -300,13 +300,7 @@ class MacOSBuilder extends UnixBuilderBase
->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a') ->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o') ->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o')
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o'); ->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'"); $this->patchPhpScripts();
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);
} }
private function getMakeExtraVars(): array 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}]"); 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()); logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckUnix(); $ext->runCliCheckUnix();
} }
@ -238,4 +238,29 @@ abstract class UnixBuilderBase extends BuilderBase
logger()->info('cleaning up'); logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean'); 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) shell()->cd($this->source_dir)
->setEnv(['CFLAGS' => $this->getLibExtraCFlags(), 'LDFLAGS' => $this->getLibExtraLdFlags(), 'LIBS' => $this->getLibExtraLibs()]) ->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}") ->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); $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 ') . ($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') . ($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
$config_file_scan_dir . $config_file_scan_dir .
"{$this->makeExtensionArgs()} " . "{$this->makeStaticExtensionArgs()} " .
$zts . $zts .
'"' '"'
); );
@ -286,7 +286,7 @@ class WindowsBuilder extends BuilderBase
throw new RuntimeException('cli failed sanity check'); throw new RuntimeException('cli failed sanity check');
} }
foreach ($this->exts as $ext) { foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName()); logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows(); $ext->runCliCheckWindows();
} }

View File

@ -165,7 +165,7 @@ abstract class BaseCommand extends Command
return SPC_EXTENSION_ALIAS[$lower]; return SPC_EXTENSION_ALIAS[$lower];
} }
return $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 // filter internals
return array_values(array_filter($ls, function ($x) { 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->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('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-micro', null, null, 'Build micro SAPI');
$this->addOption('build-cli', null, null, 'Build cli SAPI'); $this->addOption('build-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)'); $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 // transform string to array
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs')))); $libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs'))));
// transform string to array // 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 // 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) { 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-cli\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro 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>"); $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); $builder = BuilderProvider::makeBuilderByInput($this->input);
$include_suggest_ext = $this->getOption('with-suggested-exts'); $include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs'); $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_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 // print info
$indent_texts = [ $indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')', 'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build SAPI' => $builder->getBuildTypeName($rule), 'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(',', $extensions), 'Extensions (' . count($extensions) . ')' => implode(',', $display_extensions),
'Libraries (' . count($libraries) . ')' => implode(',', $display_libs), 'Libraries (' . count($libraries) . ')' => implode(',', $display_libs),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes', 'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no', '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'))) { if (!empty($this->input->getOption('with-config-file-path'))) {
$indent_texts['Config File Path'] = $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 // compile libraries
$builder->proveLibs($libraries); $builder->proveLibs($libraries);
// check extensions // check extensions
$builder->proveExts($extensions); $builder->proveExts($static_extensions, $shared_extensions);
// validate libs and exts // validate libs and extensions
$builder->validateLibsAndExts(); $builder->validateLibsAndExts();
// clean builds and sources // clean builds and sources
@ -183,6 +210,12 @@ class BuildPHPCommand extends BuildCommand
// start to build // start to build
$builder->buildPHP($rule); $builder->buildPHP($rule);
// build dynamic extensions if needed
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
$builder->buildSharedExts();
}
// compile stopwatch :P // compile stopwatch :P
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info(''); logger()->info('');
@ -211,6 +244,12 @@ class BuildPHPCommand extends BuildCommand
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm"); $path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}"); 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 // export metadata
file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 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. * Parse build options to rule int.
*/ */
private function parseRules(): int private function parseRules(array $shared_extensions = []): int
{ {
$rule = BUILD_TARGET_NONE; $rule = BUILD_TARGET_NONE;
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : 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-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : 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); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
return $rule; return $rule;
} }

View File

@ -37,7 +37,7 @@ class SPCConfigCommand extends BuildCommand
$include_suggest_ext = $this->getOption('with-suggested-exts'); $include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs'); $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); $config = $util->config($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
if ($this->getOption('includes')) { if ($this->getOption('includes')) {

View File

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

View File

@ -125,6 +125,21 @@ class Config
return self::$lib; 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 FileSystemException
* @throws WrongUsageException * @throws WrongUsageException

View File

@ -43,7 +43,7 @@ class SourcePatcher
*/ */
public static function patchBeforeBuildconf(BuilderBase $builder): void public static function patchBeforeBuildconf(BuilderBase $builder): void
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeBuildconf() === true) { if ($ext->patchBeforeBuildconf() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf'); logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
} }
@ -86,7 +86,7 @@ class SourcePatcher
*/ */
public static function patchBeforeConfigure(BuilderBase $builder): void public static function patchBeforeConfigure(BuilderBase $builder): void
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeConfigure() === true) { if ($ext->patchBeforeConfigure() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before configure'); logger()->info('Extension [' . $ext->getName() . '] patched before configure');
} }
@ -253,7 +253,7 @@ class SourcePatcher
// } // }
// call extension patch before make // call extension patch before make
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeMake() === true) { if ($ext->patchBeforeMake() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before make'); 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('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('PATH'));
self::putenv('PKG_CONFIG=' . BUILD_BIN_PATH . '/pkg-config'); self::putenv('PKG_CONFIG=' . BUILD_BIN_PATH . '/pkg-config');
self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig'); 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 // Define env vars for linux

View File

@ -7,26 +7,51 @@ namespace SPC\util;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
class SPCConfigUtil 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) { if ($builder !== null) {
$this->builder = BuilderProvider::makeBuilderByInput($input ?? new ArgvInput()); $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 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); [$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
ob_start(); ob_start();
$this->builder->proveLibs($libraries); if ($this->builder === null) {
$this->builder->proveExts($extensions); $this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions, skip_extract: true);
}
ob_get_clean(); ob_get_clean();
$ldflags = $this->getLdflagsString(); $ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries); $libs = $this->getLibsString($libraries);
@ -47,9 +72,9 @@ class SPCConfigUtil
$libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $libs); $libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $libs);
} }
return [ return [
'cflags' => $cflags, 'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => $ldflags, 'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags),
'libs' => $libs, '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.4 available, multiple for matrix)
$test_php_version = [ $test_php_version = [
// '8.1', '8.1',
// '8.2', '8.2',
// '8.3', '8.3',
'8.4', '8.4',
]; ];
@ -25,12 +25,13 @@ $test_os = [
'macos-14', 'macos-14',
'ubuntu-latest', 'ubuntu-latest',
'ubuntu-22.04', 'ubuntu-22.04',
'ubuntu-24.04',
'ubuntu-22.04-arm', 'ubuntu-22.04-arm',
'ubuntu-24.04-arm', 'ubuntu-24.04-arm',
]; ];
// whether enable thread safe // whether enable thread safe
$zts = false; $zts = true;
$no_strip = false; $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`). // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) { $extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'imagick', 'Linux', 'Darwin' => 'phar',
'Windows' => 'pgsql,pdo_pgsql', '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`). // If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`).
$with_libs = match (PHP_OS_FAMILY) { $with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '', 'Linux', 'Darwin' => '',
@ -56,7 +63,7 @@ $with_libs = match (PHP_OS_FAMILY) {
// You can use `common`, `bulk`, `minimal` or `none`. // You can use `common`, `bulk`, `minimal` or `none`.
// note: combination is only available for *nix platform. Windows must use `none` combination // note: combination is only available for *nix platform. Windows must use `none` combination
$base_combination = match (PHP_OS_FAMILY) { $base_combination = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'minimal', 'Linux', 'Darwin' => 'common',
'Windows' => 'none', 'Windows' => 'none',
}; };
@ -87,6 +94,7 @@ if (!isset($argv[1])) {
$trim_value = "\r\n \t,"; $trim_value = "\r\n \t,";
$final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value); $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); $final_libs = trim($with_libs, $trim_value);
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
@ -107,7 +115,7 @@ function quote2(string $param): string
// generate download command // generate download command
if ($argv[1] === 'download_cmd') { if ($argv[1] === 'download_cmd') {
$down_cmd = 'download '; $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 .= '--for-libs=' . quote2($final_libs) . ' ';
$down_cmd .= '--with-php=' . quote2($argv[3]) . ' '; $down_cmd .= '--with-php=' . quote2($argv[3]) . ' ';
$down_cmd .= '--ignore-cache-sources=php-src '; $down_cmd .= '--ignore-cache-sources=php-src ';
@ -124,10 +132,39 @@ if ($argv[1] === 'install_upx_cmd') {
$install_upx_cmd = 'install-pkg upx'; $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 // generate build command
if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') { if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') {
$build_cmd = 'build '; $build_cmd = 'build ';
$build_cmd .= quote2($final_extensions) . ' '; $build_cmd .= quote2($final_extensions) . ' ';
$build_cmd .= $shared_cmd;
$build_cmd .= $zts ? '--enable-zts ' : ''; $build_cmd .= $zts ? '--enable-zts ' : '';
$build_cmd .= $no_strip ? '--no-strip ' : ''; $build_cmd .= $no_strip ? '--no-strip ' : '';
$build_cmd .= $upx ? '--with-upx-pack ' : ''; $build_cmd .= $upx ? '--with-upx-pack ' : '';
@ -155,31 +192,25 @@ echo match ($argv[1]) {
default => '', default => '',
}; };
$prefix = match ($argv[2] ?? null) { switch ($argv[1] ?? null) {
'windows-latest', 'windows-2022', 'windows-2019', 'windows-2025' => 'powershell.exe -file .\bin\spc.ps1 ', case 'download_cmd':
'ubuntu-latest', 'ubuntu-24.04', 'ubuntu-24.04-arm' => './bin/spc ', passthru($prefix . $down_cmd, $retcode);
'ubuntu-22.04', 'ubuntu-22.04-arm' => 'bin/spc-gnu-docker ', break;
'ubuntu-20.04' => 'bin/spc-alpine-docker ', case 'build_cmd':
default => 'bin/spc ', passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
}; break;
case 'build_embed_cmd':
if ($argv[1] === 'download_cmd') { passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode);
passthru($prefix . $down_cmd, $retcode); break;
} elseif ($argv[1] === 'build_cmd') { case 'doctor_cmd':
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode); passthru($prefix . $doctor_cmd, $retcode);
} elseif ($argv[1] === 'build_embed_cmd') { break;
if (str_starts_with($argv[2], 'windows-')) { case 'install_upx_cmd':
// windows does not accept embed SAPI passthru($prefix . $install_upx_cmd, $retcode);
passthru($prefix . $build_cmd . ' --build-cli', $retcode); break;
} else { default:
passthru($prefix . $build_cmd . ' --build-embed', $retcode); $retcode = 0;
} break;
} 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;
} }
exit($retcode); exit($retcode);

View File

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