2023-03-18 17:32:21 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace SPC\builder\traits;
|
|
|
|
|
|
2025-08-31 14:24:28 +08:00
|
|
|
use SPC\exception\ExecutionException;
|
2025-08-31 18:05:09 +08:00
|
|
|
use SPC\exception\SPCInternalException;
|
2025-08-31 14:24:28 +08:00
|
|
|
use SPC\exception\WrongUsageException;
|
|
|
|
|
use SPC\toolchain\ToolchainManager;
|
|
|
|
|
use SPC\toolchain\ZigToolchain;
|
|
|
|
|
use SPC\util\SPCTarget;
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
trait UnixSystemUtilTrait
|
|
|
|
|
{
|
|
|
|
|
/**
|
2025-08-31 14:24:28 +08:00
|
|
|
* 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)) {
|
2025-08-31 18:05:09 +08:00
|
|
|
self::exportDynamicSymbols($lib_file);
|
|
|
|
|
}
|
|
|
|
|
if (!is_file($symbol_file)) {
|
|
|
|
|
throw new SPCInternalException("The symbol file {$symbol_file} does not exist, please check if nm command is available.");
|
2025-08-31 14:24:28 +08:00
|
|
|
}
|
2026-03-10 08:42:17 +07:00
|
|
|
// https://github.com/ziglang/zig/issues/24662
|
|
|
|
|
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
|
|
|
|
|
return '-Wl,--export-dynamic'; // needs release 0.16, can be removed then
|
|
|
|
|
}
|
2026-01-17 11:26:28 +01:00
|
|
|
// macOS/zig
|
|
|
|
|
if (SPCTarget::getTargetOS() !== 'Linux' || ToolchainManager::getToolchainClass() === ZigToolchain::class) {
|
2025-08-31 14:24:28 +08:00
|
|
|
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
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public static function findCommand(string $name, array $paths = []): ?string
|
|
|
|
|
{
|
|
|
|
|
if (!$paths) {
|
|
|
|
|
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
|
|
|
|
|
}
|
2025-08-27 11:40:06 +07:00
|
|
|
if (str_starts_with($name, '/')) {
|
|
|
|
|
return file_exists($name) ? $name : null;
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
foreach ($paths as $path) {
|
|
|
|
|
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
|
|
|
|
|
return $path . DIRECTORY_SEPARATOR . $name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
|
|
|
|
|
/**
|
2025-08-31 14:24:28 +08:00
|
|
|
* Make environment variable string for shell command.
|
|
|
|
|
*
|
2023-08-20 19:51:45 +08:00
|
|
|
* @param array $vars Variables, like: ["CFLAGS" => "-Ixxx"]
|
|
|
|
|
* @return string like: CFLAGS="-Ixxx"
|
|
|
|
|
*/
|
|
|
|
|
public static function makeEnvVarString(array $vars): string
|
|
|
|
|
{
|
|
|
|
|
$str = '';
|
|
|
|
|
foreach ($vars as $key => $value) {
|
|
|
|
|
if ($str !== '') {
|
|
|
|
|
$str .= ' ';
|
|
|
|
|
}
|
|
|
|
|
$str .= $key . '=' . escapeshellarg($value);
|
|
|
|
|
}
|
|
|
|
|
return $str;
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|