mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-08 01:15:37 +08:00
v3 base
This commit is contained in:
148
src/StaticPHP/Util/System/LinuxUtil.php
Normal file
148
src/StaticPHP/Util/System/LinuxUtil.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util\System;
|
||||
|
||||
class LinuxUtil extends UnixUtil
|
||||
{
|
||||
/** @var null|string libc version cache */
|
||||
public static ?string $libc_version = null;
|
||||
|
||||
/**
|
||||
* Get current linux distro name and version.
|
||||
*
|
||||
* @noinspection PhpMissingBreakStatementInspection
|
||||
* @return array{dist: string, ver: string} Linux distro info (unknown if not found)
|
||||
*/
|
||||
public static function getOSRelease(): array
|
||||
{
|
||||
$ret = [
|
||||
'dist' => 'unknown',
|
||||
'ver' => 'unknown',
|
||||
];
|
||||
switch (true) {
|
||||
case file_exists('/etc/centos-release'):
|
||||
$lines = file('/etc/centos-release');
|
||||
$centos = true;
|
||||
goto rh;
|
||||
case file_exists('/etc/redhat-release'):
|
||||
$lines = file('/etc/redhat-release');
|
||||
$centos = false;
|
||||
rh:
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/release\s+(\d*(\.\d+)*)/', $line, $matches)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$ret['dist'] = $centos ? 'centos' : 'redhat';
|
||||
$ret['ver'] = $matches[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case file_exists('/etc/os-release'):
|
||||
$lines = file('/etc/os-release');
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^ID=(.*)$/', $line, $matches)) {
|
||||
$ret['dist'] = $matches[1];
|
||||
}
|
||||
if (preg_match('/^VERSION_ID=(.*)$/', $line, $matches)) {
|
||||
$ret['ver'] = $matches[1];
|
||||
}
|
||||
}
|
||||
$ret['dist'] = trim($ret['dist'], '"\'');
|
||||
$ret['ver'] = trim($ret['ver'], '"\'');
|
||||
if (strcasecmp($ret['dist'], 'centos') === 0) {
|
||||
$ret['dist'] = 'redhat';
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current linux distro is musl-based (alpine).
|
||||
*/
|
||||
public static function isMuslDist(): bool
|
||||
{
|
||||
return static::getOSRelease()['dist'] === 'alpine';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CPU core count.
|
||||
*/
|
||||
public static function getCpuCount(): int
|
||||
{
|
||||
$ncpu = 1;
|
||||
|
||||
if (is_file('/proc/cpuinfo')) {
|
||||
$cpuinfo = file_get_contents('/proc/cpuinfo');
|
||||
preg_match_all('/^processor/m', $cpuinfo, $matches);
|
||||
$ncpu = count($matches[0]);
|
||||
}
|
||||
|
||||
return $ncpu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fully-supported linux distros.
|
||||
*
|
||||
* @return string[] List of supported Linux distro name for doctor
|
||||
*/
|
||||
public static function getSupportedDistros(): array
|
||||
{
|
||||
return [
|
||||
// debian-like
|
||||
'debian', 'ubuntu', 'Deepin',
|
||||
// rhel-like
|
||||
'redhat',
|
||||
// centos
|
||||
'centos',
|
||||
// alpine
|
||||
'alpine',
|
||||
// arch
|
||||
'arch', 'manjaro',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get libc version string from ldd.
|
||||
*/
|
||||
public static function getLibcVersionIfExists(?string $libc = null): ?string
|
||||
{
|
||||
if (self::$libc_version !== null) {
|
||||
return self::$libc_version;
|
||||
}
|
||||
if ($libc === 'glibc') {
|
||||
$result = shell()->execWithResult('ldd --version', false);
|
||||
if ($result[0] !== 0) {
|
||||
return null;
|
||||
}
|
||||
// get first line
|
||||
$first_line = $result[1][0];
|
||||
// match ldd version: "ldd (some useless text) 2.17" match 2.17
|
||||
$pattern = '/ldd\s+\(.*?\)\s+(\d+\.\d+)/';
|
||||
if (preg_match($pattern, $first_line, $matches)) {
|
||||
self::$libc_version = $matches[1];
|
||||
return self::$libc_version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if ($libc === 'musl') {
|
||||
if (self::isMuslDist()) {
|
||||
$result = shell()->execWithResult('ldd 2>&1', false);
|
||||
} elseif (is_file('/usr/local/musl/lib/libc.so')) {
|
||||
$result = shell()->execWithResult('/usr/local/musl/lib/libc.so 2>&1', false);
|
||||
} else {
|
||||
$arch = php_uname('m');
|
||||
$result = shell()->execWithResult("/lib/ld-musl-{$arch}.so.1 2>&1", false);
|
||||
}
|
||||
// Match Version * line
|
||||
// match ldd version: "Version 1.2.3" match 1.2.3
|
||||
$pattern = '/Version\s+(\d+\.\d+\.\d+)/';
|
||||
if (preg_match($pattern, $result[1][1] ?? '', $matches)) {
|
||||
self::$libc_version = $matches[1];
|
||||
return self::$libc_version;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
42
src/StaticPHP/Util/System/MacOSUtil.php
Normal file
42
src/StaticPHP/Util/System/MacOSUtil.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util\System;
|
||||
|
||||
use StaticPHP\Exception\EnvironmentException;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
|
||||
class MacOSUtil extends UnixUtil
|
||||
{
|
||||
/**
|
||||
* Get Logic CPU Count for macOS.
|
||||
*/
|
||||
public static function getCpuCount(): int
|
||||
{
|
||||
$cpu = exec('sysctl -n hw.ncpu', $output, $ret);
|
||||
if ($ret !== 0) {
|
||||
throw new EnvironmentException(
|
||||
'Failed to get cpu count from macOS sysctl',
|
||||
'Please ensure you are running this command on a macOS system and have the sysctl command available.'
|
||||
);
|
||||
}
|
||||
|
||||
return (int) $cpu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Target Arch CFlags.
|
||||
*
|
||||
* @param string $arch Arch Name
|
||||
* @return string return Arch CFlags string
|
||||
*/
|
||||
public static function getArchCFlags(string $arch): string
|
||||
{
|
||||
return match ($arch) {
|
||||
'x86_64' => '--target=x86_64-apple-darwin',
|
||||
'arm64','aarch64' => '--target=arm64-apple-darwin',
|
||||
default => throw new WrongUsageException('unsupported arch: ' . $arch),
|
||||
};
|
||||
}
|
||||
}
|
||||
128
src/StaticPHP/Util/System/UnixUtil.php
Normal file
128
src/StaticPHP/Util/System/UnixUtil.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util\System;
|
||||
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Exception\ExecutionException;
|
||||
use StaticPHP\Exception\SPCInternalException;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
|
||||
abstract class UnixUtil
|
||||
{
|
||||
/**
|
||||
* 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 (SystemTarget::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)) {
|
||||
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.");
|
||||
}
|
||||
// https://github.com/ziglang/zig/issues/24662
|
||||
if (ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain) {
|
||||
return '-Wl,--export-dynamic';
|
||||
}
|
||||
// macOS
|
||||
if (SystemTarget::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
|
||||
{
|
||||
if (!$paths) {
|
||||
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
|
||||
}
|
||||
if (str_starts_with($name, '/')) {
|
||||
return file_exists($name) ? $name : null;
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
|
||||
return $path . DIRECTORY_SEPARATOR . $name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make environment variable string for shell command.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
106
src/StaticPHP/Util/System/WindowsUtil.php
Normal file
106
src/StaticPHP/Util/System/WindowsUtil.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util\System;
|
||||
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
class WindowsUtil
|
||||
{
|
||||
/**
|
||||
* Find windows program using executable name.
|
||||
*
|
||||
* @param string $name command name (xxx.exe)
|
||||
* @param array $paths search path (default use env path)
|
||||
* @return null|string null if not found, string is absolute path
|
||||
*/
|
||||
public static function findCommand(string $name, array $paths = [], bool $include_sdk_bin = false): ?string
|
||||
{
|
||||
if (!$paths) {
|
||||
$paths = explode(PATH_SEPARATOR, getenv('Path'));
|
||||
if ($include_sdk_bin) {
|
||||
$paths[] = getenv('PHP_SDK_PATH') . '\bin';
|
||||
}
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
|
||||
return $path . DIRECTORY_SEPARATOR . $name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Visual Studio installation.
|
||||
*
|
||||
* @return array<string, string>|false False if not installed, array contains 'version' and 'dir'
|
||||
*/
|
||||
public static function findVisualStudio(): array|false
|
||||
{
|
||||
$check_path = [
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
|
||||
'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
|
||||
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
|
||||
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
|
||||
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
|
||||
];
|
||||
foreach ($check_path as $path => $vs_version) {
|
||||
if (file_exists($path)) {
|
||||
$vs_ver = $vs_version;
|
||||
$d_dir = dirname($path, 4);
|
||||
return [
|
||||
'version' => $vs_ver,
|
||||
'dir' => $d_dir,
|
||||
];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CPU count for concurrency.
|
||||
*/
|
||||
public static function getCpuCount(): int
|
||||
{
|
||||
$result = f_exec('echo %NUMBER_OF_PROCESSORS%', $out, $code);
|
||||
if ($code !== 0 || !$result) {
|
||||
return 1;
|
||||
}
|
||||
return intval($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create CMake toolchain file.
|
||||
*
|
||||
* @param null|string $cflags CFLAGS for cmake, default use '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP='
|
||||
* @param null|string $ldflags LDFLAGS for cmake, default use '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt'
|
||||
*/
|
||||
public static function makeCmakeToolchainFile(?string $cflags = null, ?string $ldflags = null): string
|
||||
{
|
||||
if ($cflags === null) {
|
||||
$cflags = '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP=';
|
||||
}
|
||||
if ($ldflags === null) {
|
||||
$ldflags = '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt';
|
||||
}
|
||||
$buildroot = str_replace('\\', '\\\\', BUILD_ROOT_PATH);
|
||||
$toolchain = <<<CMAKE
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
SET(CMAKE_SYSTEM_PROCESSOR x64)
|
||||
SET(CMAKE_C_FLAGS "{$cflags}")
|
||||
SET(CMAKE_C_FLAGS_DEBUG "{$cflags}")
|
||||
SET(CMAKE_CXX_FLAGS "{$cflags}")
|
||||
SET(CMAKE_CXX_FLAGS_DEBUG "{$cflags}")
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "{$ldflags}")
|
||||
SET(CMAKE_FIND_ROOT_PATH "{$buildroot}")
|
||||
SET(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
|
||||
CMAKE;
|
||||
if (!is_dir(SOURCE_PATH)) {
|
||||
FileSystem::createDir(SOURCE_PATH);
|
||||
}
|
||||
FileSystem::writeFile(SOURCE_PATH . '\toolchain.cmake', $toolchain);
|
||||
return realpath(SOURCE_PATH . '\toolchain.cmake');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user