diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index dae38a5d..75a008c1 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -4,8 +4,9 @@ declare(strict_types=1); namespace SPC\builder; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; +use SPC\exception\SPCException; +use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; @@ -30,10 +31,10 @@ class Extension $unix_only = Config::getExt($this->name, 'unix-only', false); $windows_only = Config::getExt($this->name, 'windows-only', false); if (PHP_OS_FAMILY !== 'Windows' && $windows_only) { - throw new RuntimeException("{$ext_type} extension {$name} is not supported on Linux and macOS platform"); + throw new EnvironmentException("{$ext_type} extension {$name} is not supported on Linux and macOS platform"); } if (PHP_OS_FAMILY === 'Windows' && $unix_only) { - throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform"); + throw new EnvironmentException("{$ext_type} extension {$name} is not supported on Windows platform"); } // set source_dir for builtin if ($ext_type === 'builtin') { @@ -41,7 +42,7 @@ class Extension } elseif ($ext_type === 'external') { $source = Config::getExt($this->name, 'source'); if ($source === null) { - throw new RuntimeException("{$ext_type} extension {$name} source not found"); + throw new ValidationException("{$ext_type} extension {$name} source not found", validation_module: "Extension [{$name}] loader"); } $source_path = Config::getSource($source)['path'] ?? null; $source_path = $source_path === null ? SOURCE_PATH . '/' . $source : SOURCE_PATH . '/' . $source_path; @@ -287,13 +288,12 @@ class Extension { // Run compile check if build target is cli // If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php - // If check failed, throw RuntimeException $sharedExtensions = $this->getSharedExtensionLoadString(); [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"'); if ($ret !== 0) { - throw new RuntimeException( - 'extension ' . $this->getName() . ' failed runtime check: php-cli returned ' . $ret . "\n" . - join("\n", $out) + throw new ValidationException( + "extension {$this->getName()} failed compile check: php-cli returned {$ret}", + validation_module: 'Extension ' . $this->getName() . ' sanity check' ); } @@ -307,8 +307,10 @@ class Extension [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' -r "' . trim($test) . '"'); if ($ret !== 0) { - var_dump($out); - throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); + throw new ValidationException( + "extension {$this->getName()} failed sanity check. Code: {$ret}, output: " . implode("\n", $out), + validation_module: 'Extension ' . $this->getName() . ' function check' + ); } } } @@ -317,10 +319,9 @@ class Extension { // Run compile check if build target is cli // If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php - // If check failed, throw RuntimeException [$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -n --ri "' . $this->getDistName() . '"', false); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); + throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: "Extension {$this->getName()} sanity check"); } if (file_exists(FileSystem::convertPath(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php'))) { @@ -333,7 +334,10 @@ class Extension [$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -n -r "' . trim($test) . '"'); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); + throw new ValidationException( + "extension {$this->getName()} failed function check", + validation_module: "Extension {$this->getName()} function check" + ); } } } @@ -348,34 +352,39 @@ class Extension */ public function buildShared(): void { - if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) { + try { + if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) { + if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) { + logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)'); + return; + } + if (Config::getExt($this->getName(), 'build-with-php') === true) { + logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)'); + logger()->warning('Try deleting your build and source folders and running `spc build`` again.'); + return; + } + } if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) { - logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)'); - return; + logger()->info('Shared extension [' . $this->getName() . '] was already built, skipping (' . $this->getName() . '.so)'); } - if (Config::getExt($this->getName(), 'build-with-php') === true) { - logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)'); - logger()->warning('Try deleting your build and source folders and running `spc build`` again.'); - return; + logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)'); + foreach ($this->dependencies as $dependency) { + if (!$dependency instanceof Extension) { + continue; + } + if (!$dependency->isBuildStatic()) { + logger()->info('extension ' . $this->getName() . ' requires extension ' . $dependency->getName()); + $dependency->buildShared(); + } } + match (PHP_OS_FAMILY) { + 'Darwin', 'Linux' => $this->buildUnixShared(), + default => throw new WrongUsageException(PHP_OS_FAMILY . ' build shared extensions is not supported yet'), + }; + } catch (SPCException $e) { + $e->bindExtensionInfo(['extension_name' => $this->getName()]); + throw $e; } - if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) { - logger()->info('Shared extension [' . $this->getName() . '] was already built, skipping (' . $this->getName() . '.so)'); - } - logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)'); - foreach ($this->dependencies as $dependency) { - if (!$dependency instanceof Extension) { - continue; - } - if (!$dependency->isBuildStatic()) { - logger()->info('extension ' . $this->getName() . ' requires extension ' . $dependency->getName()); - $dependency->buildShared(); - } - } - match (PHP_OS_FAMILY) { - 'Darwin', 'Linux' => $this->buildUnixShared(), - default => throw new WrongUsageException(PHP_OS_FAMILY . ' build shared extensions is not supported yet'), - }; } /** @@ -479,7 +488,7 @@ class Extension $depLib = $this->builder->getLib($name); if (!$depLib) { if (!$optional) { - throw new RuntimeException("extension {$this->name} requires library {$name}"); + throw new WrongUsageException("extension {$this->name} requires library {$name}"); } logger()->info("enabling {$this->name} without library {$name}"); } else { @@ -492,7 +501,7 @@ class Extension $depExt = $this->builder->getExt($name); if (!$depExt) { if (!$optional) { - throw new RuntimeException("{$this->name} requires extension {$name}"); + throw new WrongUsageException("{$this->name} requires extension {$name} which is not included"); } logger()->info("enabling {$this->name} without extension {$name}"); } else { diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php index 1e0d287e..c88e8d96 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace SPC\builder; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\SPCException; +use SPC\exception\SPCInternalException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\Downloader; @@ -30,9 +30,9 @@ abstract class LibraryBase public function __construct(?string $source_dir = null) { if (static::NAME === 'unknown') { - throw new RuntimeException('no unknown!!!!!'); + throw new SPCInternalException('Please set the NAME constant in ' . static::class); } - $this->source_dir = $source_dir ?? (SOURCE_PATH . '/' . Config::getLib(static::NAME, 'source')); + $this->source_dir = $source_dir ?? (SOURCE_PATH . DIRECTORY_SEPARATOR . Config::getLib(static::NAME, 'source')); } /** @@ -154,7 +154,7 @@ abstract class LibraryBase FileSystem::extractPackage($install_file, $lock['source_type'], DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH); $this->install(); return LIB_STATUS_OK; - } catch (FileSystemException|RuntimeException $e) { + } catch (SPCException $e) { logger()->error('Failed to extract pre-built library [' . static::NAME . ']: ' . $e->getMessage()); return LIB_STATUS_INSTALL_FAILED; } @@ -304,7 +304,7 @@ abstract class LibraryBase } $replace_items = json_decode(file_get_contents($replace_item_file), true); if (!is_array($replace_items)) { - throw new RuntimeException('Invalid placeholder file: ' . $replace_item_file); + throw new SPCInternalException("Invalid placeholder file: {$replace_item_file}"); } $placeholders = get_pack_replace(); // replace placeholders in BUILD_ROOT_PATH @@ -331,7 +331,7 @@ abstract class LibraryBase return; } if (!$optional) { - throw new RuntimeException(static::NAME . " requires library {$name}"); + throw new WrongUsageException(static::NAME . " requires library {$name} but it is not included"); } logger()->debug('enabling ' . static::NAME . " without {$name}"); } diff --git a/src/SPC/builder/extension/curl.php b/src/SPC/builder/extension/curl.php index 0a9c6a1c..58347601 100644 --- a/src/SPC/builder/extension/curl.php +++ b/src/SPC/builder/extension/curl.php @@ -8,8 +8,7 @@ use SPC\builder\Extension; use SPC\builder\linux\LinuxBuilder; use SPC\builder\macos\MacOSBuilder; use SPC\builder\windows\WindowsBuilder; -use SPC\exception\FileSystemException; -use SPC\exception\WrongUsageException; +use SPC\exception\PatchException; use SPC\store\FileSystem; use SPC\util\CustomExt; @@ -98,7 +97,7 @@ class curl extends Extension ); if ($patched === null) { - throw new \RuntimeException('Failed to patch config.m4 due to a regex error'); + throw new PatchException('shared extension curl patcher', 'Failed to patch config.m4 due to a regex error'); } FileSystem::writeFile($file, $patched); diff --git a/src/SPC/builder/extension/grpc.php b/src/SPC/builder/extension/grpc.php index bda36004..61af6c04 100644 --- a/src/SPC/builder/extension/grpc.php +++ b/src/SPC/builder/extension/grpc.php @@ -6,6 +6,7 @@ namespace SPC\builder\extension; use SPC\builder\Extension; use SPC\builder\windows\WindowsBuilder; +use SPC\exception\ValidationException; use SPC\store\FileSystem; use SPC\util\CustomExt; use SPC\util\GlobalEnvManager; @@ -18,7 +19,7 @@ class grpc extends Extension public function patchBeforeBuildconf(): bool { if ($this->builder instanceof WindowsBuilder) { - throw new \RuntimeException('grpc extension does not support windows yet'); + throw new ValidationException('grpc extension does not support windows yet'); } if (file_exists(SOURCE_PATH . '/php-src/ext/grpc')) { return false; @@ -27,7 +28,7 @@ class grpc extends Extension if (is_dir($this->source_dir . '/src/php/ext/grpc')) { shell()->exec('ln -s ' . $this->source_dir . '/src/php/ext/grpc ' . SOURCE_PATH . '/php-src/ext/grpc'); } else { - throw new \RuntimeException('Cannot find grpc source code'); + throw new ValidationException('Cannot find grpc source code in ' . $this->source_dir . '/src/php/ext/grpc'); } if (SPCTarget::getTargetOS() === 'Darwin') { FileSystem::replaceFileRegex( diff --git a/src/SPC/builder/extension/mbregex.php b/src/SPC/builder/extension/mbregex.php index 0e1cad28..1132eeb2 100644 --- a/src/SPC/builder/extension/mbregex.php +++ b/src/SPC/builder/extension/mbregex.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\util\CustomExt; #[CustomExt('mbregex')] @@ -16,11 +16,6 @@ class mbregex extends Extension return 'mbstring'; } - public function getConfigureArg(bool $shared = false): string - { - return ''; - } - /** * mbregex is not an extension, we need to overwrite the default check. */ @@ -29,7 +24,7 @@ class mbregex extends Extension $sharedext = $this->builder->getExt('mbstring')->isBuildShared() ? '-d "extension_dir=' . BUILD_MODULES_PATH . '" -d "extension=mbstring"' : ''; [$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n' . $sharedext . ' --ri "mbstring" | grep regex', false); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !'); + throw new ValidationException("Extension {$this->getName()} failed compile check: compiled php-cli mbstring extension does not contain regex !"); } } @@ -37,11 +32,11 @@ class mbregex extends Extension { [$ret, $out] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n --ri "mbstring"', false); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli does not contain mbstring !'); + throw new ValidationException("extension {$this->getName()} failed compile check: compiled php-cli does not contain mbstring !"); } $out = implode("\n", $out); if (!str_contains($out, 'regex')) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !'); + throw new ValidationException("extension {$this->getName()} failed compile check: compiled php-cli mbstring extension does not contain regex !"); } } } diff --git a/src/SPC/builder/extension/opentelemetry.php b/src/SPC/builder/extension/opentelemetry.php index 4b3c5aaa..924c9ea2 100644 --- a/src/SPC/builder/extension/opentelemetry.php +++ b/src/SPC/builder/extension/opentelemetry.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; +use SPC\exception\ValidationException; use SPC\store\FileSystem; use SPC\util\CustomExt; use SPC\util\GlobalEnvManager; @@ -15,7 +16,7 @@ class opentelemetry extends Extension public function validate(): void { if ($this->builder->getPHPVersionID() < 80000 && getenv('SPC_SKIP_PHP_VERSION_CHECK') !== 'yes') { - throw new \RuntimeException('The opentelemetry extension requires PHP 8.0 or later'); + throw new ValidationException('The opentelemetry extension requires PHP 8.0 or later'); } } diff --git a/src/SPC/builder/extension/password_argon2.php b/src/SPC/builder/extension/password_argon2.php index 7a3d8e0c..30e6fd2c 100644 --- a/src/SPC/builder/extension/password_argon2.php +++ b/src/SPC/builder/extension/password_argon2.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\util\CustomExt; #[CustomExt('password-argon2')] @@ -20,7 +20,7 @@ class password_argon2 extends Extension { [$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n -r "assert(defined(\'PASSWORD_ARGON2I\'));"'); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); + throw new ValidationException('extension ' . $this->getName() . ' failed sanity check', validation_module: 'password_argon2 function check'); } } diff --git a/src/SPC/builder/extension/protobuf.php b/src/SPC/builder/extension/protobuf.php index 0aa6ed51..fd84dfec 100644 --- a/src/SPC/builder/extension/protobuf.php +++ b/src/SPC/builder/extension/protobuf.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; +use SPC\exception\ValidationException; use SPC\util\CustomExt; #[CustomExt('protobuf')] @@ -13,12 +14,12 @@ class protobuf extends Extension public function validate(): void { if ($this->builder->getPHPVersionID() < 80000 && getenv('SPC_SKIP_PHP_VERSION_CHECK') !== 'yes') { - throw new \RuntimeException('The latest protobuf extension requires PHP 8.0 or later'); + throw new ValidationException('The latest protobuf extension requires PHP 8.0 or later'); } $grpc = $this->builder->getExt('grpc'); // protobuf conflicts with grpc if ($grpc?->isBuildStatic()) { - throw new \RuntimeException('protobuf conflicts with grpc, please remove grpc or protobuf extension'); + throw new ValidationException('protobuf conflicts with grpc, please remove grpc or protobuf extension'); } } } diff --git a/src/SPC/builder/extension/swoole_hook_mysql.php b/src/SPC/builder/extension/swoole_hook_mysql.php index b45516ee..273e8ec0 100644 --- a/src/SPC/builder/extension/swoole_hook_mysql.php +++ b/src/SPC/builder/extension/swoole_hook_mysql.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\util\CustomExt; #[CustomExt('swoole-hook-mysql')] @@ -32,10 +32,10 @@ class swoole_hook_mysql extends Extension [$ret, $out] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -n' . $this->getSharedExtensionLoadString() . ' --ri "swoole"', false); $out = implode('', $out); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); + throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: 'extension swoole_hook_mysql sanity check'); } if (!str_contains($out, 'mysqlnd')) { - throw new RuntimeException('swoole mysql hook is not enabled correctly.'); + throw new ValidationException('swoole mysql hook is not enabled correctly.', validation_module: 'Extension swoole mysql hook avilability check'); } } } diff --git a/src/SPC/builder/extension/swoole_hook_pgsql.php b/src/SPC/builder/extension/swoole_hook_pgsql.php index dfbf7dc8..9849de7b 100644 --- a/src/SPC/builder/extension/swoole_hook_pgsql.php +++ b/src/SPC/builder/extension/swoole_hook_pgsql.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; use SPC\util\CustomExt; @@ -41,10 +41,16 @@ class swoole_hook_pgsql extends Extension [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"'); $out = implode('', $out); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); + throw new ValidationException( + "extension {$this->getName()} failed sanity check: php-cli returned {$ret}", + validation_module: 'Extension swoole-hook-pgsql sanity check' + ); } if (!str_contains($out, 'coroutine_pgsql')) { - throw new RuntimeException('swoole pgsql hook is not enabled correctly.'); + throw new ValidationException( + 'swoole pgsql hook is not enabled correctly.', + validation_module: 'Extension swoole pgsql hook availability check' + ); } } } diff --git a/src/SPC/builder/extension/swoole_hook_sqlite.php b/src/SPC/builder/extension/swoole_hook_sqlite.php index 29e9ef84..1a8fff6d 100644 --- a/src/SPC/builder/extension/swoole_hook_sqlite.php +++ b/src/SPC/builder/extension/swoole_hook_sqlite.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; use SPC\util\CustomExt; @@ -41,10 +41,10 @@ class swoole_hook_sqlite extends Extension [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"'); $out = implode('', $out); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); + throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: "Extension {$this->getName()} sanity check"); } if (!str_contains($out, 'coroutine_sqlite')) { - throw new RuntimeException('swoole sqlite hook is not enabled correctly.'); + throw new ValidationException('swoole sqlite hook is not enabled correctly.', validation_module: 'Extension swoole sqlite hook availability check'); } } } diff --git a/src/SPC/builder/extension/swow.php b/src/SPC/builder/extension/swow.php index 77f67d26..e2a5cbad 100644 --- a/src/SPC/builder/extension/swow.php +++ b/src/SPC/builder/extension/swow.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\ValidationException; use SPC\util\CustomExt; #[CustomExt('swow')] @@ -14,7 +14,7 @@ class swow extends Extension public function validate(): void { if ($this->builder->getPHPVersionID() < 80000 && getenv('SPC_SKIP_PHP_VERSION_CHECK') !== 'yes') { - throw new RuntimeException('The latest swow extension requires PHP 8.0 or later'); + throw new ValidationException('The latest swow extension requires PHP 8.0 or later'); } } diff --git a/src/SPC/builder/extension/uv.php b/src/SPC/builder/extension/uv.php index d803a750..0834578f 100644 --- a/src/SPC/builder/extension/uv.php +++ b/src/SPC/builder/extension/uv.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; +use SPC\exception\ValidationException; use SPC\store\FileSystem; use SPC\util\CustomExt; @@ -14,7 +15,7 @@ class uv extends Extension public function validate(): void { if ($this->builder->getPHPVersionID() < 80000 && getenv('SPC_SKIP_PHP_VERSION_CHECK') !== 'yes') { - throw new \RuntimeException('The latest uv extension requires PHP 8.0 or later'); + throw new ValidationException('The latest uv extension requires PHP 8.0 or later'); } } diff --git a/src/SPC/builder/extension/xml.php b/src/SPC/builder/extension/xml.php index f4b5f569..a111ed39 100644 --- a/src/SPC/builder/extension/xml.php +++ b/src/SPC/builder/extension/xml.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; use SPC\store\FileSystem; use SPC\util\CustomExt; @@ -24,7 +24,7 @@ class xml extends Extension 'xmlreader' => '--enable-xmlreader', 'xmlwriter' => '--enable-xmlwriter', 'simplexml' => '--enable-simplexml', - default => throw new RuntimeException('Not accept non-xml extension'), + default => throw new SPCInternalException('Not accept non-xml extension'), }; $arg .= ($shared ? '=shared' : '') . ' --with-libxml="' . BUILD_ROOT_PATH . '"'; return $arg; @@ -44,7 +44,7 @@ class xml extends Extension 'xmlreader' => '--enable-xmlreader', 'xmlwriter' => '--enable-xmlwriter', 'simplexml' => '--with-simplexml', - default => throw new RuntimeException('Not accept non-xml extension'), + default => throw new SPCInternalException('Not accept non-xml extension'), }; $arg .= ' --with-libxml'; return $arg; diff --git a/src/SPC/builder/freebsd/SystemUtil.php b/src/SPC/builder/freebsd/SystemUtil.php index 782586e2..cbc206b9 100644 --- a/src/SPC/builder/freebsd/SystemUtil.php +++ b/src/SPC/builder/freebsd/SystemUtil.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\freebsd; use SPC\builder\traits\UnixSystemUtilTrait; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; use SPC\exception\WrongUsageException; class SystemUtil @@ -20,7 +20,10 @@ class SystemUtil { [$ret, $output] = shell()->execWithResult('sysctl -n hw.ncpu'); if ($ret !== 0) { - throw new RuntimeException('Failed to get cpu count'); + throw new EnvironmentException( + 'Failed to get cpu count from FreeBSD sysctl', + 'Please ensure you are running this command on a FreeBSD system and have the sysctl command available.' + ); } return (int) $output[0]; diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 8eb766f2..e7736852 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -358,7 +358,7 @@ class LinuxBuilder extends UnixBuilderBase $out[1] = explode(' ', $out[1]); $offset = $out[1][0]; if ($ret !== 0 || !str_starts_with($offset, '0x')) { - throw new RuntimeException('Cannot find offset in readelf output'); + throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); } $offset = hexdec($offset); // remove upx extra wastes diff --git a/src/SPC/builder/macos/SystemUtil.php b/src/SPC/builder/macos/SystemUtil.php index 74846ecf..a75a78a1 100644 --- a/src/SPC/builder/macos/SystemUtil.php +++ b/src/SPC/builder/macos/SystemUtil.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\builder\macos; use SPC\builder\traits\UnixSystemUtilTrait; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; use SPC\exception\WrongUsageException; class SystemUtil @@ -18,12 +18,15 @@ class SystemUtil */ public static function getCpuCount(): int { - [$ret, $output] = shell()->execWithResult('sysctl -n hw.ncpu', false); + $cpu = exec('sysctl -n hw.ncpu', $output, $ret); if ($ret !== 0) { - throw new RuntimeException('Failed to get cpu count'); + 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) $output[0]; + return (int) $cpu; } /** diff --git a/src/SPC/builder/traits/UnixLibraryTrait.php b/src/SPC/builder/traits/UnixLibraryTrait.php index 739d16a9..868cf6f7 100644 --- a/src/SPC/builder/traits/UnixLibraryTrait.php +++ b/src/SPC/builder/traits/UnixLibraryTrait.php @@ -4,9 +4,7 @@ declare(strict_types=1); namespace SPC\builder\traits; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; +use SPC\exception\PatchException; use SPC\store\Config; use SPC\store\FileSystem; use SPC\util\SPCConfigUtil; @@ -38,7 +36,7 @@ trait UnixLibraryTrait foreach ($files as $name) { $realpath = realpath(BUILD_ROOT_PATH . '/lib/pkgconfig/' . $name); if ($realpath === false) { - throw new RuntimeException('Cannot find library [' . static::NAME . '] pkgconfig file [' . $name . '] !'); + throw new PatchException('pkg-config prefix patcher', 'Cannot find library [' . static::NAME . '] pkgconfig file [' . $name . '] in ' . BUILD_LIB_PATH . '/pkgconfig/ !'); } logger()->debug('Patching ' . $realpath); // replace prefix @@ -65,7 +63,7 @@ trait UnixLibraryTrait $realpath = realpath(BUILD_LIB_PATH . '/' . $name); if ($realpath === false) { if ($throwOnMissing) { - throw new RuntimeException('Cannot find library [' . static::NAME . '] la file [' . $name . '] !'); + throw new PatchException('la dependency patcher', 'Cannot find library [' . static::NAME . '] la file [' . $name . '] !'); } logger()->warning('Cannot find library [' . static::NAME . '] la file [' . $name . '] !'); continue; diff --git a/src/SPC/builder/traits/openssl.php b/src/SPC/builder/traits/openssl.php index 47becc99..6ffef33a 100644 --- a/src/SPC/builder/traits/openssl.php +++ b/src/SPC/builder/traits/openssl.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace SPC\builder\traits; -use SPC\exception\RuntimeException; +use SPC\exception\SPCException; use SPC\store\FileSystem; use SPC\util\PkgConfigUtil; @@ -24,7 +24,7 @@ trait openssl if (PHP_OS_FAMILY !== 'Windows') { try { return PkgConfigUtil::getModuleVersion('openssl'); - } catch (RuntimeException) { + } catch (SPCException) { } } // get openssl version from header openssl/opensslv.h diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 73cb4e07..d9a72c21 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace SPC\builder\unix; use SPC\builder\BuilderBase; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; +use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\CurlHook; @@ -56,7 +56,14 @@ abstract class UnixBuilderBase extends BuilderBase } // if some libs are not supported (but in config "lib.json", throw exception) if (!isset($support_lib_list[$library])) { - throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!'); + $os = match (PHP_OS_FAMILY) { + 'Linux' => 'Linux', + 'Darwin' => 'macOS', + 'Windows' => 'Windows', + 'BSD' => 'FreeBSD', + default => PHP_OS_FAMILY, + }; + throw new WrongUsageException("library [{$library}] is in the lib.json list but not supported to build on {$os}."); } $lib = new ($support_lib_list[$library])($this); $this->addLib($lib); @@ -80,7 +87,7 @@ abstract class UnixBuilderBase extends BuilderBase [$ret, $output] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n -r "echo \"hello\";"'); $raw_output = implode('', $output); if ($ret !== 0 || trim($raw_output) !== 'hello') { - throw new RuntimeException("cli failed sanity check: ret[{$ret}]. out[{$raw_output}]"); + throw new ValidationException("cli failed sanity check. code: {$ret}, output: {$raw_output}", validation_module: 'php-cli sanity check'); } foreach ($this->getExts() as $ext) { @@ -103,7 +110,10 @@ abstract class UnixBuilderBase extends BuilderBase foreach ($task['conditions'] as $condition => $closure) { if (!$closure($ret, $out)) { $raw_out = trim(implode('', $out)); - throw new RuntimeException("micro failed sanity check: {$task_name}, condition [{$condition}], ret[{$ret}], out[{$raw_out}]"); + throw new ValidationException( + "failure info: {$condition}, code: {$ret}, output: {$raw_out}", + validation_module: "phpmicro sanity check item [{$task_name}]" + ); } } } @@ -142,11 +152,17 @@ abstract class UnixBuilderBase extends BuilderBase } [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); if ($ret !== 0) { - throw new RuntimeException('embed failed sanity check: build failed. Error message: ' . implode("\n", $out)); + throw new ValidationException( + 'embed failed sanity check: build failed. Error message: ' . implode("\n", $out), + validation_module: 'static libphp.a sanity check' + ); } [$ret, $output] = shell()->cd($sample_file_path)->execWithResult($ext_path . './embed'); if ($ret !== 0 || trim(implode('', $output)) !== 'hello') { - throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output)); + throw new ValidationException( + 'embed failed sanity check: run failed. Error message: ' . implode("\n", $output), + validation_module: 'static libphp.a sanity check' + ); } } @@ -155,14 +171,20 @@ abstract class UnixBuilderBase extends BuilderBase logger()->info('running frankenphp sanity check'); $frankenphp = BUILD_BIN_PATH . '/frankenphp'; if (!file_exists($frankenphp)) { - throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp); + throw new ValidationException( + "FrankenPHP binary not found: {$frankenphp}", + validation_module: 'FrankenPHP sanity check' + ); } $prefix = PHP_OS_FAMILY === 'Darwin' ? 'DYLD_' : 'LD_'; [$ret, $output] = shell() ->setEnv(["{$prefix}LIBRARY_PATH" => BUILD_LIB_PATH]) ->execWithResult("{$frankenphp} version"); if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) { - throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']'); + throw new ValidationException( + 'FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']', + validation_module: 'FrankenPHP sanity check' + ); } } } @@ -178,7 +200,7 @@ abstract class UnixBuilderBase extends BuilderBase BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php', BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm', - default => throw new RuntimeException('Deployment does not accept type ' . $type), + default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); FileSystem::createDir(BUILD_BIN_PATH); @@ -191,7 +213,7 @@ abstract class UnixBuilderBase extends BuilderBase */ protected function cleanMake(): void { - logger()->info('cleaning up'); + logger()->info('cleaning up php-src build files'); shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean'); } diff --git a/src/SPC/builder/unix/library/fastlz.php b/src/SPC/builder/unix/library/fastlz.php index db0b7f79..49466475 100644 --- a/src/SPC/builder/unix/library/fastlz.php +++ b/src/SPC/builder/unix/library/fastlz.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace SPC\builder\unix\library; +use SPC\exception\BuildFailureException; + trait fastlz { protected function build(): void @@ -13,10 +15,10 @@ trait fastlz ->exec((getenv('AR') ?: 'ar') . ' rcs libfastlz.a fastlz.o'); if (!copy($this->source_dir . '/fastlz.h', BUILD_INCLUDE_PATH . '/fastlz.h')) { - throw new \RuntimeException('Failed to copy fastlz.h'); + throw new BuildFailureException('Failed to copy fastlz.h, file does not exist'); } if (!copy($this->source_dir . '/libfastlz.a', BUILD_LIB_PATH . '/libfastlz.a')) { - throw new \RuntimeException('Failed to copy libfastlz.a'); + throw new BuildFailureException('Failed to copy libfastlz.a, file does not exist'); } } } diff --git a/src/SPC/builder/unix/library/postgresql.php b/src/SPC/builder/unix/library/postgresql.php index 80564284..3bc6835d 100644 --- a/src/SPC/builder/unix/library/postgresql.php +++ b/src/SPC/builder/unix/library/postgresql.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace SPC\builder\unix\library; use SPC\builder\linux\library\LinuxLibraryBase; +use SPC\exception\BuildFailureException; use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; use SPC\store\FileSystem; use SPC\util\SPCTarget; @@ -60,7 +60,7 @@ trait postgresql $envs .= " LIBS=\"{$libs}{$libcpp}\" "; } if ($error_exec_cnt > 0) { - throw new RuntimeException('Failed to get pkg-config information!'); + throw new BuildFailureException('Failed to get pkg-config information!'); } FileSystem::resetDir($this->source_dir . '/build'); @@ -74,7 +74,7 @@ trait postgresql ->exec('sed -i.backup "278 s/^/# /" ../src/Makefile.shlib') ->exec('sed -i.backup "402 s/^/# /" ../src/Makefile.shlib'); } else { - throw new RuntimeException('Unsupported version for postgresql: ' . $version . ' !'); + throw new BuildFailureException('Unsupported version for postgresql: ' . $version . ' !'); } // configure diff --git a/src/SPC/builder/windows/SystemUtil.php b/src/SPC/builder/windows/SystemUtil.php index c95697bd..3a0c23ae 100644 --- a/src/SPC/builder/windows/SystemUtil.php +++ b/src/SPC/builder/windows/SystemUtil.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace SPC\builder\windows; -use SPC\exception\FileSystemException; use SPC\store\FileSystem; class SystemUtil diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 2e7c1154..3f21f639 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace SPC\builder\windows; use SPC\builder\BuilderBase; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; +use SPC\exception\ValidationException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; @@ -267,7 +267,7 @@ class WindowsBuilder extends BuilderBase logger()->info('running cli sanity check'); [$ret, $output] = cmd()->execWithResult(BUILD_ROOT_PATH . '\bin\php.exe -n -r "echo \"hello\";"'); if ($ret !== 0 || trim(implode('', $output)) !== 'hello') { - throw new RuntimeException('cli failed sanity check'); + throw new ValidationException('cli failed sanity check', validation_module: 'php-cli function check'); } foreach ($this->getExts(false) as $ext) { @@ -290,7 +290,10 @@ class WindowsBuilder extends BuilderBase foreach ($task['conditions'] as $condition => $closure) { if (!$closure($ret, $out)) { $raw_out = trim(implode('', $out)); - throw new RuntimeException("micro failed sanity check: {$task_name}, condition [{$condition}], ret[{$ret}], out[{$raw_out}]"); + throw new ValidationException( + "failure info: {$condition}, code: {$ret}, output: {$raw_out}", + validation_module: "phpmicro sanity check item [{$task_name}]" + ); } } } @@ -308,7 +311,7 @@ class WindowsBuilder extends BuilderBase $src = match ($type) { BUILD_TARGET_CLI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php.exe", BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx", - default => throw new RuntimeException('Deployment does not accept type ' . $type), + default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; // with-upx-pack for cli and micro diff --git a/src/SPC/builder/windows/library/libsodium.php b/src/SPC/builder/windows/library/libsodium.php index 95c35945..c2ddb9ac 100644 --- a/src/SPC/builder/windows/library/libsodium.php +++ b/src/SPC/builder/windows/library/libsodium.php @@ -5,24 +5,35 @@ declare(strict_types=1); namespace SPC\builder\windows\library; use SPC\builder\windows\SystemUtil; -use SPC\exception\RuntimeException; +use SPC\exception\BuildFailureException; +use SPC\exception\EnvironmentException; use SPC\store\FileSystem; class libsodium extends WindowsLibraryBase { public const NAME = 'libsodium'; - protected function build() + private string $vs_ver_dir; + + public function validate(): void { - FileSystem::replaceFileStr($this->source_dir . '\src\libsodium\include\sodium\export.h', '#ifdef SODIUM_STATIC', '#if 1'); - $vs_ver_dir = match (SystemUtil::findVisualStudio()['version']) { + $this->vs_ver_dir = match ($ver = SystemUtil::findVisualStudio()['version']) { 'vs17' => '\vs2022', 'vs16' => '\vs2019', - default => throw new RuntimeException('Current VS version is not supported yet!'), + default => throw new EnvironmentException("Current VS version {$ver} is not supported yet!"), }; + } + public function patchBeforeBuild(): bool + { + FileSystem::replaceFileStr($this->source_dir . '\src\libsodium\include\sodium\export.h', '#ifdef SODIUM_STATIC', '#if 1'); + return true; + } + + protected function build(): void + { // start build - cmd()->cd($this->source_dir . '\builds\msvc\\' . $vs_ver_dir) + cmd()->cd($this->source_dir . '\builds\msvc' . $this->vs_ver_dir) ->execWithWrapper( $this->builder->makeSimpleWrapper('msbuild'), 'libsodium.sln /t:Rebuild /p:Configuration=StaticRelease /p:Platform=x64 /p:PreprocessorDefinitions="SODIUM_STATIC=1"' @@ -46,7 +57,7 @@ class libsodium extends WindowsLibraryBase } } if (!$find) { - throw new RuntimeException('libsodium.lib not found'); + throw new BuildFailureException("Build libsodium success, but cannot find libsodium.lib in {$this->source_dir}\\bin ."); } } } diff --git a/src/SPC/builder/windows/library/openssl.php b/src/SPC/builder/windows/library/openssl.php index 09c0068b..491a33b4 100644 --- a/src/SPC/builder/windows/library/openssl.php +++ b/src/SPC/builder/windows/library/openssl.php @@ -5,23 +5,33 @@ declare(strict_types=1); namespace SPC\builder\windows\library; use SPC\builder\windows\SystemUtil; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; use SPC\store\FileSystem; class openssl extends WindowsLibraryBase { public const NAME = 'openssl'; + private ?string $perl; + + public function validate(): void + { + global $argv; + $perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe'; + $this->perl = file_exists($perl_path_native) ? ($perl_path_native) : SystemUtil::findCommand('perl.exe'); + if ($this->perl === null) { + throw new EnvironmentException( + 'You need to install perl first!', + "Please run \"{$argv[0]} doctor\" to fix the environment.", + ); + } + } + protected function build(): void { - $perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe'; - $perl = file_exists($perl_path_native) ? ($perl_path_native) : SystemUtil::findCommand('perl.exe'); - if ($perl === null) { - throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")'); - } cmd()->cd($this->source_dir) ->execWithWrapper( - $this->builder->makeSimpleWrapper($perl), + $this->builder->makeSimpleWrapper($this->perl), 'Configure zlib VC-WIN64A ' . 'no-shared ' . '--prefix=' . quote(BUILD_ROOT_PATH) . ' ' . diff --git a/src/SPC/doctor/item/LinuxMuslCheck.php b/src/SPC/doctor/item/LinuxMuslCheck.php index a52b32fd..76d59e23 100644 --- a/src/SPC/doctor/item/LinuxMuslCheck.php +++ b/src/SPC/doctor/item/LinuxMuslCheck.php @@ -9,10 +9,6 @@ use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; use SPC\doctor\CheckResult; use SPC\doctor\OptionalCheck; -use SPC\exception\DownloaderException; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\store\PackageManager; @@ -58,55 +54,47 @@ class LinuxMuslCheck #[AsFixItem('fix-musl-wrapper')] public function fixMusl(): bool { - try { - $prefix = ''; - if (get_current_user() !== 'root') { - $prefix = 'sudo '; - logger()->warning('Current user is not root, using sudo for running command'); - } - // The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`. - $musl_version_name = 'musl-1.2.5'; - $musl_source = [ - 'type' => 'url', - 'url' => "https://musl.libc.org/releases/{$musl_version_name}.tar.gz", - ]; - logger()->info('Downloading ' . $musl_source['url']); - Downloader::downloadSource($musl_version_name, $musl_source); - FileSystem::extractSource($musl_version_name, SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz"); - - // Apply CVE-2025-26519 patch - SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0001.patch', SOURCE_PATH . "/{$musl_version_name}"); - SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0002.patch', SOURCE_PATH . "/{$musl_version_name}"); - logger()->info('Installing musl wrapper'); - shell()->cd(SOURCE_PATH . "/{$musl_version_name}") - ->exec('CC=gcc CXX=g++ AR=ar LD=ld ./configure --disable-gcc-wrapper') - ->exec('CC=gcc CXX=g++ AR=ar LD=ld make -j') - ->exec("CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install"); - // TODO: add path using putenv instead of editing /etc/profile - return true; - } catch (RuntimeException) { - return false; + $prefix = ''; + if (get_current_user() !== 'root') { + $prefix = 'sudo '; + logger()->warning('Current user is not root, using sudo for running command'); } + // The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`. + $musl_version_name = 'musl-1.2.5'; + $musl_source = [ + 'type' => 'url', + 'url' => "https://musl.libc.org/releases/{$musl_version_name}.tar.gz", + ]; + logger()->info('Downloading ' . $musl_source['url']); + Downloader::downloadSource($musl_version_name, $musl_source); + FileSystem::extractSource($musl_version_name, SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz"); + + // Apply CVE-2025-26519 patch + SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0001.patch', SOURCE_PATH . "/{$musl_version_name}"); + SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0002.patch', SOURCE_PATH . "/{$musl_version_name}"); + logger()->info('Installing musl wrapper'); + shell()->cd(SOURCE_PATH . "/{$musl_version_name}") + ->exec('CC=gcc CXX=g++ AR=ar LD=ld ./configure --disable-gcc-wrapper') + ->exec('CC=gcc CXX=g++ AR=ar LD=ld make -j') + ->exec("CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install"); + // TODO: add path using putenv instead of editing /etc/profile + return true; } #[AsFixItem('fix-musl-cross-make')] public function fixMuslCrossMake(): bool { - try { - $prefix = ''; - if (get_current_user() !== 'root') { - $prefix = 'sudo '; - logger()->warning('Current user is not root, using sudo for running command'); - } - $arch = arch2gnu(php_uname('m')); - logger()->info("Downloading package musl-toolchain-{$arch}-linux"); - PackageManager::installPackage("musl-toolchain-{$arch}-linux"); - $pkg_root = PKG_ROOT_PATH . "/musl-toolchain-{$arch}-linux"; - shell()->exec("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl"); - FileSystem::removeDir($pkg_root); - return true; - } catch (RuntimeException) { - return false; + $prefix = ''; + if (get_current_user() !== 'root') { + $prefix = 'sudo '; + logger()->warning('Current user is not root, using sudo for running command'); } + $arch = arch2gnu(php_uname('m')); + logger()->info("Downloading package musl-toolchain-{$arch}-linux"); + PackageManager::installPackage("musl-toolchain-{$arch}-linux"); + $pkg_root = PKG_ROOT_PATH . "/musl-toolchain-{$arch}-linux"; + shell()->exec("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl"); + FileSystem::removeDir($pkg_root); + return true; } } diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php index 5db566fa..c07a32da 100644 --- a/src/SPC/doctor/item/LinuxToolCheckList.php +++ b/src/SPC/doctor/item/LinuxToolCheckList.php @@ -9,7 +9,7 @@ use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; use SPC\doctor\CheckResult; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; class LinuxToolCheckList { @@ -74,16 +74,7 @@ class LinuxToolCheckList } } if (!empty($missing)) { - return match ($distro['dist']) { - 'ubuntu', - 'alpine', - 'redhat', - 'centos', - 'Deepin', - 'arch', - 'debian' => CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', [$distro, $missing]), - default => CheckResult::fail(implode(', ', $missing) . ' not installed on your system'), - }; + return CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', [$distro, $missing]); } return CheckResult::ok(); } @@ -123,22 +114,23 @@ class LinuxToolCheckList 'redhat' => 'dnf install -y', 'centos' => 'yum install -y', 'arch' => 'pacman -S --noconfirm', - default => throw new RuntimeException('Current linux distro does not have an auto-install script for musl packages yet.'), + default => 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'); - } - try { - $is_debian = in_array($distro['dist'], ['debian', 'ubuntu', 'Deepin']); - $to_install = $is_debian ? str_replace('xz', 'xz-utils', $missing) : $missing; - // debian, alpine libtool -> libtoolize - $to_install = str_replace('libtoolize', 'libtool', $to_install); - shell(true)->exec($prefix . $install_cmd . ' ' . implode(' ', $to_install)); - } catch (RuntimeException) { - return false; + logger()->warning('Current user (' . $user . ') is not root, using sudo for running command (may require password input)'); } + + $is_debian = in_array($distro['dist'], ['debian', 'ubuntu', 'Deepin']); + $to_install = $is_debian ? str_replace('xz', 'xz-utils', $missing) : $missing; + // debian, alpine libtool -> libtoolize + $to_install = str_replace('libtoolize', 'libtool', $to_install); + shell(true)->exec($prefix . $install_cmd . ' ' . implode(' ', $to_install)); + return true; } } diff --git a/src/SPC/doctor/item/MacOSToolCheckList.php b/src/SPC/doctor/item/MacOSToolCheckList.php index 249b618e..7cc9ca2a 100644 --- a/src/SPC/doctor/item/MacOSToolCheckList.php +++ b/src/SPC/doctor/item/MacOSToolCheckList.php @@ -8,7 +8,6 @@ use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; use SPC\doctor\CheckResult; -use SPC\exception\RuntimeException; class MacOSToolCheckList { @@ -89,11 +88,7 @@ class MacOSToolCheckList #[AsFixItem('brew')] public function fixBrew(): bool { - try { - shell(true)->exec('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'); - } catch (RuntimeException) { - return false; - } + shell(true)->exec('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'); return true; } @@ -104,14 +99,10 @@ class MacOSToolCheckList 'glibtoolize' => 'libtool', ]; foreach ($missing as $cmd) { - try { - if (isset($replacement[$cmd])) { - $cmd = $replacement[$cmd]; - } - shell(true)->exec('brew install --formula ' . escapeshellarg($cmd)); - } catch (RuntimeException) { - return false; + if (isset($replacement[$cmd])) { + $cmd = $replacement[$cmd]; } + shell(true)->exec('brew install --formula ' . escapeshellarg($cmd)); } return true; } diff --git a/src/SPC/doctor/item/WindowsToolCheckList.php b/src/SPC/doctor/item/WindowsToolCheckList.php index bfeb063e..7f71f10e 100644 --- a/src/SPC/doctor/item/WindowsToolCheckList.php +++ b/src/SPC/doctor/item/WindowsToolCheckList.php @@ -8,7 +8,6 @@ use SPC\builder\windows\SystemUtil; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; use SPC\doctor\CheckResult; -use SPC\exception\RuntimeException; use SPC\store\FileSystem; use SPC\store\PackageManager; @@ -80,12 +79,8 @@ class WindowsToolCheckList #[AsFixItem('install-php-sdk')] public function installPhpSdk(): bool { - try { - FileSystem::removeDir(getenv('PHP_SDK_PATH')); - cmd(true)->exec('git.exe clone --depth 1 https://github.com/php/php-sdk-binary-tools.git ' . getenv('PHP_SDK_PATH')); - } catch (RuntimeException) { - return false; - } + FileSystem::removeDir(getenv('PHP_SDK_PATH')); + cmd(true)->exec('git.exe clone --depth 1 https://github.com/php/php-sdk-binary-tools.git ' . getenv('PHP_SDK_PATH')); return true; } diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 91c17df5..d659253e 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace SPC\store; use SPC\exception\DownloaderException; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; +use SPC\exception\InterruptException; +use SPC\exception\SPCException; use SPC\store\pkg\CustomPackage; use SPC\store\source\CustomSourceBase; use SPC\util\SPCTarget; @@ -201,9 +200,9 @@ class Downloader unlink(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename)); } }; - self::registerCancelEvent($cancel_func); + keyboard_interrupt_register($cancel_func); self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"), headers: $headers, hooks: $hooks, retries: self::getRetryAttempts()); - self::unregisterCancelEvent(); + keyboard_interrupt_unregister(); logger()->debug("Locking {$filename}"); if ($download_as === SPC_DOWNLOAD_PRE_BUILT) { $name = self::getPreBuiltLockName($name); @@ -248,12 +247,12 @@ class Downloader f_passthru("cd \"{$download_path}\" && {$git} submodule update --init " . escapeshellarg($submodule)); } } - } catch (RuntimeException $e) { + } catch (SPCException $e) { if (is_dir($download_path)) { FileSystem::removeDir($download_path); } if ($e->getCode() === 2 || $e->getCode() === -1073741510) { - throw new WrongUsageException('Keyboard interrupted, download failed !'); + throw new InterruptException('Keyboard interrupted, download failed !'); } if ($retries > 0) { self::downloadGit($name, $url, $branch, $submodules, $move_path, $retries - 1, $lock_as); @@ -385,7 +384,7 @@ class Downloader default: throw new DownloaderException('unknown source type: ' . $pkg['type']); } - } catch (RuntimeException $e) { + } catch (\Throwable $e) { // Because sometimes files downloaded through the command line are not automatically deleted after a failure. // Here we need to manually delete the file if it is detected to exist. if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) { @@ -503,7 +502,7 @@ class Downloader default: throw new DownloaderException('unknown source type: ' . $source['type']); } - } catch (RuntimeException $e) { + } catch (\Throwable $e) { // Because sometimes files downloaded through the command line are not automatically deleted after a failure. // Here we need to manually delete the file if it is detected to exist. if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) { @@ -551,7 +550,7 @@ class Downloader } f_exec($cmd, $output, $ret); if ($ret === 2 || $ret === -1073741510) { - throw new RuntimeException(sprintf('Failed to fetch "%s"', $url)); + throw new InterruptException(sprintf('Canceled fetching "%s"', $url)); } if ($ret !== 0) { throw new DownloaderException(sprintf('Failed to fetch "%s"', $url)); @@ -563,7 +562,7 @@ class Downloader } f_exec($cmd, $output, $ret); if ($ret === 2 || $ret === -1073741510) { - throw new RuntimeException(sprintf('Failed to fetch "%s"', $url)); + throw new InterruptException(sprintf('Canceled fetching "%s"', $url)); } if ($ret !== 0) { throw new DownloaderException(sprintf('Failed to fetch "%s"', $url)); @@ -599,9 +598,9 @@ class Downloader $cmd = SPC_CURL_EXEC . " -{$check}fSL {$retry} -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\""; try { f_passthru($cmd); - } catch (RuntimeException $e) { + } catch (\Throwable $e) { if ($e->getCode() === 2 || $e->getCode() === -1073741510) { - throw new WrongUsageException('Keyboard interrupted, download failed !'); + throw new InterruptException('Keyboard interrupted, download failed !'); } throw $e; } @@ -639,11 +638,11 @@ class Downloader $url = "https://dl.static-php.dev/static-php-cli/deps/spc-download-mirror/{$source_name}/?format=json"; $json = json_decode(Downloader::curlExec(url: $url, retries: intval(getenv('SPC_DOWNLOAD_RETRIES') ?: 0)), true); if (!is_array($json)) { - throw new RuntimeException('failed http fetch'); + throw new DownloaderException('failed http fetch'); } $item = $json[0] ?? null; if ($item === null) { - throw new RuntimeException('failed to parse json'); + throw new DownloaderException('failed to parse json'); } $full_url = 'https://dl.static-php.dev' . $item['full_path']; $filename = basename($item['full_path']); diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 1849fb4b..a40f5f01 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\store; use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\SPCException; class FileSystem { @@ -147,6 +147,7 @@ class FileSystem */ public static function copyDir(string $from, string $to): void { + logger()->debug("Copying directory from {$from} to {$to}"); $dst_path = FileSystem::convertPath($to); $src_path = FileSystem::convertPath($from); switch (PHP_OS_FAMILY) { @@ -161,6 +162,23 @@ class FileSystem } } + /** + * Copy file from one location to another. + * This method will throw an exception if the copy operation fails. + * + * @param string $from Source file path + * @param string $to Destination file path + */ + public static function copy(string $from, string $to): void + { + logger()->debug("Copying file from {$from} to {$to}"); + $dst_path = FileSystem::convertPath($to); + $src_path = FileSystem::convertPath($from); + if (!copy($src_path, $dst_path)) { + throw new FileSystemException('Cannot copy file from ' . $src_path . ' to ' . $dst_path); + } + } + /** * Extract package archive to specified directory * @@ -187,13 +205,13 @@ class FileSystem try { // extract wrapper command self::extractWithType($source_type, $filename, $extract_path); - } catch (RuntimeException $e) { + } catch (SPCException $e) { if (PHP_OS_FAMILY === 'Windows') { f_passthru('rmdir /s /q ' . $target); } else { f_passthru('rm -rf ' . $target); } - throw new FileSystemException('Cannot extract package ' . $name, $e->getCode(), $e); + throw new FileSystemException("Cannot extract package {$name}", $e->getCode(), $e); } } @@ -223,7 +241,7 @@ class FileSystem try { self::extractWithType($source_type, $filename, $move_path); self::emitSourceExtractHook($name, $target); - } catch (RuntimeException $e) { + } catch (SPCException $e) { if (PHP_OS_FAMILY === 'Windows') { f_passthru('rmdir /s /q ' . $target); } else { @@ -505,7 +523,7 @@ class FileSystem public static function restoreBackupFile(string $path): void { if (!file_exists($path . '.bak')) { - throw new RuntimeException('Cannot find bak file for ' . $path); + throw new FileSystemException("Backup restore failed: Cannot find bak file for {$path}"); } copy($path . '.bak', $path); unlink($path . '.bak'); diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php index b81bafdf..cab5b9da 100644 --- a/src/SPC/store/LockFile.php +++ b/src/SPC/store/LockFile.php @@ -4,8 +4,7 @@ declare(strict_types=1); namespace SPC\store; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; use SPC\exception\WrongUsageException; class LockFile @@ -128,10 +127,10 @@ class LockFile SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']), SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'), SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT', - default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"), + default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new SPCInternalException("Unknown source type: {$lock_options['source_type']}"), }; if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) { - throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}"); + throw new SPCInternalException("Failed to get hash for source: {$lock_options['source_type']}"); } return $result ?: ''; } @@ -182,7 +181,7 @@ class LockFile $file_content = file_get_contents(self::LOCK_FILE); self::$lock_file_content = json_decode($file_content, true); if (self::$lock_file_content === null) { - throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE); + throw new SPCInternalException('Failed to decode lock file: ' . self::LOCK_FILE); } } } diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index a51ab0a9..b92b945c 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -9,8 +9,7 @@ use SPC\builder\linux\SystemUtil; use SPC\builder\unix\UnixBuilderBase; use SPC\builder\windows\WindowsBuilder; use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; +use SPC\exception\PatchException; use SPC\util\SPCTarget; class SourcePatcher @@ -171,7 +170,7 @@ class SourcePatcher $patches[] = "sapi/micro/patches/{$patchName}_{$tryMajMin}.patch"; continue 2; } - throw new RuntimeException("failed finding patch {$patchName}"); + throw new PatchException('phpmicro patches', "Failed finding patch file or versioned file {$patchName} !"); } foreach ($patches as $patch) { @@ -202,7 +201,7 @@ class SourcePatcher $patch_str = FileSystem::convertPath($patch_file); if (!file_exists($patch_str)) { - throw new RuntimeException("Patch file [{$patch_str}] does not exist"); + throw new PatchException($patch_name, "Patch file [{$patch_str}] does not exist"); } // Copy patch from phar @@ -524,7 +523,7 @@ class SourcePatcher ++$line_num; } if ($found === false) { - throw new RuntimeException('Cannot patch windows CLI Makefile!'); + throw new PatchException('Windows Makefile patching for php.exe target', 'Cannot patch windows CLI Makefile, Makefile does not contain "$(BUILD_DIR)\php.exe:" line'); } $lines[$line_num] = '$(BUILD_DIR)\php.exe: generated_files $(DEPS_CLI) $(PHP_GLOBAL_OBJS) $(CLI_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest'; $lines[$line_num + 1] = "\t" . '"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CLI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CLI) $(BUILD_DIR)\php.exe.res /out:$(BUILD_DIR)\php.exe $(LDFLAGS) $(LDFLAGS_CLI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286'; diff --git a/src/SPC/store/pkg/Zig.php b/src/SPC/store/pkg/Zig.php index 7089f87f..3ac90c6d 100644 --- a/src/SPC/store/pkg/Zig.php +++ b/src/SPC/store/pkg/Zig.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace SPC\store\pkg; -use SPC\exception\RuntimeException; +use SPC\exception\DownloaderException; use SPC\exception\WrongUsageException; use SPC\store\CurlHook; use SPC\store\Downloader; @@ -77,14 +77,14 @@ class Zig extends CustomPackage } if (!$latest_version) { - throw new RuntimeException('Could not determine latest Zig version'); + throw new DownloaderException('Could not determine latest Zig version'); } logger()->info("Installing Zig version {$latest_version}"); $platform_key = "{$zig_arch}-{$zig_os}"; if (!isset($index_json[$latest_version][$platform_key])) { - throw new RuntimeException("No download available for {$platform_key} in Zig version {$latest_version}"); + throw new DownloaderException("No download available for {$platform_key} in Zig version {$latest_version}"); } $download_info = $index_json[$latest_version][$platform_key]; diff --git a/src/SPC/toolchain/ClangNativeToolchain.php b/src/SPC/toolchain/ClangNativeToolchain.php index 86e1b63d..ad09244c 100644 --- a/src/SPC/toolchain/ClangNativeToolchain.php +++ b/src/SPC/toolchain/ClangNativeToolchain.php @@ -7,7 +7,7 @@ namespace SPC\toolchain; use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil; use SPC\builder\linux\SystemUtil as LinuxSystemUtil; use SPC\builder\macos\SystemUtil as MacOSSystemUtil; -use SPC\exception\RuntimeException; +use SPC\exception\EnvironmentException; use SPC\exception\WrongUsageException; use SPC\util\GlobalEnvManager; @@ -35,7 +35,7 @@ class ClangNativeToolchain implements ToolchainInterface 'Linux' => LinuxSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), 'Darwin' => MacOSSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), 'BSD' => FreeBSDSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), - default => throw new RuntimeException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'), + default => throw new EnvironmentException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'), }; } } diff --git a/src/SPC/toolchain/GccNativeToolchain.php b/src/SPC/toolchain/GccNativeToolchain.php index 05fe026a..b8e26c25 100644 --- a/src/SPC/toolchain/GccNativeToolchain.php +++ b/src/SPC/toolchain/GccNativeToolchain.php @@ -7,6 +7,7 @@ namespace SPC\toolchain; use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil; use SPC\builder\linux\SystemUtil as LinuxSystemUtil; use SPC\builder\macos\SystemUtil as MacOSSystemUtil; +use SPC\exception\EnvironmentException; use SPC\exception\WrongUsageException; use SPC\util\GlobalEnvManager; @@ -31,7 +32,7 @@ class GccNativeToolchain implements ToolchainInterface 'Linux' => LinuxSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), 'Darwin' => MacOSSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), 'BSD' => FreeBSDSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."), - default => throw new \RuntimeException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'), + default => throw new EnvironmentException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'), }; } } diff --git a/src/SPC/toolchain/MuslToolchain.php b/src/SPC/toolchain/MuslToolchain.php index 684473c6..ac3c7b73 100644 --- a/src/SPC/toolchain/MuslToolchain.php +++ b/src/SPC/toolchain/MuslToolchain.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace SPC\toolchain; -use SPC\exception\WrongUsageException; +use SPC\exception\EnvironmentException; use SPC\util\GlobalEnvManager; class MuslToolchain implements ToolchainInterface @@ -33,7 +33,7 @@ class MuslToolchain implements ToolchainInterface GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE=LD_LIBRARY_PATH=\"{$ld_library_path}\" {$configure}"); if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) { - throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)'); + throw new EnvironmentException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)'); } } diff --git a/src/SPC/toolchain/ZigToolchain.php b/src/SPC/toolchain/ZigToolchain.php index 5d859e41..3a929f2b 100644 --- a/src/SPC/toolchain/ZigToolchain.php +++ b/src/SPC/toolchain/ZigToolchain.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace SPC\toolchain; -use SPC\exception\WrongUsageException; +use SPC\exception\EnvironmentException; use SPC\store\pkg\Zig; use SPC\util\GlobalEnvManager; @@ -43,7 +43,7 @@ class ZigToolchain implements ToolchainInterface public function afterInit(): void { if (!is_dir(Zig::getEnvironment()['PATH'])) { - throw new WrongUsageException('You are building with zig, but zig is not installed, please install zig first. (You can use `doctor` command to install it)'); + throw new EnvironmentException('You are building with zig, but zig is not installed, please install zig first. (You can use `doctor` command to install it)'); } GlobalEnvManager::addPathIfNotExists(Zig::getEnvironment()['PATH']); f_passthru('ulimit -n 2048'); // zig opens extra file descriptors, so when a lot of extensions are built statically, 1024 is not enough diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index f2689472..59e4d06c 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\util; use SPC\builder\macos\SystemUtil; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; use SPC\exception\WrongUsageException; use SPC\toolchain\ToolchainManager; @@ -33,7 +33,7 @@ class GlobalEnvManager } // Check pre-defined env vars exists if (getenv('BUILD_ROOT_PATH') === false) { - throw new RuntimeException('You must include src/globals/internal-env.php before using GlobalEnvManager'); + throw new SPCInternalException('You must include src/globals/internal-env.php before using GlobalEnvManager'); } // Define env vars for unix diff --git a/src/SPC/util/LicenseDumper.php b/src/SPC/util/LicenseDumper.php index a6d6c998..8ba17c91 100644 --- a/src/SPC/util/LicenseDumper.php +++ b/src/SPC/util/LicenseDumper.php @@ -4,9 +4,7 @@ declare(strict_types=1); namespace SPC\util; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; +use SPC\exception\SPCInternalException; use SPC\store\Config; use SPC\store\FileSystem; @@ -100,7 +98,7 @@ class LicenseDumper { $licenses = Config::getSource($source_name)['license'] ?? []; if ($licenses === []) { - throw new RuntimeException('source [' . $source_name . '] license meta not exist'); + throw new SPCInternalException("source [{$source_name}] license meta not exist"); } if (!array_is_list($licenses)) { @@ -111,7 +109,7 @@ class LicenseDumper yield $index => match ($license['type']) { 'text' => $license['text'], 'file' => $this->loadSourceFile($source_name, $index, $license['path'], Config::getSource($source_name)['path'] ?? null), - default => throw new RuntimeException('source [' . $source_name . '] license type is not allowed'), + default => throw new SPCInternalException("source [{$source_name}] license type is not allowed"), }; } } @@ -122,7 +120,7 @@ class LicenseDumper private function loadSourceFile(string $source_name, int $index, null|array|string $in_path, ?string $custom_base_path = null): string { if (is_null($in_path)) { - throw new RuntimeException('source [' . $source_name . '] license file is not set, please check config/source.json'); + throw new SPCInternalException("source [{$source_name}] license file is not set, please check config/source.json"); } if (!is_array($in_path)) { @@ -139,6 +137,6 @@ class LicenseDumper return file_get_contents(BUILD_ROOT_PATH . '/source-licenses/' . $source_name . '/' . $index . '.txt'); } - throw new RuntimeException('Cannot find any license file in source [' . $source_name . '] directory!'); + throw new SPCInternalException("Cannot find any license file in source [{$source_name}] directory!"); } } diff --git a/src/SPC/util/PkgConfigUtil.php b/src/SPC/util/PkgConfigUtil.php index 6b7cd01d..41a3b6e2 100644 --- a/src/SPC/util/PkgConfigUtil.php +++ b/src/SPC/util/PkgConfigUtil.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace SPC\util; -use SPC\exception\RuntimeException; +use SPC\exception\ExecutionException; /** * Utility class for pkg-config operations @@ -95,7 +95,7 @@ class PkgConfigUtil { f_exec($cmd, $output, $result_code); if ($result_code !== 0) { - throw new RuntimeException("pkg-config command failed with code {$result_code}: {$cmd}"); + throw new ExecutionException($cmd, "pkg-config command failed with code: {$result_code}"); } return implode("\n", $output); } diff --git a/tests/SPC/globals/GlobalFunctionsTest.php b/tests/SPC/globals/GlobalFunctionsTest.php index 038610f6..818f9686 100644 --- a/tests/SPC/globals/GlobalFunctionsTest.php +++ b/tests/SPC/globals/GlobalFunctionsTest.php @@ -6,8 +6,7 @@ namespace SPC\Tests\globals; use PHPUnit\Framework\TestCase; use Psr\Log\LogLevel; -use SPC\exception\RuntimeException; -use SPC\exception\WrongUsageException; +use SPC\exception\ExecutionException; use ZM\Logger\ConsoleLogger; /** @@ -64,7 +63,7 @@ class GlobalFunctionsTest extends TestCase $this->markTestSkipped('Windows not support f_passthru'); } $this->assertEquals(null, f_passthru('echo ""')); - $this->expectException('SPC\exception\RuntimeException'); + $this->expectException(ExecutionException::class); f_passthru('false'); } @@ -80,16 +79,16 @@ class GlobalFunctionsTest extends TestCase $this->markTestSkipped('Windows not support shell'); } $shell = shell(); - $this->assertInstanceOf('SPC\util\UnixShell', $shell); - $this->assertInstanceOf('SPC\util\UnixShell', $shell->cd('/')); - $this->assertInstanceOf('SPC\util\UnixShell', $shell->exec('echo ""')); - $this->assertInstanceOf('SPC\util\UnixShell', $shell->setEnv(['SPC_TEST_ENV' => '1'])); + $this->assertInstanceOf('SPC\util\shell\UnixShell', $shell); + $this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->cd('/')); + $this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->exec('echo ""')); + $this->assertInstanceOf('SPC\util\shell\UnixShell', $shell->setEnv(['SPC_TEST_ENV' => '1'])); [$code, $out] = $shell->execWithResult('echo "_"'); $this->assertEquals(0, $code); $this->assertEquals('_', implode('', $out)); - $this->expectException('SPC\exception\RuntimeException'); + $this->expectException('SPC\exception\ExecutionException'); $shell->exec('false'); } } diff --git a/tests/SPC/store/DownloaderTest.php b/tests/SPC/store/DownloaderTest.php index 91865bfb..749fb385 100644 --- a/tests/SPC/store/DownloaderTest.php +++ b/tests/SPC/store/DownloaderTest.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\Tests\store; use PHPUnit\Framework\TestCase; -use SPC\exception\WrongUsageException; +use SPC\exception\InterruptException; use SPC\store\Downloader; use SPC\store\LockFile; @@ -34,7 +34,7 @@ class DownloaderTest extends TestCase // test keyboard interrupt try { Downloader::downloadGit('setup-static-php', 'https://github.com/static-php/setup-static-php.git', 'SIGINT'); - } catch (WrongUsageException $e) { + } catch (InterruptException $e) { $this->assertStringContainsString('interrupted', $e->getMessage()); return; } @@ -49,7 +49,7 @@ class DownloaderTest extends TestCase // test keyboard interrupt try { Downloader::downloadFile('fake-file', 'https://fakecmd.com/curlDown', 'SIGINT'); - } catch (WrongUsageException $e) { + } catch (InterruptException $e) { $this->assertStringContainsString('interrupted', $e->getMessage()); return; } diff --git a/tests/SPC/util/GlobalEnvManagerTest.php b/tests/SPC/util/GlobalEnvManagerTest.php index 1caeaa19..1d9ed108 100644 --- a/tests/SPC/util/GlobalEnvManagerTest.php +++ b/tests/SPC/util/GlobalEnvManagerTest.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\Tests\util; use PHPUnit\Framework\TestCase; -use SPC\exception\RuntimeException; +use SPC\exception\SPCInternalException; use SPC\util\GlobalEnvManager; /** @@ -105,7 +105,7 @@ final class GlobalEnvManagerTest extends TestCase // Temporarily unset BUILD_ROOT_PATH putenv('BUILD_ROOT_PATH'); - $this->expectException(RuntimeException::class); + $this->expectException(SPCInternalException::class); GlobalEnvManager::init(); } diff --git a/tests/SPC/util/PkgConfigUtilTest.php b/tests/SPC/util/PkgConfigUtilTest.php index 64882a54..41de6d70 100644 --- a/tests/SPC/util/PkgConfigUtilTest.php +++ b/tests/SPC/util/PkgConfigUtilTest.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace SPC\Tests\util; use PHPUnit\Framework\TestCase; -use SPC\exception\RuntimeException; +use SPC\exception\ExecutionException; use SPC\util\PkgConfigUtil; /** @@ -75,7 +75,7 @@ final class PkgConfigUtilTest extends TestCase */ public function testGetCflagsWithInvalidPackage(string $package): void { - $this->expectException(RuntimeException::class); + $this->expectException(ExecutionException::class); PkgConfigUtil::getCflags($package); } @@ -84,7 +84,7 @@ final class PkgConfigUtilTest extends TestCase */ public function testGetLibsArrayWithInvalidPackage(string $package): void { - $this->expectException(RuntimeException::class); + $this->expectException(ExecutionException::class); PkgConfigUtil::getLibsArray($package); } diff --git a/tests/mock/SPC_store.php b/tests/mock/SPC_store.php index 9ee59093..99f74fcc 100644 --- a/tests/mock/SPC_store.php +++ b/tests/mock/SPC_store.php @@ -6,7 +6,8 @@ declare(strict_types=1); namespace SPC\store; -use SPC\exception\RuntimeException; +use SPC\exception\InterruptException; +use SPC\exception\SPCInternalException; function f_exec(string $command, mixed &$output, mixed &$result_code): bool { @@ -52,13 +53,13 @@ function f_passthru(string $cmd): bool { if (str_starts_with($cmd, 'git')) { if (str_contains($cmd, '--branch "SIGINT"')) { - throw new RuntimeException('Interrupt', 2); + throw new InterruptException('interrupted', 2); } return true; } if (str_contains($cmd, 'https://fakecmd.com/curlDown')) { if (str_contains($cmd, 'SIGINT')) { - throw new RuntimeException('Interrupt', 2); + throw new InterruptException('interrupted', 2); } return true; } @@ -71,5 +72,5 @@ function f_passthru(string $cmd): bool return true; } } - throw new RuntimeException('Invalid tests'); + throw new SPCInternalException('Invalid tests'); }