Merge pull request #677 from crazywhalecc/dynamic-ext-refactor

Dynamic extension build support for macOS and glibc Linux
This commit is contained in:
Marc
2025-03-30 23:23:43 +07:00
committed by GitHub
58 changed files with 350 additions and 203 deletions

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
@@ -145,4 +145,23 @@ echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"'
# shellcheck disable=SC2086 # shellcheck disable=SC2086
# shellcheck disable=SC2090 # shellcheck disable=SC2090
$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 $@
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)" --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 $@
fi

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

@@ -771,6 +771,10 @@
"Windows": "no", "Windows": "no",
"BSD": "wip" "BSD": "wip"
}, },
"target": [
"static",
"shared"
],
"notes": true, "notes": true,
"type": "external", "type": "external",
"source": "swoole", "source": "swoole",
@@ -919,15 +923,16 @@
}, },
"xdebug": { "xdebug": {
"type": "external", "type": "external",
"source": "xdebug",
"target": [
"shared"
],
"support": { "support": {
"Windows": "wip", "Windows": "wip",
"BSD": "no", "BSD": "no",
"Darwin": "no", "Darwin": "partial",
"Linux": "wip" "Linux": "partial"
}, },
"lib-depends": [
"xdebug"
],
"notes": true "notes": true
}, },
"xhprof": { "xhprof": {
@@ -1042,6 +1047,10 @@
"support": { "support": {
"BSD": "wip" "BSD": "wip"
}, },
"target": [
"static",
"shared"
],
"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": {
@@ -751,12 +750,6 @@
"libiconv" "libiconv"
] ]
}, },
"xdebug": {
"source": "xdebug",
"static-libs-unix": [
"xdebug.so"
]
},
"xz": { "xz": {
"source": "xz", "source": "xz",
"static-libs-unix": [ "static-libs-unix": [

View File

@@ -914,11 +914,9 @@
} }
}, },
"xdebug": { "xdebug": {
"type": "ghrel", "type": "url",
"repo": "xdebug/xdebug", "url": "https://pecl.php.net/get/xdebug",
"match": "Source code", "filename": "xdebug.tgz",
"prefer-stable": true,
"provide-pre-built": false,
"license": { "license": {
"type": "file", "type": "file",
"path": "LICENSE" "path": "LICENSE"

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,9 +173,22 @@ 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): void
{ {
CustomExt::loadCustomExt(); CustomExt::loadCustomExt();
// 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!');
}
}
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!');
}
}
$this->emitPatchPoint('before-php-extract'); $this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: ['php-src']); SourceManager::initSource(sources: ['php-src']);
$this->emitPatchPoint('after-php-extract'); $this->emitPatchPoint('after-php-extract');
@@ -182,11 +198,18 @@ abstract class BuilderBase
$this->emitPatchPoint('after-micro-extract'); $this->emitPatchPoint('after-micro-extract');
} }
$this->emitPatchPoint('before-exts-extract'); $this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: $extensions); SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract'); $this->emitPatchPoint('after-exts-extract');
foreach ($extensions as $extension) { 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 +217,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 +230,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 +248,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 +430,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 +475,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

@@ -14,6 +14,12 @@ 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 +36,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 +150,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 +185,17 @@ class Extension
return false; return false;
} }
/**
* Run shared extension check when cli is enabled
*/
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');
}
}
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */
@@ -231,6 +260,43 @@ 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 RuntimeException
*/
public function buildUnixShared(): void
{
// prepare configure args
shell()->cd($this->source_dir)
->setEnv(['CFLAGS' => $this->builder->arch_c_flags ?? ''])
->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 +307,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

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

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

@@ -5,7 +5,17 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\util\DynamicExt; use SPC\exception\RuntimeException;
use SPC\util\CustomExt;
#[DynamicExt] #[CustomExt('xdebug')]
class xdebug extends Extension {} 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

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\linux\library;
use SPC\util\DynamicExt;
#[DynamicExt]
class xdebug extends LinuxLibraryBase
{
use \SPC\builder\unix\library\xdebug;
public const NAME = 'xdebug';
}

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

@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
trait xdebug
{
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public function build(): void
{
shell()->cd($this->source_dir)
->exec(BUILD_BIN_PATH . '/phpize')
->exec('./configure --with-php-config=' . BUILD_BIN_PATH . '/php-config')
->exec('make clean')
->exec("make -j{$this->builder->concurrency}");
copy($this->source_dir . '/modules/xdebug.so', BUILD_LIB_PATH . '/xdebug.so');
copy($this->source_dir . '/modules/xdebug.la', BUILD_LIB_PATH . '/xdebug.la');
}
}

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

@@ -11,7 +11,6 @@ use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
use SPC\util\DynamicExt;
use SPC\util\GlobalEnvManager; use SPC\util\GlobalEnvManager;
use SPC\util\LicenseDumper; use SPC\util\LicenseDumper;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
@@ -28,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)');
@@ -53,13 +53,27 @@ 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;
}
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>");
@@ -108,33 +122,22 @@ 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']));
$dynamic_libs = $dynamic_exts = array_filter($extensions, function (string $ext) { $display_extensions = array_map(fn ($ext) => in_array($ext, $shared_extensions) ? "*{$ext}" : $ext, $extensions);
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/builder/extension', 'SPC\builder\extension');
$extension = array_find($classes, function (string $class) use ($ext) {
$a = explode('\\', $class);
return end($a) === $ext;
});
if (!$extension) {
return false;
}
$reflector = new \ReflectionClass($extension);
$attributes = $reflector->getAttributes();
return array_find($attributes, fn ($attr) => $attr->getName() === DynamicExt::class) !== null;
});
$extensions = array_diff($extensions, $dynamic_exts);
$libraries = array_diff($libraries, $dynamic_libs);
// 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');
} }
@@ -168,7 +171,7 @@ 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 extensions // validate libs and extensions
$builder->validateLibsAndExts(); $builder->validateLibsAndExts();
@@ -199,11 +202,10 @@ class BuildPHPCommand extends BuildCommand
// start to build // start to build
$builder->buildPHP($rule); $builder->buildPHP($rule);
if ($rule & BUILD_TARGET_EMBED) { // build dynamic extensions if needed
// build dynamic extensions if (!empty($shared_extensions)) {
$builder->proveLibs($dynamic_libs); logger()->info('Building shared extensions ...');
// build or install libraries $builder->buildSharedExts();
$builder->setupLibs();
} }
// compile stopwatch :P // compile stopwatch :P
@@ -234,13 +236,19 @@ 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));
file_put_contents(BUILD_ROOT_PATH . '/build-libraries.json', json_encode($libraries, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); file_put_contents(BUILD_ROOT_PATH . '/build-libraries.json', json_encode($libraries, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
// export licenses // export licenses
$dumper = new LicenseDumper(); $dumper = new LicenseDumper();
$dumper->addExts($extensions)->addLibs($libraries)->addLibs($dynamic_libs)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license'); $dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
$path = FileSystem::convertPath("{$build_root_path}/license/"); $path = FileSystem::convertPath("{$build_root_path}/license/");
logger()->info("License path{$fixed}: {$path}"); logger()->info("License path{$fixed}: {$path}");
return static::SUCCESS; return static::SUCCESS;
@@ -262,13 +270,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 $dynamic_exts = []): 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($dynamic_exts) ? 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

@@ -106,6 +106,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'];
}
/** /**
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException

View File

@@ -127,10 +127,6 @@ class Downloader
if (!$match_result) { if (!$match_result) {
return $release['assets']; return $release['assets'];
} }
if ($source['match'] === 'Source code') {
$url = $release['tarball_url'];
break;
}
foreach ($release['assets'] as $asset) { foreach ($release['assets'] as $asset) {
if (preg_match('|' . $source['match'] . '|', $asset['name'])) { if (preg_match('|' . $source['match'] . '|', $asset['name'])) {
$url = $asset['browser_download_url']; $url = $asset['browser_download_url'];
@@ -143,9 +139,6 @@ class Downloader
throw new DownloaderException("failed to find {$name} release metadata"); throw new DownloaderException("failed to find {$name} release metadata");
} }
$filename = basename($url); $filename = basename($url);
if ($source['match'] === 'Source code') {
$filename = $name . $filename . '.tar.gz';
}
return [$url, $filename]; return [$url, $filename];
} }

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

@@ -1,8 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\util;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class DynamicExt {}

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

@@ -9,14 +9,15 @@ use SPC\builder\BuilderProvider;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
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());
} }
} }
@@ -25,8 +26,11 @@ class SPCConfigUtil
[$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);
}
ob_get_clean(); ob_get_clean();
$ldflags = $this->getLdflagsString(); $ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries); $libs = $this->getLibsString($libraries);
@@ -47,9 +51,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

@@ -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()