Use cmake to build librdkafka, add helper function for SPCConfigUtil (#955)

This commit is contained in:
Marc 2025-11-04 14:58:58 +01:00 committed by GitHub
commit 7402fbf7c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 136 additions and 90 deletions

View File

@ -506,13 +506,16 @@
}, },
"librdkafka": { "librdkafka": {
"source": "librdkafka", "source": "librdkafka",
"static-libs-unix": [ "pkg-configs": [
"librdkafka.a", "rdkafka++-static",
"librdkafka++.a", "rdkafka-static"
"librdkafka-static.a"
], ],
"cpp-library": true, "cpp-library": true,
"lib-suggests": [ "lib-suggests": [
"curl",
"liblz4",
"openssl",
"zlib",
"zstd" "zstd"
] ]
}, },

View File

@ -220,7 +220,7 @@ class Extension
*/ */
public function patchBeforeSharedMake(): bool public function patchBeforeSharedMake(): bool
{ {
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], array_map(fn ($l) => $l->getName(), $this->builder->getLibs())); $config = (new SPCConfigUtil($this->builder))->getExtensionConfig($this);
[$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
$lstdcpp = str_contains($sharedLibs, '-l:libstdc++.a') ? '-l:libstdc++.a' : null; $lstdcpp = str_contains($sharedLibs, '-l:libstdc++.a') ? '-l:libstdc++.a' : null;
$lstdcpp ??= str_contains($sharedLibs, '-lstdc++') ? '-lstdc++' : ''; $lstdcpp ??= str_contains($sharedLibs, '-lstdc++') ? '-lstdc++' : '';
@ -486,18 +486,46 @@ class Extension
return $this->build_static; return $this->build_static;
} }
/**
* Get the library dependencies that current extension depends on.
*
* @param bool $recursive Whether it includes dependencies recursively
*/
public function getLibraryDependencies(bool $recursive = false): array
{
$ret = array_filter($this->dependencies, fn ($x) => $x instanceof LibraryBase);
if (!$recursive) {
return $ret;
}
$deps = [];
$added = 1;
while ($added !== 0) {
$added = 0;
foreach ($ret as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
if (!array_key_exists($depdepName, $deps)) {
$deps[$depdepName] = $depdep;
++$added;
}
}
if (!array_key_exists($depName, $deps)) {
$deps[$depName] = $dep;
}
}
}
return $deps;
}
/** /**
* Returns the environment variables a shared extension needs to be built. * Returns the environment variables a shared extension needs to be built.
* CFLAGS, CXXFLAGS, LDFLAGS and so on. * CFLAGS, CXXFLAGS, LDFLAGS and so on.
*/ */
protected function getSharedExtensionEnv(): array protected function getSharedExtensionEnv(): array
{ {
$config = (new SPCConfigUtil($this->builder))->config( $config = (new SPCConfigUtil($this->builder))->getExtensionConfig($this);
[$this->getName()],
array_map(fn ($l) => $l->getName(), $this->getLibraryDependencies(recursive: true)),
$this->builder->getOption('with-suggested-exts'),
$this->builder->getOption('with-suggested-libs'),
);
[$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
$preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group '; $preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group ';
$postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group '; $postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group ';
@ -519,7 +547,7 @@ class Extension
} }
logger()->info("enabling {$this->name} without library {$name}"); logger()->info("enabling {$this->name} without library {$name}");
} else { } else {
$this->dependencies[] = $depLib; $this->dependencies[$name] = $depLib;
} }
} }
@ -532,7 +560,7 @@ class Extension
} }
logger()->info("enabling {$this->name} without extension {$name}"); logger()->info("enabling {$this->name} without extension {$name}");
} else { } else {
$this->dependencies[] = $depExt; $this->dependencies[$name] = $depExt;
} }
} }
@ -567,37 +595,4 @@ class Extension
} }
return [trim($staticLibString), trim($sharedLibString)]; return [trim($staticLibString), trim($sharedLibString)];
} }
private function getLibraryDependencies(bool $recursive = false): array
{
$ret = array_filter($this->dependencies, fn ($x) => $x instanceof LibraryBase);
if (!$recursive) {
return $ret;
}
$deps = [];
$added = 1;
while ($added !== 0) {
$added = 0;
foreach ($ret as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
if (!array_key_exists($depdepName, $deps)) {
$deps[$depdepName] = $depdep;
++$added;
}
}
if (!array_key_exists($depName, $deps)) {
$deps[$depName] = $dep;
}
}
}
if (array_key_exists(0, $deps)) {
$zero = [0 => $deps[0]];
unset($deps[0]);
return $zero + $deps;
}
return $deps;
}
} }

View File

@ -43,7 +43,7 @@ class grpc extends Extension
public function patchBeforeConfigure(): bool public function patchBeforeConfigure(): bool
{ {
$util = new SPCConfigUtil($this->builder, ['libs_only_deps' => true]); $util = new SPCConfigUtil($this->builder, ['libs_only_deps' => true]);
$config = $util->config(['grpc']); $config = $util->getExtensionConfig($this);
$libs = $config['libs']; $libs = $config['libs'];
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/configure', '-lgrpc', $libs); FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/configure', '-lgrpc', $libs);
return true; return true;

View File

@ -7,6 +7,7 @@ namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
use SPC\util\SPCConfigUtil;
#[CustomExt('rdkafka')] #[CustomExt('rdkafka')]
class rdkafka extends Extension class rdkafka extends Extension
@ -15,6 +16,7 @@ class rdkafka extends Extension
{ {
FileSystem::replaceFileStr("{$this->source_dir}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\n", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm \$RDKAFKA_LIBS\n"); FileSystem::replaceFileStr("{$this->source_dir}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\n", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm \$RDKAFKA_LIBS\n");
FileSystem::replaceFileStr("{$this->source_dir}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\"\n", '-L$RDKAFKA_DIR/$PHP_LIBDIR -lm $RDKAFKA_LIBS"'); FileSystem::replaceFileStr("{$this->source_dir}/config.m4", "-L\$RDKAFKA_DIR/\$PHP_LIBDIR -lm\"\n", '-L$RDKAFKA_DIR/$PHP_LIBDIR -lm $RDKAFKA_LIBS"');
FileSystem::replaceFileStr("{$this->source_dir}/config.m4", 'PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,', 'AC_CHECK_LIB([$LIBNAME], [$LIBSYMBOL],');
return true; return true;
} }
@ -37,8 +39,7 @@ class rdkafka extends Extension
public function getUnixConfigureArg(bool $shared = false): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$pkgconf_libs = shell()->execWithResult('pkg-config --libs --static rdkafka')[1]; $pkgconf_libs = (new SPCConfigUtil($this->builder, ['no_php' => true, 'libs_only_deps' => true]))->getExtensionConfig($this);
$pkgconf_libs = trim(implode('', $pkgconf_libs)); return '--with-rdkafka=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . " RDKAFKA_LIBS=\"{$pkgconf_libs['libs']}\"";
return '--with-rdkafka=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . ' RDKAFKA_LIBS="' . $pkgconf_libs . '"';
} }
} }

View File

@ -70,7 +70,7 @@ class swoole extends Extension
$arg .= $this->builder->getExt('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd'; $arg .= $this->builder->getExt('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd';
$arg .= $this->builder->getExt('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite'; $arg .= $this->builder->getExt('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite';
if ($this->builder->getExt('swoole-hook-odbc')) { if ($this->builder->getExt('swoole-hook-odbc')) {
$config = (new SPCConfigUtil($this->builder, ['libs_only_deps' => true]))->config([], ['unixodbc']); $config = (new SPCConfigUtil($this->builder))->getLibraryConfig($this->builder->getLib('unixodbc'));
$arg .= ' --with-swoole-odbc=unixODBC,' . BUILD_ROOT_PATH . ' SWOOLE_ODBC_LIBS="' . $config['libs'] . '"'; $arg .= ' --with-swoole-odbc=unixODBC,' . BUILD_ROOT_PATH . ' SWOOLE_ODBC_LIBS="' . $config['libs'] . '"';
} }

View File

@ -301,7 +301,7 @@ abstract class UnixBuilderBase extends BuilderBase
} }
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list); $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list);
$cflags = "{$this->arch_c_flags} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'); $cflags = "{$this->arch_c_flags} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -DFRANKENPHP_VERSION=' . $frankenPhpVersion;
$libs = $config['libs']; $libs = $config['libs'];
// Go's gcc driver doesn't automatically link against -lgcov or -lrt. Ugly, but necessary fix. // Go's gcc driver doesn't automatically link against -lgcov or -lrt. Ugly, but necessary fix.
if ((str_contains((string) getenv('SPC_DEFAULT_C_FLAGS'), '-fprofile') || if ((str_contains((string) getenv('SPC_DEFAULT_C_FLAGS'), '-fprofile') ||

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\unix\library; namespace SPC\builder\unix\library;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor; use SPC\util\executor\UnixCMakeExecutor;
trait librdkafka trait librdkafka
{ {
@ -26,34 +26,18 @@ trait librdkafka
protected function build(): void protected function build(): void
{ {
UnixAutoconfExecutor::create($this) UnixCMakeExecutor::create($this)
->appendEnv(['CFLAGS' => '-Wno-int-conversion -Wno-unused-but-set-variable -Wno-unused-variable']) ->optionalLib('zstd', ...cmake_boolean_args('WITH_ZSTD'))
->optionalLib( ->optionalLib('curl', ...cmake_boolean_args('WITH_CURL'))
'zstd', ->optionalLib('openssl', ...cmake_boolean_args('WITH_SSL'))
function ($lib) { ->optionalLib('zlib', ...cmake_boolean_args('WITH_ZLIB'))
putenv("STATIC_LIB_libzstd={$lib->getLibDir()}/libzstd.a"); ->optionalLib('liblz4', ...cmake_boolean_args('ENABLE_LZ4_EXT'))
return ''; ->addConfigureArgs(
}, '-DWITH_SASL=OFF',
'--disable-zstd' '-DRDKAFKA_BUILD_STATIC=ON',
'-DRDKAFKA_BUILD_EXAMPLES=OFF',
'-DRDKAFKA_BUILD_TESTS=OFF',
) )
->removeConfigureArgs( ->build();
'--with-pic',
'--enable-pic',
)
->configure(
'--disable-curl',
'--disable-sasl',
'--disable-valgrind',
'--disable-zlib',
'--disable-ssl',
)
->make();
$this->patchPkgconfPrefix(['rdkafka.pc', 'rdkafka-static.pc', 'rdkafka++.pc', 'rdkafka++-static.pc']);
// remove dynamic libs
shell()
->exec("rm -rf {$this->getLibDir()}/*.so.*")
->exec("rm -rf {$this->getLibDir()}/*.so")
->exec("rm -rf {$this->getLibDir()}/*.dylib");
} }
} }

View File

@ -47,7 +47,7 @@ trait postgresql
{ {
$libs = array_map(fn ($x) => $x->getName(), $this->getDependencies(true)); $libs = array_map(fn ($x) => $x->getName(), $this->getDependencies(true));
$spc = new SPCConfigUtil($this->builder, ['no_php' => true, 'libs_only_deps' => true]); $spc = new SPCConfigUtil($this->builder, ['no_php' => true, 'libs_only_deps' => true]);
$config = $spc->config(libraries: $libs, include_suggest_lib: $this->builder->getOption('with-suggested-libs')); $config = $spc->config(libraries: $libs, include_suggest_lib: $this->builder->getOption('with-suggested-libs', false));
$env_vars = [ $env_vars = [
'CFLAGS' => $config['cflags'], 'CFLAGS' => $config['cflags'],

View File

@ -159,7 +159,9 @@ class ExceptionHandler
public static function handleDefaultException(\Throwable $e): void public static function handleDefaultException(\Throwable $e): void
{ {
$class = get_class($e); $class = get_class($e);
self::logError("✗ Unhandled exception {$class}:\n\t{$e->getMessage()}\n"); $file = $e->getFile();
$line = $e->getLine();
self::logError("✗ Unhandled exception {$class} on {$file} line {$line}:\n\t{$e->getMessage()}\n");
self::logError('Stack trace:'); self::logError('Stack trace:');
self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4); self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
self::logError('⚠ Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues'); self::logError('⚠ Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');

View File

@ -7,6 +7,7 @@ namespace SPC\util;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\LibraryBase;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
@ -53,6 +54,9 @@ class SPCConfigUtil
*/ */
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
{ {
logger()->debug('config extensions: ' . implode(',', $extensions));
logger()->debug('config libs: ' . implode(',', $libraries));
logger()->debug('config suggest for [ext, lib]: ' . ($include_suggest_ext ? 'true' : 'false') . ',' . ($include_suggest_lib ? 'true' : 'false'));
$extra_exts = []; $extra_exts = [];
foreach ($extensions as $ext) { foreach ($extensions as $ext) {
$extra_exts = array_merge($extra_exts, Config::getExt($ext, 'ext-suggests', [])); $extra_exts = array_merge($extra_exts, Config::getExt($ext, 'ext-suggests', []));
@ -124,6 +128,63 @@ class SPCConfigUtil
]; ];
} }
/**
* [Helper function]
* Get configuration for a specific extension(s) dependencies.
*
* @param Extension|Extension[] $extension Extension instance or list
* @param bool $include_suggest_ext Whether to include suggested extensions
* @param bool $include_suggest_lib Whether to include suggested libraries
* @return array{
* cflags: string,
* ldflags: string,
* libs: string
* }
*/
public function getExtensionConfig(array|Extension $extension, bool $include_suggest_ext = false, bool $include_suggest_lib = false): array
{
if (!is_array($extension)) {
$extension = [$extension];
}
$libs = array_map(fn ($y) => $y->getName(), array_merge(...array_map(fn ($x) => $x->getLibraryDependencies(true), $extension)));
return $this->config(
extensions: array_map(fn ($x) => $x->getName(), $extension),
libraries: $libs,
include_suggest_ext: $include_suggest_ext ?: $this->builder?->getOption('with-suggested-exts') ?? false,
include_suggest_lib: $include_suggest_lib ?: $this->builder?->getOption('with-suggested-libs') ?? false,
);
}
/**
* [Helper function]
* Get configuration for a specific library(s) dependencies.
*
* @param LibraryBase|LibraryBase[] $lib Library instance or list
* @param bool $include_suggest_lib Whether to include suggested libraries
* @return array{
* cflags: string,
* ldflags: string,
* libs: string
* }
*/
public function getLibraryConfig(array|LibraryBase $lib, bool $include_suggest_lib = false): array
{
if (!is_array($lib)) {
$lib = [$lib];
}
$save_no_php = $this->no_php;
$this->no_php = true;
$save_libs_only_deps = $this->libs_only_deps;
$this->libs_only_deps = true;
$ret = $this->config(
libraries: array_map(fn ($x) => $x->getName(), $lib),
include_suggest_lib: $include_suggest_lib ?: $this->builder?->getOption('with-suggested-libs') ?? false,
);
$this->no_php = $save_no_php;
$this->libs_only_deps = $save_libs_only_deps;
return $ret;
}
private function hasCpp(array $extensions, array $libraries): bool private function hasCpp(array $extensions, array $libraries): bool
{ {
// judge cpp-extension // judge cpp-extension

View File

@ -13,11 +13,11 @@ 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',
'8.5', // '8.5',
// 'git', // 'git',
]; ];
@ -50,8 +50,8 @@ $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' => 'trader', 'Linux', 'Darwin' => 'rdkafka',
'Windows' => 'bcmath,bz2,calendar,ctype,curl,dba,dom,exif,ffi,fileinfo,filter,ftp,iconv,libxml,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_mysql,pdo_sqlite,pdo_sqlsrv,phar,session,shmop,simdjson,simplexml,soap,sockets,sqlite3,sqlsrv,ssh2,sysvshm,tokenizer,xml,xmlreader,xmlwriter,yaml,zip,zlib', 'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip',
}; };
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`). // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
@ -62,7 +62,7 @@ $shared_extensions = match (PHP_OS_FAMILY) {
}; };
// If you want to test lib-suggests for all extensions and libraries, set it to true. // If you want to test lib-suggests for all extensions and libraries, set it to true.
$with_suggested_libs = false; $with_suggested_libs = true;
// If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true. // If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true.
$with_libs = match (PHP_OS_FAMILY) { $with_libs = match (PHP_OS_FAMILY) {