Skip to content

Commit 2fba61e

Browse files
committed
Enhance exception handling by binding builder and extra info to ExceptionHandler
1 parent 1e94342 commit 2fba61e

File tree

5 files changed

+111
-73
lines changed

5 files changed

+111
-73
lines changed

src/SPC/builder/BuilderBase.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE);
229229
*/
230230
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
231231

232+
/**
233+
* Build shared extensions.
234+
*/
232235
public function buildSharedExts(): void
233236
{
234237
$lines = file(BUILD_BIN_PATH . '/php-config');

src/SPC/builder/BuilderProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use SPC\builder\linux\LinuxBuilder;
99
use SPC\builder\macos\MacOSBuilder;
1010
use SPC\builder\windows\WindowsBuilder;
11+
use SPC\exception\ExceptionHandler;
1112
use SPC\exception\WrongUsageException;
1213
use Symfony\Component\Console\Input\InputInterface;
1314

@@ -29,6 +30,10 @@ public static function makeBuilderByInput(InputInterface $input): BuilderBase
2930
'BSD' => new BSDBuilder($input->getOptions()),
3031
default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'),
3132
};
33+
34+
// bind the builder to ExceptionHandler
35+
ExceptionHandler::$bind_builder = self::$builder;
36+
3237
return self::$builder;
3338
}
3439

src/SPC/command/BuildPHPCommand.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace SPC\command;
66

77
use SPC\builder\BuilderProvider;
8-
use SPC\exception\SPCException;
8+
use SPC\exception\ExceptionHandler;
99
use SPC\store\Config;
1010
use SPC\store\FileSystem;
1111
use SPC\store\SourcePatcher;
@@ -165,8 +165,9 @@ public function handle(): int
165165
}
166166
$this->printFormatInfo($this->getDefinedEnvs(), true);
167167
$this->printFormatInfo($indent_texts);
168-
// bind extra info to SPCException
169-
SPCException::bindBuildPHPExtraInfo($indent_texts);
168+
169+
// bind extra info to exception handler
170+
ExceptionHandler::$bind_build_php_extra_info = $indent_texts;
170171

171172
logger()->notice('Build will start after 2s ...');
172173
sleep(2);

src/SPC/exception/ExceptionHandler.php

Lines changed: 84 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SPC\exception;
66

7+
use SPC\builder\BuilderBase;
78
use ZM\Logger\ConsoleColor;
89

910
class ExceptionHandler
@@ -26,21 +27,27 @@ class ExceptionHandler
2627
WrongUsageException::class,
2728
];
2829

30+
/** @var null|BuilderBase Builder binding */
31+
public static ?BuilderBase $bind_builder = null;
32+
33+
/** @var array<string, mixed> Build PHP extra info binding */
34+
public static array $bind_build_php_extra_info = [];
35+
2936
public static function handleSPCException(SPCException $e): void
3037
{
3138
// XXX error: yyy
3239
$head_msg = match ($class = get_class($e)) {
33-
BuildFailureException::class => "Build failed: {$e->getMessage()}",
34-
DownloaderException::class => "Download failed: {$e->getMessage()}",
35-
EnvironmentException::class => "Environment check failed: {$e->getMessage()}",
36-
ExecutionException::class => "Command execution failed: {$e->getMessage()}",
37-
FileSystemException::class => "File system error: {$e->getMessage()}",
40+
BuildFailureException::class => "Build failed: {$e->getMessage()}",
41+
DownloaderException::class => "Download failed: {$e->getMessage()}",
42+
EnvironmentException::class => "Environment check failed: {$e->getMessage()}",
43+
ExecutionException::class => "Command execution failed: {$e->getMessage()}",
44+
FileSystemException::class => "File system error: {$e->getMessage()}",
3845
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
39-
PatchException::class => "Patch apply failed: {$e->getMessage()}",
40-
SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
41-
ValidationException::class => "Validation failed: {$e->getMessage()}",
46+
PatchException::class => "Patch apply failed: {$e->getMessage()}",
47+
SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
48+
ValidationException::class => "Validation failed: {$e->getMessage()}",
4249
WrongUsageException::class => $e->getMessage(),
43-
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
50+
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
4451
};
4552
self::logError($head_msg);
4653

@@ -55,24 +62,24 @@ public static function handleSPCException(SPCException $e): void
5562

5663
// get the SPCException module
5764
if ($php_info = $e->getBuildPHPInfo()) {
58-
self::logError('Failed module: ' . ConsoleColor::yellow("PHP builder {$php_info['builder_class']} for {$php_info['os']}"));
65+
self::logError('Failed module: ' . ConsoleColor::yellow("Builder for {$php_info['os']}"));
5966
} elseif ($lib_info = $e->getLibraryInfo()) {
60-
self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
67+
self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
6168
} elseif ($ext_info = $e->getExtensionInfo()) {
62-
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
69+
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
6370
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
64-
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
71+
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
6572
}
66-
self::logError('');
6773

6874
// get command execution info
6975
if ($e instanceof ExecutionException) {
70-
self::logError('✗ Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
76+
self::logError('');
77+
self::logError('Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
7178
if ($cd = $e->getCd()) {
72-
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
79+
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
7380
}
7481
if ($env = $e->getEnv()) {
75-
self::logError('Command inline env variables:');
82+
self::logError('Command inline env variables:');
7683
foreach ($env as $k => $v) {
7784
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
7885
}
@@ -81,46 +88,42 @@ public static function handleSPCException(SPCException $e): void
8188

8289
// validation error
8390
if ($e instanceof ValidationException) {
84-
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
91+
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
8592
}
8693

8794
// environment error
8895
if ($e instanceof EnvironmentException) {
89-
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
96+
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
9097
if (($solution = $e->getSolution()) !== null) {
91-
self::logError('Solution: ' . ConsoleColor::yellow($solution));
98+
self::logError('Solution: ' . ConsoleColor::yellow($solution));
9299
}
93100
}
94101

95102
// get patch info
96103
if ($e instanceof PatchException) {
97-
self::logError("Failed patch module: {$e->getPatchModule()}");
104+
self::logError("Failed patch module: {$e->getPatchModule()}");
98105
}
99106

100107
// get internal trace
101108
if ($e instanceof SPCInternalException) {
102-
self::logError('Internal trace:');
109+
self::logError('Internal trace:');
103110
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
104111
}
105112

106113
// get the full build info if possible
107-
if (($info = $e->getBuildPHPExtraInfo()) && defined('DEBUG_MODE')) {
108-
self::logError('✗ Build PHP extra info:');
109-
$maxlen = 0;
110-
foreach ($info as $k => $v) {
111-
$maxlen = max(strlen($k), $maxlen);
112-
}
113-
foreach ($info as $k => $v) {
114-
if (is_string($v)) {
115-
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4);
116-
} elseif (is_array($v) && !is_assoc_array($v)) {
117-
$first = array_shift($v);
118-
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4);
119-
foreach ($v as $vs) {
120-
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4);
121-
}
122-
}
123-
}
114+
if ($info = ExceptionHandler::$bind_build_php_extra_info) {
115+
self::logError('', output_log: defined('DEBUG_MODE'));
116+
self::logError('Build PHP extra info:', output_log: defined('DEBUG_MODE'));
117+
self::printArrayInfo($info);
118+
}
119+
120+
// get the full builder options if possible
121+
if (self::$bind_builder && $e->getBuildPHPInfo()) {
122+
$info = $e->getBuildPHPInfo();
123+
self::logError('', output_log: defined('DEBUG_MODE'));
124+
self::logError('Builder function: ' . ConsoleColor::yellow($info['builder_function']), output_log: defined('DEBUG_MODE'));
125+
self::logError('Builder options:', output_log: defined('DEBUG_MODE'));
126+
self::printArrayInfo(self::$bind_builder->getOptions());
124127
}
125128

126129
self::logError("\n----------------------------------------\n");
@@ -142,20 +145,57 @@ public static function handleSPCException(SPCException $e): void
142145
public static function handleDefaultException(\Throwable $e): void
143146
{
144147
$class = get_class($e);
145-
self::logError("Unhandled exception {$class}: {$e->getMessage()}\n\t{$e->getMessage()}\n");
148+
self::logError("Unhandled exception {$class}:\n\t{$e->getMessage()}\n");
146149
self::logError('Stack trace:');
147-
self::logError(ConsoleColor::gray($e->getTraceAsString()), 4);
148-
self::logError('Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
150+
self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
151+
self::logError('Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
149152
}
150153

151-
private static function logError($message, int $indent_space = 0): void
154+
private static function logError($message, int $indent_space = 0, bool $output_log = true): void
152155
{
153156
$spc_log = fopen(SPC_OUTPUT_LOG, 'a');
154157
$msg = explode("\n", (string) $message);
155158
foreach ($msg as $v) {
156159
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
157160
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
158-
echo ConsoleColor::red($line) . PHP_EOL;
161+
if ($output_log) {
162+
echo ConsoleColor::red($line) . PHP_EOL;
163+
}
164+
}
165+
}
166+
167+
/**
168+
* Print array info to console and log.
169+
*/
170+
private static function printArrayInfo(array $info): void
171+
{
172+
$log_output = defined('DEBUG_MODE');
173+
$maxlen = 0;
174+
foreach ($info as $k => $v) {
175+
$maxlen = max(strlen($k), $maxlen);
176+
}
177+
foreach ($info as $k => $v) {
178+
if (is_string($v)) {
179+
if ($v === '') {
180+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('""'), 4, $log_output);
181+
} else {
182+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4, $log_output);
183+
}
184+
} elseif (is_array($v) && !is_assoc_array($v)) {
185+
if ($v === []) {
186+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('[]'), 4, $log_output);
187+
continue;
188+
}
189+
$first = array_shift($v);
190+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4, $log_output);
191+
foreach ($v as $vs) {
192+
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4, $log_output);
193+
}
194+
} elseif (is_bool($v) || is_null($v)) {
195+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::cyan($v === true ? 'true' : ($v === false ? 'false' : 'null')), 4, $log_output);
196+
} else {
197+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow(json_encode($v, JSON_PRETTY_PRINT)), 4, $log_output);
198+
}
159199
}
160200
}
161201
}

src/SPC/exception/SPCException.php

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
*/
2525
abstract class SPCException extends \Exception
2626
{
27-
private static ?array $build_php_extra_info = null;
28-
2927
private ?array $library_info = null;
3028

3129
private ?array $extension_info = null;
@@ -40,11 +38,6 @@ public function __construct(string $message = '', int $code = 0, ?\Throwable $pr
4038
$this->loadStackTraceInfo();
4139
}
4240

43-
public static function bindBuildPHPExtraInfo(array $indent_texts): void
44-
{
45-
self::$build_php_extra_info = $indent_texts;
46-
}
47-
4841
public function bindExtensionInfo(array $extension_info): void
4942
{
5043
$this->extension_info = $extension_info;
@@ -55,11 +48,6 @@ public function addExtraLogFile(string $key, string $filename): void
5548
$this->extra_log_files[$key] = $filename;
5649
}
5750

58-
public function getBuildPHPExtraInfo(): ?array
59-
{
60-
return self::$build_php_extra_info;
61-
}
62-
6351
/**
6452
* Returns an array containing information about the SPC module.
6553
*
@@ -83,6 +71,8 @@ public function getLibraryInfo(): ?array
8371
*
8472
* @return null|array{
8573
* builder_class: string,
74+
* builder_options: array<string, mixed>,
75+
* builder_function: string,
8676
* os: string,
8777
* file: null|string,
8878
* line: null|int,
@@ -124,7 +114,7 @@ private function loadStackTraceInfo(): void
124114
}
125115

126116
// Check if the class is a subclass of LibraryBase
127-
if (!$this->library_info && is_subclass_of($frame['class'], LibraryBase::class)) {
117+
if (!$this->library_info && is_a($frame['class'], LibraryBase::class, true)) {
128118
try {
129119
$reflection = new \ReflectionClass($frame['class']);
130120
if ($reflection->hasConstant('NAME')) {
@@ -152,21 +142,20 @@ private function loadStackTraceInfo(): void
152142
}
153143

154144
// Check if the class is a subclass of BuilderBase and the method is buildPHP
155-
if (!$this->build_php_info && is_subclass_of($frame['class'], BuilderBase::class) && $frame['function'] === 'buildPHP') {
156-
$reflection = new \ReflectionClass($frame['class']);
157-
if ($reflection->hasProperty('options')) {
158-
$options = $reflection->getProperty('options')->getValue();
159-
}
145+
if (!$this->build_php_info && is_a($frame['class'], BuilderBase::class, true)) {
146+
$options = ExceptionHandler::$bind_builder?->getOptions() ?? [];
147+
$os = match (get_class(ExceptionHandler::$bind_builder ?? $frame['class'])) {
148+
BSDBuilder::class => 'BSD',
149+
LinuxBuilder::class => 'Linux',
150+
MacOSBuilder::class => 'macOS',
151+
WindowsBuilder::class => 'Windows',
152+
default => 'Unknown',
153+
};
160154
$this->build_php_info = [
161155
'builder_class' => $frame['class'],
162-
'builder_options' => $options ?? [],
163-
'os' => match (true) {
164-
is_a($frame['class'], BSDBuilder::class, true) => 'BSD',
165-
is_a($frame['class'], LinuxBuilder::class, true) => 'Linux',
166-
is_a($frame['class'], MacOSBuilder::class, true) => 'macOS',
167-
is_a($frame['class'], WindowsBuilder::class, true) => 'Windows',
168-
default => 'Unknown',
169-
},
156+
'builder_options' => $options,
157+
'builder_function' => $frame['function'],
158+
'os' => $os,
170159
'file' => $frame['file'] ?? null,
171160
'line' => $frame['line'] ?? null,
172161
];

0 commit comments

Comments
 (0)