diff --git a/src/SPC/builder/freebsd/BSDBuilder.php b/src/SPC/builder/freebsd/BSDBuilder.php index dcaa5a2d..dcfe2433 100644 --- a/src/SPC/builder/freebsd/BSDBuilder.php +++ b/src/SPC/builder/freebsd/BSDBuilder.php @@ -122,6 +122,11 @@ class BSDBuilder extends UnixBuilderBase } $this->buildEmbed(); } + $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared')))); + if (!empty($shared_extensions)) { + logger()->info('Building shared extensions ...'); + $this->buildSharedExts(); + } if ($enableFrankenphp) { logger()->info('building frankenphp'); $this->buildFrankenphp(); diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 373e1b21..90d5c7b9 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -142,6 +142,12 @@ class LinuxBuilder extends UnixBuilderBase } $this->buildEmbed(); } + // build dynamic extensions if needed, must happen before building FrankenPHP to make sure we export all necessary, undefined symbols + $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared')))); + if (!empty($shared_extensions)) { + logger()->info('Building shared extensions ...'); + $this->buildSharedExts(); + } if ($enableFrankenphp) { logger()->info('building frankenphp'); $this->buildFrankenphp(); diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index 00c1da8c..a67161c5 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -156,6 +156,11 @@ class MacOSBuilder extends UnixBuilderBase } $this->buildEmbed(); } + $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared')))); + if (!empty($shared_extensions)) { + logger()->info('Building shared extensions ...'); + $this->buildSharedExts(); + } if ($enableFrankenphp) { logger()->info('building frankenphp'); $this->buildFrankenphp(); diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index c09a9e1b..64d19478 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -31,6 +31,63 @@ abstract class UnixBuilderBase extends BuilderBase /** @var string LD flags */ public string $arch_ld_flags; + private static array $undefined_symbols = []; + + public static function getUndefinedSymbols(): array + { + if (count(self::$undefined_symbols)) { + return self::$undefined_symbols; + } + + $undefined = []; + + $addSymbolsFromString = function (string $out, &$list) { + foreach (preg_split('/\R/', trim($out)) as $line) { + if ($line === '') { + continue; + } + $name = strtok($line, " \t"); + if (!$name) { + continue; + } + $name = preg_replace('/@.*$/', '', $name); + if ($name !== '' && $name !== false) { + $list[$name] = true; + } + } + }; + + // collect undefined symbols from shared extensions + $files = glob(rtrim(BUILD_MODULES_PATH, '/') . DIRECTORY_SEPARATOR . '*.so') ?: []; + foreach ($files as $so) { + $cmd = 'nm -D --undefined-only -P ' . escapeshellarg($so) . ' 2>/dev/null'; + $out = shell_exec($cmd); + if ($out !== '') { + $addSymbolsFromString($out, $undefined); + } + } + + // keep only symbols defined in libphp.a + $defined = []; + $libphp = BUILD_LIB_PATH . '/libphp.a'; + if (!is_file($libphp)) { + throw new WrongUsageException('You must build libphp.a statically before calling this function.'); + } + $cmd = 'nm -g --defined-only -P ' . escapeshellarg($libphp) . ' 2>/dev/null'; + $out = shell_exec($cmd) ?: ''; + if ($out !== '') { + $addSymbolsFromString($out, $defined); + } + // intersect: only undefined symbols that libphp.a actually defines + $keys = array_intersect_key($undefined, $defined); + + $list = array_keys($keys); + sort($list, SORT_STRING); + + self::$undefined_symbols = $list; + return $list; + } + public function proveLibs(array $sorted_libraries): void { // search all supported libs @@ -137,6 +194,7 @@ abstract class UnixBuilderBase extends BuilderBase if (SPCTarget::isStatic()) { $lens .= ' -static'; } + $dynamic_exports = ''; // if someone changed to EMBED_TYPE=shared, we need to add LD_LIBRARY_PATH if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { if (PHP_OS_FAMILY === 'Darwin') { @@ -151,8 +209,17 @@ abstract class UnixBuilderBase extends BuilderBase foreach (glob(BUILD_LIB_PATH . "/libphp*.{$suffix}") as $file) { unlink($file); } + $symbols = self::getUndefinedSymbols(); // returns unique, version-stripped names + // turn list into many --export-dynamic-symbol=... flags + $dynamic_exports = implode( + ' ', + array_map( + static fn (string $s) => '-Wl,--export-dynamic-symbol=' . escapeshellarg($s), + $symbols + ) + ); } - [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); + [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('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), @@ -271,11 +338,23 @@ abstract class UnixBuilderBase extends BuilderBase $libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion); } $debugFlags = $this->getOption('no-strip') ? '-w -s ' : ''; - $extLdFlags = "-extldflags '-pie'"; + $dynamic_exports = ''; + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { + $symbols = self::getUndefinedSymbols(); // returns unique, version-stripped names + // turn list into many --export-dynamic-symbol=... flags + $dynamic_exports = ' ' . implode( + ' ', + array_map( + static fn (string $s) => '-Wl,--export-dynamic-symbol="' . $s . '"', + $symbols + ) + ); + } + $extLdFlags = "-extldflags '-pie{$dynamic_exports}'"; $muslTags = ''; $staticFlags = ''; if (SPCTarget::isStatic()) { - $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'"; + $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000{$dynamic_exports}'"; $muslTags = 'static_build,'; $staticFlags = '-static-pie'; } diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index fb14e47c..58b6059b 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -211,12 +211,6 @@ class BuildPHPCommand extends BuildCommand // start to build $builder->buildPHP($rule); - // build dynamic extensions if needed - if (!empty($shared_extensions)) { - logger()->info('Building shared extensions ...'); - $builder->buildSharedExts(); - } - $builder->testPHP($rule); // compile stopwatch :P