This commit is contained in:
crazywhalecc
2025-11-30 15:35:04 +08:00
parent f6c818d3c0
commit 14bfb4198a
179 changed files with 19502 additions and 655 deletions

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Attribute\Doctor\OptionalCheck;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Toolchain\MuslToolchain;
use StaticPHP\Toolchain\ZigToolchain;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\System\LinuxUtil;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LinuxMuslCheck
{
public static function optionalCheck(): bool
{
return getenv('SPC_TOOLCHAIN') === MuslToolchain::class ||
(getenv('SPC_TOOLCHAIN') === ZigToolchain::class && !LinuxUtil::isMuslDist());
}
/** @noinspection PhpUnused */
#[CheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)]
public function checkMusl(): CheckResult
{
$musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m'));
if (file_exists($musl_wrapper_lib) && (file_exists('/usr/local/musl/lib/libc.a') || getenv('SPC_TOOLCHAIN') === ZigToolchain::class)) {
return CheckResult::ok();
}
return CheckResult::fail('musl-wrapper is not installed on your system', 'fix-musl-wrapper');
}
#[CheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)]
public function checkMuslCrossMake(): CheckResult
{
if (getenv('SPC_TOOLCHAIN') === ZigToolchain::class && !LinuxUtil::isMuslDist()) {
return CheckResult::ok();
}
$arch = arch2gnu(php_uname('m'));
$cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a";
$cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc";
if (file_exists($cross_compile_lib) && file_exists($cross_compile_gcc)) {
return CheckResult::ok();
}
return CheckResult::fail('musl-cross-make is not installed on your system', 'fix-musl-cross-make');
}
#[FixItem('fix-musl-wrapper')]
public function fixMusl(): bool
{
// TODO: implement musl-wrapper installation
// This should:
// 1. Download musl source using Downloader::downloadSource()
// 2. Extract the source using FileSystem::extractSource()
// 3. Apply CVE patches using SourcePatcher::patchFile()
// 4. Build and install musl wrapper
// 5. Add path using putenv instead of editing /etc/profile
return false;
}
#[FixItem('fix-musl-cross-make')]
public function fixMuslCrossMake(): bool
{
// TODO: implement musl-cross-make installation
// This should:
// 1. Install musl-toolchain package using PackageManager::installPackage()
// 2. Copy toolchain files to /usr/local/musl
return false;
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Util\System\MacOSUtil;
class MacOSToolCheck
{
public const array REQUIRED_COMMANDS = [
'curl',
'make',
'bison',
're2c',
'flex',
'pkg-config',
'git',
'autoconf',
'automake',
'tar',
'libtool',
'unzip',
'xz',
'gzip',
'bzip2',
'cmake',
'glibtoolize',
];
#[CheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)]
public function checkBrew(): ?CheckResult
{
if (($path = MacOSUtil::findCommand('brew')) === null) {
return CheckResult::fail('Homebrew is not installed', 'brew');
}
if ($path !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') {
return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !');
}
return CheckResult::ok();
}
#[CheckItem('if necessary tools are installed', limit_os: 'Darwin')]
public function checkCliTools(): ?CheckResult
{
$missing = [];
foreach (self::REQUIRED_COMMANDS as $cmd) {
if (MacOSUtil::findCommand($cmd) === null) {
$missing[] = $cmd;
}
}
if (!empty($missing)) {
return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools', [$missing]);
}
return CheckResult::ok();
}
#[CheckItem('if bison version is 3.0 or later', limit_os: 'Darwin')]
public function checkBisonVersion(array $command_path = []): ?CheckResult
{
// if the bison command is /usr/bin/bison, it is the system bison that may be too old
if (($bison = MacOSUtil::findCommand('bison', $command_path)) === null) {
return CheckResult::fail('bison is not installed or too old', 'build-tools', [['bison']]);
}
// check version: bison (GNU Bison) x.y(.z)
$version = shell()->execWithResult("{$bison} --version", false);
if (preg_match('/bison \(GNU Bison\) (\d+)\.(\d+)(?:\.(\d+))?/', $version[1][0], $matches)) {
$major = (int) $matches[1];
// major should be 3 or later
if ($major < 3) {
// find homebrew keg-only bison
if ($command_path !== []) {
return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]);
}
return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin']);
}
return CheckResult::ok($matches[0]);
}
return CheckResult::fail('bison version cannot be determined');
}
#[FixItem('brew')]
public function fixBrew(): bool
{
shell(true)->exec('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
return true;
}
#[FixItem('build-tools')]
public function fixBuildTools(array $missing): bool
{
$replacement = [
'glibtoolize' => 'libtool',
];
foreach ($missing as $cmd) {
if (isset($replacement[$cmd])) {
$cmd = $replacement[$cmd];
}
shell()->exec('brew install --formula ' . escapeshellarg($cmd));
}
return true;
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Util\System\LinuxUtil;
class OSCheck
{
#[CheckItem('if current OS are supported', level: 1000)]
public function checkOS(): ?CheckResult
{
if (!in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'Windows'])) {
return CheckResult::fail('Current OS is not supported: ' . PHP_OS_FAMILY);
}
$distro = PHP_OS_FAMILY === 'Linux' ? (' ' . LinuxUtil::getOSRelease()['dist']) : '';
$known_distro = PHP_OS_FAMILY !== 'Linux' || in_array(LinuxUtil::getOSRelease()['dist'], LinuxUtil::getSupportedDistros());
return CheckResult::ok(PHP_OS_FAMILY . ' ' . php_uname('m') . $distro . ', supported' . ($known_distro ? '' : ' (but not tested on this distro)'));
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Attribute\Doctor\OptionalCheck;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\PkgConfigUtil;
#[OptionalCheck([self::class, 'optionalCheck'])]
class PkgConfigCheck
{
public static function optionalCheck(): bool
{
return SystemTarget::getTargetOS() !== 'Windows';
}
#[CheckItem('if pkg-config is installed or built', level: 800)]
public function check(): CheckResult
{
if (!($pkgconf = PkgConfigUtil::findPkgConfig())) {
return CheckResult::fail('pkg-config is not installed or built', 'install-pkg-config');
}
return CheckResult::ok($pkgconf);
}
#[CheckItem('if pkg-config is functional', level: 799)]
public function checkFunctional(): CheckResult
{
$pkgconf = PkgConfigUtil::findPkgConfig();
[$ret, $output] = shell()->execWithResult("{$pkgconf} --version", false);
if ($ret !== 0) {
return CheckResult::fail('pkg-config is not functional', 'install-pkg-config');
}
return CheckResult::ok(trim($output[0]));
}
#[FixItem('install-pkg-config')]
public function fix(): bool
{
ApplicationContext::set('elephant', true);
$installer = new PackageInstaller(['dl-prefer-binary' => true]);
$installer->addInstallPackage('pkg-config');
$installer->run(false, true);
return true;
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Doctor\CheckResult;
class Re2cVersionCheck
{
#[CheckItem('if re2c version >= 1.0.3', limit_os: 'Linux', level: 20)]
#[CheckItem('if re2c version >= 1.0.3', limit_os: 'Darwin', level: 20)]
public function checkRe2cVersion(): ?CheckResult
{
$ver = shell(false)->execWithResult('re2c --version', false);
// match version: re2c X.X(.X)
if ($ver[0] !== 0 || !preg_match('/re2c\s+(\d+\.\d+(\.\d+)?)/', $ver[1][0], $matches)) {
return CheckResult::fail('Failed to get re2c version', 'build-re2c');
}
$version_string = $matches[1];
if (version_compare($version_string, '1.0.3') < 0) {
return CheckResult::fail('re2c version is too low (' . $version_string . ')', 'build-re2c');
}
return CheckResult::ok($version_string);
}
#[FixItem('build-re2c')]
public function buildRe2c(): bool
{
// TODO: implement re2c build process
return false;
}
}