2025-12-08 09:49:11 +08:00
< ? 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\Exception\EnvironmentException ;
use StaticPHP\Util\System\LinuxUtil ;
class LinuxToolCheck
{
public const TOOLS_ALPINE = [
2026-05-06 11:39:08 +07:00
'make' , 'bison' , 're2c' , 'flex' , 'gperf' ,
2025-12-08 09:49:11 +08:00
'git' , 'autoconf' , 'automake' , 'gettext-dev' ,
'tar' , 'unzip' , 'gzip' ,
'bzip2' , 'cmake' , 'gcc' ,
'g++' , 'patch' , 'binutils-gold' ,
'libtoolize' , 'which' ,
];
public const TOOLS_DEBIAN = [
2026-05-06 11:39:08 +07:00
'make' , 'bison' , 're2c' , 'flex' , 'gperf' ,
2025-12-08 09:49:11 +08:00
'git' , 'autoconf' , 'automake' , 'autopoint' ,
'tar' , 'unzip' , 'gzip' , 'gcc' , 'g++' ,
'bzip2' , 'cmake' , 'patch' ,
'xz' , 'libtoolize' , 'which' ,
];
public const TOOLS_RHEL = [
2026-05-06 11:39:08 +07:00
'perl' , 'make' , 'bison' , 're2c' , 'flex' , 'gperf' ,
2025-12-08 09:49:11 +08:00
'git' , 'autoconf' , 'automake' ,
'tar' , 'unzip' , 'gzip' , 'gcc' , 'g++' ,
'bzip2' , 'cmake' , 'patch' , 'which' ,
2026-02-02 13:32:35 +08:00
'xz' , 'libtool' , 'gettext-devel' , 'file' ,
2025-12-08 09:49:11 +08:00
];
public const TOOLS_ARCH = [
2026-05-06 11:39:08 +07:00
'base-devel' , 'cmake' , 'gperf' ,
2025-12-08 09:49:11 +08:00
];
private const PROVIDED_COMMAND = [
'perl' => '/usr/share/perl5/FindBin.pm' ,
'binutils-gold' => 'ld.gold' ,
'base-devel' => 'automake' ,
'gettext-devel' => 'gettextize' ,
'gettext-dev' => 'gettextize' ,
'perl-IPC-Cmd' => '/usr/share/perl5/vendor_perl/IPC/Cmd.pm' ,
'perl-Time-Piece' => '/usr/lib64/perl5/Time/Piece.pm' ,
];
/** @noinspection PhpUnused */
#[CheckItem('if necessary tools are installed', limit_os: 'Linux', level: 999)]
public function checkCliTools () : ? CheckResult
{
$distro = LinuxUtil :: getOSRelease ();
$required = match ( $distro [ 'dist' ]) {
'alpine' => self :: TOOLS_ALPINE ,
'redhat' => self :: TOOLS_RHEL ,
'centos' => array_merge ( self :: TOOLS_RHEL , [ 'perl-IPC-Cmd' , 'perl-Time-Piece' ]),
2026-04-29 15:53:52 +08:00
'arch' , 'manjaro' => self :: TOOLS_ARCH ,
default => null ,
2025-12-08 09:49:11 +08:00
};
2026-04-29 15:53:52 +08:00
if ( $required === null ) {
// fallback to family-based detection
$family = explode ( ' ' , strtolower ( $distro [ 'family' ]));
$required = match ( true ) {
in_array ( 'alpine' , $family ) => self :: TOOLS_ALPINE ,
in_array ( 'rhel' , $family ) || in_array ( 'fedora' , $family ) => self :: TOOLS_RHEL ,
in_array ( 'arch' , $family ) => self :: TOOLS_ARCH ,
default => self :: TOOLS_DEBIAN ,
};
}
2025-12-08 09:49:11 +08:00
$missing = [];
foreach ( $required as $package ) {
if ( LinuxUtil :: findCommand ( self :: PROVIDED_COMMAND [ $package ] ? ? $package ) === null ) {
$missing [] = $package ;
}
}
if ( ! empty ( $missing )) {
2026-02-06 21:17:19 +08:00
return CheckResult :: fail ( implode ( ', ' , $missing ) . ' not installed on your system' , 'install-linux-tools' , [ 'distro' => $distro , 'missing' => $missing ]);
2025-12-08 09:49:11 +08:00
}
return CheckResult :: ok ();
}
#[CheckItem('if cmake version >= 3.22', limit_os: 'Linux')]
public function checkCMakeVersion () : ? CheckResult
{
$ver = get_cmake_version ();
if ( $ver === null ) {
return CheckResult :: fail ( 'Failed to get cmake version' );
}
if ( version_compare ( $ver , '3.22.0' ) < 0 ) {
return CheckResult :: fail ( 'cmake version is too low (' . $ver . '), please update it manually!' );
}
return CheckResult :: ok ( $ver );
}
/** @noinspection PhpUnused */
#[CheckItem('if necessary linux headers are installed', limit_os: 'Linux')]
public function checkSystemOSPackages () : ? CheckResult
{
if ( LinuxUtil :: isMuslDist ()) {
// check linux-headers installation
if ( ! file_exists ( '/usr/include/linux/mman.h' )) {
2026-02-06 21:17:19 +08:00
return CheckResult :: fail ( 'linux-headers not installed on your system' , 'install-linux-tools' , [ 'distro' => LinuxUtil :: getOSRelease (), 'missing' => [ 'linux-headers' ]]);
2025-12-08 09:49:11 +08:00
}
}
return CheckResult :: ok ();
}
#[FixItem('install-linux-tools')]
public function fixBuildTools ( array $distro , array $missing ) : bool
{
$install_cmd = match ( $distro [ 'dist' ]) {
'ubuntu' , 'debian' , 'Deepin' , 'neon' => 'apt-get install -y' ,
'alpine' => 'apk add' ,
'redhat' => 'dnf install -y' ,
'centos' => 'yum install -y' ,
'arch' => 'pacman -S --noconfirm' ,
default => null ,
};
if ( $install_cmd === null ) {
// try family
$family = explode ( ' ' , strtolower ( $distro [ 'family' ]));
2026-04-29 15:53:52 +08:00
if ( in_array ( 'debian' , $family ) || in_array ( 'ubuntu' , $family )) {
2025-12-08 09:49:11 +08:00
$install_cmd = 'apt-get install -y' ;
} elseif ( in_array ( 'rhel' , $family ) || in_array ( 'fedora' , $family )) {
$install_cmd = 'dnf install -y' ;
2026-04-29 15:53:52 +08:00
} elseif ( in_array ( 'arch' , $family )) {
$install_cmd = 'pacman -S --noconfirm' ;
2025-12-08 09:49:11 +08:00
} else {
throw new EnvironmentException (
" Current linux distro [ { $distro [ 'dist' ] } ] does not have an auto-install script for packages yet. " ,
'You can submit an issue to request support: https://github.com/crazywhalecc/static-php-cli/issues'
);
}
}
$prefix = '' ;
if (( $user = exec ( 'whoami' )) !== 'root' ) {
$prefix = 'sudo ' ;
logger () -> warning ( " Current user ( { $user } ) is not root, using sudo for running command (may require password input) " );
}
$is_debian = LinuxUtil :: isDebianDist ();
$to_install = $is_debian ? str_replace ( 'xz' , 'xz-utils' , $missing ) : $missing ;
// debian, alpine libtool -> libtoolize
$to_install = str_replace ( 'libtoolize' , 'libtool' , $to_install );
2026-02-06 21:17:19 +08:00
f_passthru ( $prefix . $install_cmd . ' ' . implode ( ' ' , $to_install ));
2025-12-08 09:49:11 +08:00
return true ;
}
}