2023-03-15 20:40:49 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace SPC\exception;
|
|
|
|
|
|
2025-08-11 10:48:48 +08:00
|
|
|
use SPC\builder\BuilderBase;
|
2025-08-11 12:05:41 +08:00
|
|
|
use SPC\builder\freebsd\BSDBuilder;
|
|
|
|
|
use SPC\builder\linux\LinuxBuilder;
|
|
|
|
|
use SPC\builder\macos\MacOSBuilder;
|
|
|
|
|
use SPC\builder\windows\WindowsBuilder;
|
2025-08-06 20:45:16 +08:00
|
|
|
use ZM\Logger\ConsoleColor;
|
|
|
|
|
|
2023-03-15 20:40:49 +08:00
|
|
|
class ExceptionHandler
|
|
|
|
|
{
|
2025-08-06 20:45:16 +08:00
|
|
|
public const array KNOWN_EXCEPTIONS = [
|
|
|
|
|
BuildFailureException::class,
|
|
|
|
|
DownloaderException::class,
|
|
|
|
|
EnvironmentException::class,
|
|
|
|
|
ExecutionException::class,
|
|
|
|
|
FileSystemException::class,
|
|
|
|
|
InterruptException::class,
|
|
|
|
|
PatchException::class,
|
|
|
|
|
SPCInternalException::class,
|
|
|
|
|
ValidationException::class,
|
|
|
|
|
WrongUsageException::class,
|
|
|
|
|
];
|
2023-03-15 20:40:49 +08:00
|
|
|
|
2025-08-06 20:45:16 +08:00
|
|
|
public const array MINOR_LOG_EXCEPTIONS = [
|
|
|
|
|
InterruptException::class,
|
|
|
|
|
WrongUsageException::class,
|
|
|
|
|
];
|
2023-03-15 20:40:49 +08:00
|
|
|
|
2025-08-11 10:48:48 +08:00
|
|
|
/** @var null|BuilderBase Builder binding */
|
2025-08-11 12:05:41 +08:00
|
|
|
private static ?BuilderBase $bind_builder = null;
|
2025-08-11 10:48:48 +08:00
|
|
|
|
|
|
|
|
/** @var array<string, mixed> Build PHP extra info binding */
|
2025-08-11 12:05:41 +08:00
|
|
|
private static array $bind_build_php_extra_info = [];
|
2025-08-11 10:48:48 +08:00
|
|
|
|
2025-08-06 20:45:16 +08:00
|
|
|
public static function handleSPCException(SPCException $e): void
|
2023-03-15 20:40:49 +08:00
|
|
|
{
|
2025-08-06 20:45:16 +08:00
|
|
|
// XXX error: yyy
|
|
|
|
|
$head_msg = match ($class = get_class($e)) {
|
2025-08-11 10:48:48 +08:00
|
|
|
BuildFailureException::class => "✗ Build failed: {$e->getMessage()}",
|
|
|
|
|
DownloaderException::class => "✗ Download failed: {$e->getMessage()}",
|
|
|
|
|
EnvironmentException::class => "⚠ Environment check failed: {$e->getMessage()}",
|
|
|
|
|
ExecutionException::class => "✗ Command execution failed: {$e->getMessage()}",
|
|
|
|
|
FileSystemException::class => "✗ File system error: {$e->getMessage()}",
|
2025-08-06 20:45:16 +08:00
|
|
|
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
|
2025-08-11 10:48:48 +08:00
|
|
|
PatchException::class => "✗ Patch apply failed: {$e->getMessage()}",
|
|
|
|
|
SPCInternalException::class => "✗ SPC internal error: {$e->getMessage()}",
|
|
|
|
|
ValidationException::class => "⚠ Validation failed: {$e->getMessage()}",
|
2025-08-06 20:45:16 +08:00
|
|
|
WrongUsageException::class => $e->getMessage(),
|
2025-08-11 12:05:41 +08:00
|
|
|
default => "✗ Unknown SPC exception {$class}: {$e->getMessage()}",
|
2025-08-06 20:45:16 +08:00
|
|
|
};
|
|
|
|
|
self::logError($head_msg);
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
|
$minor_logs = in_array($class, self::MINOR_LOG_EXCEPTIONS, true);
|
|
|
|
|
|
|
|
|
|
if ($minor_logs) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self::logError("----------------------------------------\n");
|
|
|
|
|
|
|
|
|
|
// get the SPCException module
|
2025-08-11 12:05:41 +08:00
|
|
|
if ($lib_info = $e->getLibraryInfo()) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
|
2025-08-06 20:45:16 +08:00
|
|
|
} elseif ($ext_info = $e->getExtensionInfo()) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
|
2025-08-11 12:05:41 +08:00
|
|
|
} elseif (self::$bind_builder) {
|
|
|
|
|
$os = match (get_class(self::$bind_builder)) {
|
|
|
|
|
WindowsBuilder::class => 'Windows',
|
|
|
|
|
MacOSBuilder::class => 'macOS',
|
|
|
|
|
LinuxBuilder::class => 'Linux',
|
|
|
|
|
BSDBuilder::class => 'FreeBSD',
|
|
|
|
|
default => 'Unknown OS',
|
|
|
|
|
};
|
|
|
|
|
self::logError('Failed module: ' . ConsoleColor::yellow("Builder for {$os}"));
|
2025-08-06 20:45:16 +08:00
|
|
|
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get command execution info
|
|
|
|
|
if ($e instanceof ExecutionException) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('');
|
|
|
|
|
self::logError('Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
|
2025-08-06 20:45:16 +08:00
|
|
|
if ($cd = $e->getCd()) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
if ($env = $e->getEnv()) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Command inline env variables:');
|
2025-08-06 20:45:16 +08:00
|
|
|
foreach ($env as $k => $v) {
|
|
|
|
|
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validation error
|
|
|
|
|
if ($e instanceof ValidationException) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// environment error
|
|
|
|
|
if ($e instanceof EnvironmentException) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
|
2025-08-06 20:45:16 +08:00
|
|
|
if (($solution = $e->getSolution()) !== null) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Solution: ' . ConsoleColor::yellow($solution));
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get patch info
|
|
|
|
|
if ($e instanceof PatchException) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError("Failed patch module: {$e->getPatchModule()}");
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get internal trace
|
|
|
|
|
if ($e instanceof SPCInternalException) {
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError('Internal trace:');
|
2025-08-06 20:45:16 +08:00
|
|
|
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the full build info if possible
|
2025-08-11 10:48:48 +08:00
|
|
|
if ($info = ExceptionHandler::$bind_build_php_extra_info) {
|
|
|
|
|
self::logError('', output_log: defined('DEBUG_MODE'));
|
|
|
|
|
self::logError('Build PHP extra info:', output_log: defined('DEBUG_MODE'));
|
|
|
|
|
self::printArrayInfo($info);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the full builder options if possible
|
2025-08-11 12:05:41 +08:00
|
|
|
if ($e->getBuildPHPInfo()) {
|
2025-08-11 10:48:48 +08:00
|
|
|
$info = $e->getBuildPHPInfo();
|
|
|
|
|
self::logError('', output_log: defined('DEBUG_MODE'));
|
|
|
|
|
self::logError('Builder function: ' . ConsoleColor::yellow($info['builder_function']), output_log: defined('DEBUG_MODE'));
|
2025-08-11 12:05:41 +08:00
|
|
|
if (self::$bind_builder) {
|
|
|
|
|
self::logError('Builder options:', output_log: defined('DEBUG_MODE'));
|
|
|
|
|
self::printArrayInfo(self::$bind_builder->getOptions());
|
|
|
|
|
}
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self::logError("\n----------------------------------------\n");
|
|
|
|
|
|
|
|
|
|
self::logError('⚠ The ' . ConsoleColor::cyan('console output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none(SPC_OUTPUT_LOG));
|
|
|
|
|
if (file_exists(SPC_SHELL_LOG)) {
|
|
|
|
|
self::logError('⚠ The ' . ConsoleColor::cyan('shell output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none(SPC_SHELL_LOG));
|
|
|
|
|
}
|
|
|
|
|
if ($e->getExtraLogFiles() !== []) {
|
|
|
|
|
foreach ($e->getExtraLogFiles() as $key => $file) {
|
|
|
|
|
self::logError("⚠ Log file [{$key}] is saved in: " . ConsoleColor::none(SPC_LOGS_DIR . "/{$file}"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!defined('DEBUG_MODE')) {
|
|
|
|
|
self::logError('⚠ If you want to see more details in console, use `--debug` option.');
|
2023-03-15 20:40:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 20:45:16 +08:00
|
|
|
public static function handleDefaultException(\Throwable $e): void
|
2023-03-15 20:40:49 +08:00
|
|
|
{
|
2025-08-06 20:45:16 +08:00
|
|
|
$class = get_class($e);
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError("✗ Unhandled exception {$class}:\n\t{$e->getMessage()}\n");
|
2025-08-06 20:45:16 +08:00
|
|
|
self::logError('Stack trace:');
|
2025-08-11 10:48:48 +08:00
|
|
|
self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
|
|
|
|
|
self::logError('⚠ Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-11 13:29:48 +08:00
|
|
|
public static function bindBuilder(?BuilderBase $bind_builder): void
|
2025-08-11 12:05:41 +08:00
|
|
|
{
|
|
|
|
|
self::$bind_builder = $bind_builder;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 13:29:54 +08:00
|
|
|
public static function bindBuildPhpExtraInfo(array $build_php_extra_info): void
|
2025-08-11 12:05:41 +08:00
|
|
|
{
|
2025-08-11 13:29:59 +08:00
|
|
|
self::$build_php_extra_info = $build_php_extra_info;
|
2025-08-11 12:05:41 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-11 10:48:48 +08:00
|
|
|
private static function logError($message, int $indent_space = 0, bool $output_log = true): void
|
2025-08-06 20:45:16 +08:00
|
|
|
{
|
|
|
|
|
$spc_log = fopen(SPC_OUTPUT_LOG, 'a');
|
|
|
|
|
$msg = explode("\n", (string) $message);
|
|
|
|
|
foreach ($msg as $v) {
|
|
|
|
|
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
|
|
|
|
|
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
|
2025-08-11 10:48:48 +08:00
|
|
|
if ($output_log) {
|
|
|
|
|
echo ConsoleColor::red($line) . PHP_EOL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Print array info to console and log.
|
|
|
|
|
*/
|
|
|
|
|
private static function printArrayInfo(array $info): void
|
|
|
|
|
{
|
|
|
|
|
$log_output = defined('DEBUG_MODE');
|
|
|
|
|
$maxlen = 0;
|
|
|
|
|
foreach ($info as $k => $v) {
|
|
|
|
|
$maxlen = max(strlen($k), $maxlen);
|
|
|
|
|
}
|
|
|
|
|
foreach ($info as $k => $v) {
|
|
|
|
|
if (is_string($v)) {
|
|
|
|
|
if ($v === '') {
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('""'), 4, $log_output);
|
|
|
|
|
} else {
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4, $log_output);
|
|
|
|
|
}
|
|
|
|
|
} elseif (is_array($v) && !is_assoc_array($v)) {
|
|
|
|
|
if ($v === []) {
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('[]'), 4, $log_output);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$first = array_shift($v);
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4, $log_output);
|
|
|
|
|
foreach ($v as $vs) {
|
|
|
|
|
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4, $log_output);
|
|
|
|
|
}
|
|
|
|
|
} elseif (is_bool($v) || is_null($v)) {
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::cyan($v === true ? 'true' : ($v === false ? 'false' : 'null')), 4, $log_output);
|
|
|
|
|
} else {
|
|
|
|
|
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow(json_encode($v, JSON_PRETTY_PRINT)), 4, $log_output);
|
|
|
|
|
}
|
2025-08-06 20:45:16 +08:00
|
|
|
}
|
2023-03-15 20:40:49 +08:00
|
|
|
}
|
|
|
|
|
}
|