From 465bd3ce8528d62c58887facb488e845b7b12902 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 31 Aug 2025 14:24:28 +0800 Subject: [PATCH] Use separated functions for exporting symbols --- src/SPC/builder/linux/LinuxBuilder.php | 2 + src/SPC/builder/macos/MacOSBuilder.php | 2 + .../builder/traits/UnixSystemUtilTrait.php | 87 +++++++++++++++++-- src/SPC/builder/unix/UnixBuilderBase.php | 74 ++-------------- 4 files changed, 92 insertions(+), 73 deletions(-) diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 90d5c7b9..3d8a30f7 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -318,6 +318,8 @@ class LinuxBuilder extends UnixBuilderBase if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { $AR = getenv('AR') ?: 'ar'; f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); + // export dynamic symbols + SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a'); } if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) { diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index a67161c5..66e9a399 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -252,6 +252,8 @@ class MacOSBuilder extends UnixBuilderBase if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { $AR = getenv('AR') ?: 'ar'; f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); + // export dynamic symbols + SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a'); } $this->patchPhpScripts(); } diff --git a/src/SPC/builder/traits/UnixSystemUtilTrait.php b/src/SPC/builder/traits/UnixSystemUtilTrait.php index a64c2d94..a4e18b62 100644 --- a/src/SPC/builder/traits/UnixSystemUtilTrait.php +++ b/src/SPC/builder/traits/UnixSystemUtilTrait.php @@ -4,15 +4,88 @@ declare(strict_types=1); namespace SPC\builder\traits; -/** - * Unix 系统的工具函数 Trait,适用于 Linux、macOS - */ +use SPC\exception\ExecutionException; +use SPC\exception\WrongUsageException; +use SPC\toolchain\ToolchainManager; +use SPC\toolchain\ZigToolchain; +use SPC\util\SPCTarget; + trait UnixSystemUtilTrait { /** - * @param string $name 命令名称 - * @param array $paths 寻找的目标路径(如果不传入,则使用环境变量 PATH) - * @return null|string 找到了返回命令路径,找不到返回 null + * Export static library dynamic symbols to a .dynsym file. + * It will export to "/path/to/libxxx.a.dynsym". + * + * @param string $lib_file Static library file path (e.g. /path/to/libxxx.a) + */ + public static function exportDynamicSymbols(string $lib_file): void + { + // check + if (!is_file($lib_file)) { + throw new WrongUsageException("The lib archive file {$lib_file} does not exist, please build it first."); + } + // shell out + $cmd = 'nm -g --defined-only -P ' . escapeshellarg($lib_file); + $result = shell()->execWithResult($cmd); + if ($result[0] !== 0) { + throw new ExecutionException($cmd, 'Failed to get defined symbols from ' . $lib_file); + } + // parse shell output and filter + $defined = []; + foreach ($result[1] as $line) { + $line = trim($line); + if ($line === '' || str_ends_with($line, '.o:') || str_ends_with($line, '.o]:')) { + continue; + } + $name = strtok($line, " \t"); + if (!$name) { + continue; + } + $name = preg_replace('/@.*$/', '', $name); + if ($name !== '' && $name !== false) { + $defined[] = $name; + } + } + $defined = array_unique($defined); + sort($defined); + // export + if (SPCTarget::getTargetOS() === 'Linux') { + file_put_contents("{$lib_file}.dynsym", "{\n" . implode("\n", array_map(fn ($x) => " {$x};", $defined)) . "};\n"); + } else { + file_put_contents("{$lib_file}.dynsym", implode("\n", $defined) . "\n"); + } + } + + /** + * Get linker flag to export dynamic symbols from a static library. + * + * @param string $lib_file Static library file path (e.g. /path/to/libxxx.a) + * @return null|string Linker flag to export dynamic symbols, null if no .dynsym file found + */ + public static function getDynamicExportedSymbols(string $lib_file): ?string + { + $symbol_file = "{$lib_file}.dynsym"; + if (!is_file($symbol_file)) { + return null; + } + // https://github.com/ziglang/zig/issues/24662 + if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { + return '-Wl,--export-dynamic'; + } + // macOS + if (SPCTarget::getTargetOS() !== 'Linux') { + return "-Wl,-exported_symbols_list,{$symbol_file}"; + } + return "-Wl,--dynamic-list={$symbol_file}"; + } + + /** + * Find a command in given paths or system PATH. + * If $name is an absolute path, check if it exists. + * + * @param string $name Command name or absolute path + * @param array $paths Paths to search, if empty, use system PATH + * @return null|string Absolute path of the command if found, null otherwise */ public static function findCommand(string $name, array $paths = []): ?string { @@ -31,6 +104,8 @@ trait UnixSystemUtilTrait } /** + * Make environment variable string for shell command. + * * @param array $vars Variables, like: ["CFLAGS" => "-Ixxx"] * @return string like: CFLAGS="-Ixxx" */ diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 64fe32b3..9e96c7a2 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\unix; use SPC\builder\BuilderBase; +use SPC\builder\linux\SystemUtil as LinuxSystemUtil; use SPC\exception\SPCInternalException; use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; @@ -15,7 +16,6 @@ use SPC\store\FileSystem; use SPC\store\pkg\GoXcaddy; use SPC\toolchain\GccNativeToolchain; use SPC\toolchain\ToolchainManager; -use SPC\toolchain\ZigToolchain; use SPC\util\DependencyUtil; use SPC\util\GlobalEnvManager; use SPC\util\SPCConfigUtil; @@ -32,68 +32,6 @@ abstract class UnixBuilderBase extends BuilderBase /** @var string LD flags */ public string $arch_ld_flags; - private ?string $dynamic_export_list = null; - - public function getDynamicExportSymbolsArgument(): ?string - { - if ($this->dynamic_export_list) { - return $this->dynamic_export_list; - } - if (SPCTarget::isStatic()) { - return null; - } - - $defined = []; - $libphp = BUILD_LIB_PATH . '/libphp.a'; - if (!is_file($libphp)) { - throw new WrongUsageException('You must build libphp.a before calling this function.'); - } - - if ($out = shell_exec('nm -g --defined-only -P ' . escapeshellarg($libphp) . ' 2>/dev/null')) { - foreach (preg_split('/\R/', trim($out)) as $line) { - if ($line === '' || str_ends_with($line, '.o:') || str_ends_with($line, '.o]:')) { - continue; - } - $name = strtok($line, " \t"); - if (!$name) { - continue; - } - $name = preg_replace('/@.*$/', '', $name); - if ($name !== '' && $name !== false) { - $defined[] = $name; - } - } - } - $defined = array_unique($defined); - sort($defined); - - $exportList = BUILD_LIB_PATH . '/export-dynamic.list'; - $lines = []; - if (SPCTarget::getTargetOS() === 'Linux') { - $lines[] = '{'; - foreach ($defined as $sym) { - $lines[] = " {$sym};"; - } - $lines[] = '};'; - } else { - foreach ($defined as $sym) { - $lines[] = $sym; - } - } - file_put_contents($exportList, implode("\n", $lines) . "\n"); - - $argument = "-Wl,--dynamic-list={$exportList}"; - if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { - $argument = '-Wl,--export-dynamic'; // https://github.com/ziglang/zig/issues/24662 - } - if (SPCTarget::getTargetOS() !== 'Linux') { - $argument = "-Wl,-exported_symbols_list,{$exportList}"; - } - - $this->dynamic_export_list = $argument; - return $argument; - } - public function proveLibs(array $sorted_libraries): void { // search all supported libs @@ -215,11 +153,13 @@ abstract class UnixBuilderBase extends BuilderBase foreach (glob(BUILD_LIB_PATH . "/libphp*.{$suffix}") as $file) { unlink($file); } - if ($dynamicSymbolsArgument = $this->getDynamicExportSymbolsArgument()) { - $dynamic_exports = ' ' . $dynamicSymbolsArgument; + // calling linux system util in other unix OS is okay + if ($dynamic_exports = LinuxSystemUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a')) { + $dynamic_exports = ' ' . $dynamic_exports; } } - [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens . ' ' . $dynamic_exports); + $cc = getenv('CC'); + [$ret, $out] = shell()->cd($sample_file_path)->execWithResult("{$cc} -o embed embed.c {$lens} {$dynamic_exports}"); if ($ret !== 0) { throw new ValidationException( 'embed failed sanity check: build failed. Error message: ' . implode("\n", $out), @@ -338,7 +278,7 @@ abstract class UnixBuilderBase extends BuilderBase if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { $libphpVersion = preg_replace('/\.\d+$/', '', $libphpVersion); } else { - if ($dynamicSymbolsArgument = $this->getDynamicExportSymbolsArgument()) { + if ($dynamicSymbolsArgument = LinuxSystemUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a')) { $dynamic_exports = ' ' . $dynamicSymbolsArgument; } }