Skip to content

Commit 38ec03f

Browse files
authored
Merge pull request #871 from crazywhalecc/fix/frankenphp-dynamic-exports
build frankenphp and embed after shared extensions
2 parents 5583677 + 08a6879 commit 38ec03f

File tree

8 files changed

+129
-25
lines changed

8 files changed

+129
-25
lines changed

src/SPC/builder/freebsd/BSDBuilder.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
118118
}
119119
$this->buildEmbed();
120120
}
121+
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
122+
if (!empty($shared_extensions)) {
123+
logger()->info('Building shared extensions ...');
124+
$this->buildSharedExts();
125+
}
121126
if ($enableFrankenphp) {
122127
logger()->info('building frankenphp');
123128
$this->buildFrankenphp();

src/SPC/builder/linux/LinuxBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
142142
}
143143
$this->buildEmbed();
144144
}
145+
// build dynamic extensions if needed, must happen before building FrankenPHP to make sure we export all necessary, undefined symbols
146+
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
147+
if (!empty($shared_extensions)) {
148+
logger()->info('Building shared extensions ...');
149+
$this->buildSharedExts();
150+
}
145151
if ($enableFrankenphp) {
146152
logger()->info('building frankenphp');
147153
$this->buildFrankenphp();
@@ -312,6 +318,8 @@ protected function buildEmbed(): void
312318
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
313319
$AR = getenv('AR') ?: 'ar';
314320
f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a');
321+
// export dynamic symbols
322+
SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a');
315323
}
316324

317325
if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) {

src/SPC/builder/linux/library/liburing.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ protected function build(): void
5151
$use_libc ? '--use-libc' : '',
5252
)
5353
->configure()
54-
->make('library', with_clean: false)
55-
->exec("rm -rf {$this->getLibDir()}/liburing*.so*");
54+
->make('library', 'install ENABLE_SHARED=0', with_clean: false);
5655

5756
$this->patchPkgconfPrefix();
5857
}

src/SPC/builder/macos/MacOSBuilder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
156156
}
157157
$this->buildEmbed();
158158
}
159+
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
160+
if (!empty($shared_extensions)) {
161+
logger()->info('Building shared extensions ...');
162+
$this->buildSharedExts();
163+
}
159164
if ($enableFrankenphp) {
160165
logger()->info('building frankenphp');
161166
$this->buildFrankenphp();
@@ -247,6 +252,8 @@ protected function buildEmbed(): void
247252
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
248253
$AR = getenv('AR') ?: 'ar';
249254
f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a');
255+
// export dynamic symbols
256+
SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a');
250257
}
251258
$this->patchPhpScripts();
252259
}

src/SPC/builder/traits/UnixSystemUtilTrait.php

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,92 @@
44

55
namespace SPC\builder\traits;
66

7-
/**
8-
* Unix 系统的工具函数 Trait,适用于 Linux、macOS
9-
*/
7+
use SPC\exception\ExecutionException;
8+
use SPC\exception\SPCInternalException;
9+
use SPC\exception\WrongUsageException;
10+
use SPC\toolchain\ToolchainManager;
11+
use SPC\toolchain\ZigToolchain;
12+
use SPC\util\SPCTarget;
13+
1014
trait UnixSystemUtilTrait
1115
{
1216
/**
13-
* @param string $name 命令名称
14-
* @param array $paths 寻找的目标路径(如果不传入,则使用环境变量 PATH)
15-
* @return null|string 找到了返回命令路径,找不到返回 null
17+
* Export static library dynamic symbols to a .dynsym file.
18+
* It will export to "/path/to/libxxx.a.dynsym".
19+
*
20+
* @param string $lib_file Static library file path (e.g. /path/to/libxxx.a)
21+
*/
22+
public static function exportDynamicSymbols(string $lib_file): void
23+
{
24+
// check
25+
if (!is_file($lib_file)) {
26+
throw new WrongUsageException("The lib archive file {$lib_file} does not exist, please build it first.");
27+
}
28+
// shell out
29+
$cmd = 'nm -g --defined-only -P ' . escapeshellarg($lib_file);
30+
$result = shell()->execWithResult($cmd);
31+
if ($result[0] !== 0) {
32+
throw new ExecutionException($cmd, 'Failed to get defined symbols from ' . $lib_file);
33+
}
34+
// parse shell output and filter
35+
$defined = [];
36+
foreach ($result[1] as $line) {
37+
$line = trim($line);
38+
if ($line === '' || str_ends_with($line, '.o:') || str_ends_with($line, '.o]:')) {
39+
continue;
40+
}
41+
$name = strtok($line, " \t");
42+
if (!$name) {
43+
continue;
44+
}
45+
$name = preg_replace('/@.*$/', '', $name);
46+
if ($name !== '' && $name !== false) {
47+
$defined[] = $name;
48+
}
49+
}
50+
$defined = array_unique($defined);
51+
sort($defined);
52+
// export
53+
if (SPCTarget::getTargetOS() === 'Linux') {
54+
file_put_contents("{$lib_file}.dynsym", "{\n" . implode("\n", array_map(fn ($x) => " {$x};", $defined)) . "};\n");
55+
} else {
56+
file_put_contents("{$lib_file}.dynsym", implode("\n", $defined) . "\n");
57+
}
58+
}
59+
60+
/**
61+
* Get linker flag to export dynamic symbols from a static library.
62+
*
63+
* @param string $lib_file Static library file path (e.g. /path/to/libxxx.a)
64+
* @return null|string Linker flag to export dynamic symbols, null if no .dynsym file found
65+
*/
66+
public static function getDynamicExportedSymbols(string $lib_file): ?string
67+
{
68+
$symbol_file = "{$lib_file}.dynsym";
69+
if (!is_file($symbol_file)) {
70+
self::exportDynamicSymbols($lib_file);
71+
}
72+
if (!is_file($symbol_file)) {
73+
throw new SPCInternalException("The symbol file {$symbol_file} does not exist, please check if nm command is available.");
74+
}
75+
// https://github.com/ziglang/zig/issues/24662
76+
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
77+
return '-Wl,--export-dynamic';
78+
}
79+
// macOS
80+
if (SPCTarget::getTargetOS() !== 'Linux') {
81+
return "-Wl,-exported_symbols_list,{$symbol_file}";
82+
}
83+
return "-Wl,--dynamic-list={$symbol_file}";
84+
}
85+
86+
/**
87+
* Find a command in given paths or system PATH.
88+
* If $name is an absolute path, check if it exists.
89+
*
90+
* @param string $name Command name or absolute path
91+
* @param array $paths Paths to search, if empty, use system PATH
92+
* @return null|string Absolute path of the command if found, null otherwise
1693
*/
1794
public static function findCommand(string $name, array $paths = []): ?string
1895
{
@@ -31,6 +108,8 @@ public static function findCommand(string $name, array $paths = []): ?string
31108
}
32109

33110
/**
111+
* Make environment variable string for shell command.
112+
*
34113
* @param array $vars Variables, like: ["CFLAGS" => "-Ixxx"]
35114
* @return string like: CFLAGS="-Ixxx"
36115
*/

src/SPC/builder/unix/UnixBuilderBase.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace SPC\builder\unix;
66

77
use SPC\builder\BuilderBase;
8+
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
89
use SPC\exception\SPCInternalException;
910
use SPC\exception\ValidationException;
1011
use SPC\exception\WrongUsageException;
@@ -137,6 +138,7 @@ protected function sanityCheck(int $build_target): void
137138
if (SPCTarget::isStatic()) {
138139
$lens .= ' -static';
139140
}
141+
$dynamic_exports = '';
140142
// if someone changed to EMBED_TYPE=shared, we need to add LD_LIBRARY_PATH
141143
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
142144
if (PHP_OS_FAMILY === 'Darwin') {
@@ -151,8 +153,13 @@ protected function sanityCheck(int $build_target): void
151153
foreach (glob(BUILD_LIB_PATH . "/libphp*.{$suffix}") as $file) {
152154
unlink($file);
153155
}
156+
// calling linux system util in other unix OS is okay
157+
if ($dynamic_exports = LinuxSystemUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a')) {
158+
$dynamic_exports = ' ' . $dynamic_exports;
159+
}
154160
}
155-
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens);
161+
$cc = getenv('CC');
162+
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult("{$cc} -o embed embed.c {$lens} {$dynamic_exports}");
156163
if ($ret !== 0) {
157164
throw new ValidationException(
158165
'embed failed sanity check: build failed. Error message: ' . implode("\n", $out),
@@ -267,15 +274,20 @@ protected function buildFrankenphp(): void
267274
), true);
268275
$frankenPhpVersion = $releaseInfo['tag_name'];
269276
$libphpVersion = $this->getPHPVersion();
277+
$dynamic_exports = '';
270278
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
271-
$libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion);
279+
$libphpVersion = preg_replace('/\.\d+$/', '', $libphpVersion);
280+
} else {
281+
if ($dynamicSymbolsArgument = LinuxSystemUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a')) {
282+
$dynamic_exports = ' ' . $dynamicSymbolsArgument;
283+
}
272284
}
273285
$debugFlags = $this->getOption('no-strip') ? '-w -s ' : '';
274-
$extLdFlags = "-extldflags '-pie'";
286+
$extLdFlags = "-extldflags '-pie{$dynamic_exports}'";
275287
$muslTags = '';
276288
$staticFlags = '';
277289
if (SPCTarget::isStatic()) {
278-
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
290+
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000{$dynamic_exports}'";
279291
$muslTags = 'static_build,';
280292
$staticFlags = '-static-pie';
281293
}

src/SPC/command/BuildPHPCommand.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,6 @@ public function handle(): int
211211
// start to build
212212
$builder->buildPHP($rule);
213213

214-
// build dynamic extensions if needed
215-
if (!empty($shared_extensions)) {
216-
logger()->info('Building shared extensions ...');
217-
$builder->buildSharedExts();
218-
}
219-
220214
$builder->testPHP($rule);
221215

222216
// compile stopwatch :P

src/globals/test-extensions.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
// test php version (8.1 ~ 8.4 available, multiple for matrix)
1515
$test_php_version = [
16-
'8.1',
17-
'8.2',
18-
'8.3',
16+
// '8.1',
17+
// '8.2',
18+
// '8.3',
1919
'8.4',
2020
// '8.5',
2121
// 'git',
@@ -28,9 +28,9 @@
2828
'macos-15', // bin/spc for arm64
2929
'ubuntu-latest', // bin/spc-alpine-docker for x86_64
3030
'ubuntu-22.04', // bin/spc-gnu-docker for x86_64
31-
'ubuntu-24.04', // bin/spc for x86_64
31+
// 'ubuntu-24.04', // bin/spc for x86_64
3232
'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64
33-
'ubuntu-24.04-arm', // bin/spc for arm64
33+
// 'ubuntu-24.04-arm', // bin/spc for arm64
3434
// 'windows-latest', // .\bin\spc.ps1
3535
];
3636

@@ -50,13 +50,13 @@
5050

5151
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
5252
$extensions = match (PHP_OS_FAMILY) {
53-
'Linux', 'Darwin' => 'swoole,swoole-hook-mysql,swoole-hook-pgsql,swoole-hook-sqlite,swoole-hook-odbc,apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,event,exif,fileinfo,filter,ftp,gd,gmp,iconv,imagick,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,swoole,sysvmsg,sysvsem,sysvshm,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib',
53+
'Linux', 'Darwin' => 'bcmath',
5454
'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',
5555
};
5656

5757
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
5858
$shared_extensions = match (PHP_OS_FAMILY) {
59-
'Linux' => '',
59+
'Linux' => 'zip',
6060
'Darwin' => '',
6161
'Windows' => '',
6262
};

0 commit comments

Comments
 (0)