mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-08 17:35:36 +08:00
Enhance exception handling by binding builder and extra info to ExceptionHandler
This commit is contained in:
@@ -229,6 +229,9 @@ abstract class BuilderBase
|
|||||||
*/
|
*/
|
||||||
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
|
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build shared extensions.
|
||||||
|
*/
|
||||||
public function buildSharedExts(): void
|
public function buildSharedExts(): void
|
||||||
{
|
{
|
||||||
$lines = file(BUILD_BIN_PATH . '/php-config');
|
$lines = file(BUILD_BIN_PATH . '/php-config');
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use SPC\builder\freebsd\BSDBuilder;
|
|||||||
use SPC\builder\linux\LinuxBuilder;
|
use SPC\builder\linux\LinuxBuilder;
|
||||||
use SPC\builder\macos\MacOSBuilder;
|
use SPC\builder\macos\MacOSBuilder;
|
||||||
use SPC\builder\windows\WindowsBuilder;
|
use SPC\builder\windows\WindowsBuilder;
|
||||||
|
use SPC\exception\ExceptionHandler;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
|
||||||
@@ -29,6 +30,10 @@ class BuilderProvider
|
|||||||
'BSD' => new BSDBuilder($input->getOptions()),
|
'BSD' => new BSDBuilder($input->getOptions()),
|
||||||
default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'),
|
default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// bind the builder to ExceptionHandler
|
||||||
|
ExceptionHandler::$bind_builder = self::$builder;
|
||||||
|
|
||||||
return self::$builder;
|
return self::$builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace SPC\command;
|
namespace SPC\command;
|
||||||
|
|
||||||
use SPC\builder\BuilderProvider;
|
use SPC\builder\BuilderProvider;
|
||||||
use SPC\exception\SPCException;
|
use SPC\exception\ExceptionHandler;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
use SPC\store\SourcePatcher;
|
use SPC\store\SourcePatcher;
|
||||||
@@ -165,8 +165,9 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
}
|
}
|
||||||
$this->printFormatInfo($this->getDefinedEnvs(), true);
|
$this->printFormatInfo($this->getDefinedEnvs(), true);
|
||||||
$this->printFormatInfo($indent_texts);
|
$this->printFormatInfo($indent_texts);
|
||||||
// bind extra info to SPCException
|
|
||||||
SPCException::bindBuildPHPExtraInfo($indent_texts);
|
// bind extra info to exception handler
|
||||||
|
ExceptionHandler::$bind_build_php_extra_info = $indent_texts;
|
||||||
|
|
||||||
logger()->notice('Build will start after 2s ...');
|
logger()->notice('Build will start after 2s ...');
|
||||||
sleep(2);
|
sleep(2);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace SPC\exception;
|
namespace SPC\exception;
|
||||||
|
|
||||||
|
use SPC\builder\BuilderBase;
|
||||||
use ZM\Logger\ConsoleColor;
|
use ZM\Logger\ConsoleColor;
|
||||||
|
|
||||||
class ExceptionHandler
|
class ExceptionHandler
|
||||||
@@ -26,21 +27,27 @@ class ExceptionHandler
|
|||||||
WrongUsageException::class,
|
WrongUsageException::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** @var null|BuilderBase Builder binding */
|
||||||
|
public static ?BuilderBase $bind_builder = null;
|
||||||
|
|
||||||
|
/** @var array<string, mixed> Build PHP extra info binding */
|
||||||
|
public static array $bind_build_php_extra_info = [];
|
||||||
|
|
||||||
public static function handleSPCException(SPCException $e): void
|
public static function handleSPCException(SPCException $e): void
|
||||||
{
|
{
|
||||||
// XXX error: yyy
|
// XXX error: yyy
|
||||||
$head_msg = match ($class = get_class($e)) {
|
$head_msg = match ($class = get_class($e)) {
|
||||||
BuildFailureException::class => "Build failed: {$e->getMessage()}",
|
BuildFailureException::class => "✗ Build failed: {$e->getMessage()}",
|
||||||
DownloaderException::class => "Download failed: {$e->getMessage()}",
|
DownloaderException::class => "✗ Download failed: {$e->getMessage()}",
|
||||||
EnvironmentException::class => "Environment check failed: {$e->getMessage()}",
|
EnvironmentException::class => "⚠ Environment check failed: {$e->getMessage()}",
|
||||||
ExecutionException::class => "Command execution failed: {$e->getMessage()}",
|
ExecutionException::class => "✗ Command execution failed: {$e->getMessage()}",
|
||||||
FileSystemException::class => "File system error: {$e->getMessage()}",
|
FileSystemException::class => "✗ File system error: {$e->getMessage()}",
|
||||||
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
|
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
|
||||||
PatchException::class => "Patch apply failed: {$e->getMessage()}",
|
PatchException::class => "✗ Patch apply failed: {$e->getMessage()}",
|
||||||
SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
|
SPCInternalException::class => "✗ SPC internal error: {$e->getMessage()}",
|
||||||
ValidationException::class => "Validation failed: {$e->getMessage()}",
|
ValidationException::class => "⚠ Validation failed: {$e->getMessage()}",
|
||||||
WrongUsageException::class => $e->getMessage(),
|
WrongUsageException::class => $e->getMessage(),
|
||||||
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
|
default => "✗Unknown SPC exception {$class}: {$e->getMessage()}",
|
||||||
};
|
};
|
||||||
self::logError($head_msg);
|
self::logError($head_msg);
|
||||||
|
|
||||||
@@ -55,24 +62,24 @@ class ExceptionHandler
|
|||||||
|
|
||||||
// get the SPCException module
|
// get the SPCException module
|
||||||
if ($php_info = $e->getBuildPHPInfo()) {
|
if ($php_info = $e->getBuildPHPInfo()) {
|
||||||
self::logError('✗ Failed module: ' . ConsoleColor::yellow("PHP builder {$php_info['builder_class']} for {$php_info['os']}"));
|
self::logError('Failed module: ' . ConsoleColor::yellow("Builder for {$php_info['os']}"));
|
||||||
} elseif ($lib_info = $e->getLibraryInfo()) {
|
} elseif ($lib_info = $e->getLibraryInfo()) {
|
||||||
self::logError('✗ Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
|
self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
|
||||||
} elseif ($ext_info = $e->getExtensionInfo()) {
|
} elseif ($ext_info = $e->getExtensionInfo()) {
|
||||||
self::logError('✗ Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
|
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
|
||||||
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
|
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
|
||||||
self::logError('✗ Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
|
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
|
||||||
}
|
}
|
||||||
self::logError('');
|
|
||||||
|
|
||||||
// get command execution info
|
// get command execution info
|
||||||
if ($e instanceof ExecutionException) {
|
if ($e instanceof ExecutionException) {
|
||||||
self::logError('✗ Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
|
self::logError('');
|
||||||
|
self::logError('Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
|
||||||
if ($cd = $e->getCd()) {
|
if ($cd = $e->getCd()) {
|
||||||
self::logError('✗ Command executed in: ' . ConsoleColor::yellow($cd));
|
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
|
||||||
}
|
}
|
||||||
if ($env = $e->getEnv()) {
|
if ($env = $e->getEnv()) {
|
||||||
self::logError('✗ Command inline env variables:');
|
self::logError('Command inline env variables:');
|
||||||
foreach ($env as $k => $v) {
|
foreach ($env as $k => $v) {
|
||||||
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
|
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
|
||||||
}
|
}
|
||||||
@@ -81,46 +88,42 @@ class ExceptionHandler
|
|||||||
|
|
||||||
// validation error
|
// validation error
|
||||||
if ($e instanceof ValidationException) {
|
if ($e instanceof ValidationException) {
|
||||||
self::logError('✗ Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
|
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// environment error
|
// environment error
|
||||||
if ($e instanceof EnvironmentException) {
|
if ($e instanceof EnvironmentException) {
|
||||||
self::logError('✗ Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
|
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
|
||||||
if (($solution = $e->getSolution()) !== null) {
|
if (($solution = $e->getSolution()) !== null) {
|
||||||
self::logError('✗ Solution: ' . ConsoleColor::yellow($solution));
|
self::logError('Solution: ' . ConsoleColor::yellow($solution));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get patch info
|
// get patch info
|
||||||
if ($e instanceof PatchException) {
|
if ($e instanceof PatchException) {
|
||||||
self::logError("✗ Failed patch module: {$e->getPatchModule()}");
|
self::logError("Failed patch module: {$e->getPatchModule()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// get internal trace
|
// get internal trace
|
||||||
if ($e instanceof SPCInternalException) {
|
if ($e instanceof SPCInternalException) {
|
||||||
self::logError('✗ Internal trace:');
|
self::logError('Internal trace:');
|
||||||
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
|
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the full build info if possible
|
// get the full build info if possible
|
||||||
if (($info = $e->getBuildPHPExtraInfo()) && defined('DEBUG_MODE')) {
|
if ($info = ExceptionHandler::$bind_build_php_extra_info) {
|
||||||
self::logError('✗ Build PHP extra info:');
|
self::logError('', output_log: defined('DEBUG_MODE'));
|
||||||
$maxlen = 0;
|
self::logError('Build PHP extra info:', output_log: defined('DEBUG_MODE'));
|
||||||
foreach ($info as $k => $v) {
|
self::printArrayInfo($info);
|
||||||
$maxlen = max(strlen($k), $maxlen);
|
}
|
||||||
}
|
|
||||||
foreach ($info as $k => $v) {
|
// get the full builder options if possible
|
||||||
if (is_string($v)) {
|
if (self::$bind_builder && $e->getBuildPHPInfo()) {
|
||||||
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4);
|
$info = $e->getBuildPHPInfo();
|
||||||
} elseif (is_array($v) && !is_assoc_array($v)) {
|
self::logError('', output_log: defined('DEBUG_MODE'));
|
||||||
$first = array_shift($v);
|
self::logError('Builder function: ' . ConsoleColor::yellow($info['builder_function']), output_log: defined('DEBUG_MODE'));
|
||||||
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4);
|
self::logError('Builder options:', output_log: defined('DEBUG_MODE'));
|
||||||
foreach ($v as $vs) {
|
self::printArrayInfo(self::$bind_builder->getOptions());
|
||||||
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self::logError("\n----------------------------------------\n");
|
self::logError("\n----------------------------------------\n");
|
||||||
@@ -142,20 +145,57 @@ class ExceptionHandler
|
|||||||
public static function handleDefaultException(\Throwable $e): void
|
public static function handleDefaultException(\Throwable $e): void
|
||||||
{
|
{
|
||||||
$class = get_class($e);
|
$class = get_class($e);
|
||||||
self::logError("Unhandled exception {$class}: {$e->getMessage()}\n\t{$e->getMessage()}\n");
|
self::logError("✗ Unhandled exception {$class}:\n\t{$e->getMessage()}\n");
|
||||||
self::logError('Stack trace:');
|
self::logError('Stack trace:');
|
||||||
self::logError(ConsoleColor::gray($e->getTraceAsString()), 4);
|
self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
|
||||||
self::logError('Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
|
self::logError('⚠ Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function logError($message, int $indent_space = 0): void
|
private static function logError($message, int $indent_space = 0, bool $output_log = true): void
|
||||||
{
|
{
|
||||||
$spc_log = fopen(SPC_OUTPUT_LOG, 'a');
|
$spc_log = fopen(SPC_OUTPUT_LOG, 'a');
|
||||||
$msg = explode("\n", (string) $message);
|
$msg = explode("\n", (string) $message);
|
||||||
foreach ($msg as $v) {
|
foreach ($msg as $v) {
|
||||||
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
|
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
|
||||||
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
|
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
|
||||||
echo ConsoleColor::red($line) . PHP_EOL;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ use SPC\builder\windows\WindowsBuilder;
|
|||||||
*/
|
*/
|
||||||
abstract class SPCException extends \Exception
|
abstract class SPCException extends \Exception
|
||||||
{
|
{
|
||||||
private static ?array $build_php_extra_info = null;
|
|
||||||
|
|
||||||
private ?array $library_info = null;
|
private ?array $library_info = null;
|
||||||
|
|
||||||
private ?array $extension_info = null;
|
private ?array $extension_info = null;
|
||||||
@@ -40,11 +38,6 @@ abstract class SPCException extends \Exception
|
|||||||
$this->loadStackTraceInfo();
|
$this->loadStackTraceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function bindBuildPHPExtraInfo(array $indent_texts): void
|
|
||||||
{
|
|
||||||
self::$build_php_extra_info = $indent_texts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function bindExtensionInfo(array $extension_info): void
|
public function bindExtensionInfo(array $extension_info): void
|
||||||
{
|
{
|
||||||
$this->extension_info = $extension_info;
|
$this->extension_info = $extension_info;
|
||||||
@@ -55,11 +48,6 @@ abstract class SPCException extends \Exception
|
|||||||
$this->extra_log_files[$key] = $filename;
|
$this->extra_log_files[$key] = $filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBuildPHPExtraInfo(): ?array
|
|
||||||
{
|
|
||||||
return self::$build_php_extra_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array containing information about the SPC module.
|
* Returns an array containing information about the SPC module.
|
||||||
*
|
*
|
||||||
@@ -83,6 +71,8 @@ abstract class SPCException extends \Exception
|
|||||||
*
|
*
|
||||||
* @return null|array{
|
* @return null|array{
|
||||||
* builder_class: string,
|
* builder_class: string,
|
||||||
|
* builder_options: array<string, mixed>,
|
||||||
|
* builder_function: string,
|
||||||
* os: string,
|
* os: string,
|
||||||
* file: null|string,
|
* file: null|string,
|
||||||
* line: null|int,
|
* line: null|int,
|
||||||
@@ -124,7 +114,7 @@ abstract class SPCException extends \Exception
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the class is a subclass of LibraryBase
|
// Check if the class is a subclass of LibraryBase
|
||||||
if (!$this->library_info && is_subclass_of($frame['class'], LibraryBase::class)) {
|
if (!$this->library_info && is_a($frame['class'], LibraryBase::class, true)) {
|
||||||
try {
|
try {
|
||||||
$reflection = new \ReflectionClass($frame['class']);
|
$reflection = new \ReflectionClass($frame['class']);
|
||||||
if ($reflection->hasConstant('NAME')) {
|
if ($reflection->hasConstant('NAME')) {
|
||||||
@@ -152,21 +142,20 @@ abstract class SPCException extends \Exception
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the class is a subclass of BuilderBase and the method is buildPHP
|
// Check if the class is a subclass of BuilderBase and the method is buildPHP
|
||||||
if (!$this->build_php_info && is_subclass_of($frame['class'], BuilderBase::class) && $frame['function'] === 'buildPHP') {
|
if (!$this->build_php_info && is_a($frame['class'], BuilderBase::class, true)) {
|
||||||
$reflection = new \ReflectionClass($frame['class']);
|
$options = ExceptionHandler::$bind_builder?->getOptions() ?? [];
|
||||||
if ($reflection->hasProperty('options')) {
|
$os = match (get_class(ExceptionHandler::$bind_builder ?? $frame['class'])) {
|
||||||
$options = $reflection->getProperty('options')->getValue();
|
BSDBuilder::class => 'BSD',
|
||||||
}
|
LinuxBuilder::class => 'Linux',
|
||||||
|
MacOSBuilder::class => 'macOS',
|
||||||
|
WindowsBuilder::class => 'Windows',
|
||||||
|
default => 'Unknown',
|
||||||
|
};
|
||||||
$this->build_php_info = [
|
$this->build_php_info = [
|
||||||
'builder_class' => $frame['class'],
|
'builder_class' => $frame['class'],
|
||||||
'builder_options' => $options ?? [],
|
'builder_options' => $options,
|
||||||
'os' => match (true) {
|
'builder_function' => $frame['function'],
|
||||||
is_a($frame['class'], BSDBuilder::class, true) => 'BSD',
|
'os' => $os,
|
||||||
is_a($frame['class'], LinuxBuilder::class, true) => 'Linux',
|
|
||||||
is_a($frame['class'], MacOSBuilder::class, true) => 'macOS',
|
|
||||||
is_a($frame['class'], WindowsBuilder::class, true) => 'Windows',
|
|
||||||
default => 'Unknown',
|
|
||||||
},
|
|
||||||
'file' => $frame['file'] ?? null,
|
'file' => $frame['file'] ?? null,
|
||||||
'line' => $frame['line'] ?? null,
|
'line' => $frame['line'] ?? null,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user