Skip to content

Commit 5f62925

Browse files
authored
Merge pull request #853 from crazywhalecc/fix/exception-reflection
Enhance exception handling by binding builder and extra info to exception handler
2 parents 1e94342 + 2cabaf6 commit 5f62925

File tree

8 files changed

+163
-118
lines changed

8 files changed

+163
-118
lines changed

.github/workflows/ext-matrix-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jobs:
8282
- zlib
8383
- zstd
8484
php-version:
85-
- "git"
85+
- "8.5"
8686
operating-system:
8787
- "ubuntu-latest"
8888
#- "macos-13"

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::bindBuilder(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::bindBuildPhpExtraInfo($indent_texts);
170171

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

src/SPC/exception/ExceptionHandler.php

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
namespace SPC\exception;
66

7+
use SPC\builder\BuilderBase;
8+
use SPC\builder\freebsd\BSDBuilder;
9+
use SPC\builder\linux\LinuxBuilder;
10+
use SPC\builder\macos\MacOSBuilder;
11+
use SPC\builder\windows\WindowsBuilder;
712
use ZM\Logger\ConsoleColor;
813

914
class ExceptionHandler
@@ -26,21 +31,27 @@ class ExceptionHandler
2631
WrongUsageException::class,
2732
];
2833

34+
/** @var null|BuilderBase Builder binding */
35+
private static ?BuilderBase $builder = null;
36+
37+
/** @var array<string, mixed> Build PHP extra info binding */
38+
private static array $build_php_extra_info = [];
39+
2940
public static function handleSPCException(SPCException $e): void
3041
{
3142
// XXX error: yyy
3243
$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()}",
44+
BuildFailureException::class => "Build failed: {$e->getMessage()}",
45+
DownloaderException::class => "Download failed: {$e->getMessage()}",
46+
EnvironmentException::class => "Environment check failed: {$e->getMessage()}",
47+
ExecutionException::class => "Command execution failed: {$e->getMessage()}",
48+
FileSystemException::class => "File system error: {$e->getMessage()}",
3849
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()}",
50+
PatchException::class => "Patch apply failed: {$e->getMessage()}",
51+
SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
52+
ValidationException::class => "Validation failed: {$e->getMessage()}",
4253
WrongUsageException::class => $e->getMessage(),
43-
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
54+
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
4455
};
4556
self::logError($head_msg);
4657

@@ -54,25 +65,32 @@ public static function handleSPCException(SPCException $e): void
5465
self::logError("----------------------------------------\n");
5566

5667
// get the SPCException module
57-
if ($php_info = $e->getBuildPHPInfo()) {
58-
self::logError('✗ Failed module: ' . ConsoleColor::yellow("PHP builder {$php_info['builder_class']} for {$php_info['os']}"));
59-
} elseif ($lib_info = $e->getLibraryInfo()) {
60-
self::logError('✗ Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
68+
if ($lib_info = $e->getLibraryInfo()) {
69+
self::logError('Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
6170
} elseif ($ext_info = $e->getExtensionInfo()) {
62-
self::logError('✗ Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
71+
self::logError('Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
72+
} elseif (self::$builder) {
73+
$os = match (get_class(self::$builder)) {
74+
WindowsBuilder::class => 'Windows',
75+
MacOSBuilder::class => 'macOS',
76+
LinuxBuilder::class => 'Linux',
77+
BSDBuilder::class => 'FreeBSD',
78+
default => 'Unknown OS',
79+
};
80+
self::logError('Failed module: ' . ConsoleColor::yellow("Builder for {$os}"));
6381
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
64-
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
82+
self::logError('Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
6583
}
66-
self::logError('');
6784

6885
// get command execution info
6986
if ($e instanceof ExecutionException) {
70-
self::logError('✗ Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
87+
self::logError('');
88+
self::logError('Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
7189
if ($cd = $e->getCd()) {
72-
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
90+
self::logError('Command executed in: ' . ConsoleColor::yellow($cd));
7391
}
7492
if ($env = $e->getEnv()) {
75-
self::logError('Command inline env variables:');
93+
self::logError('Command inline env variables:');
7694
foreach ($env as $k => $v) {
7795
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
7896
}
@@ -81,46 +99,40 @@ public static function handleSPCException(SPCException $e): void
8199

82100
// validation error
83101
if ($e instanceof ValidationException) {
84-
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
102+
self::logError('Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
85103
}
86104

87105
// environment error
88106
if ($e instanceof EnvironmentException) {
89-
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
107+
self::logError('Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
90108
if (($solution = $e->getSolution()) !== null) {
91-
self::logError('Solution: ' . ConsoleColor::yellow($solution));
109+
self::logError('Solution: ' . ConsoleColor::yellow($solution));
92110
}
93111
}
94112

95113
// get patch info
96114
if ($e instanceof PatchException) {
97-
self::logError("Failed patch module: {$e->getPatchModule()}");
115+
self::logError("Failed patch module: {$e->getPatchModule()}");
98116
}
99117

100118
// get internal trace
101119
if ($e instanceof SPCInternalException) {
102-
self::logError('Internal trace:');
120+
self::logError('Internal trace:');
103121
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
104122
}
105123

106124
// 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-
}
125+
if ($info = ExceptionHandler::$build_php_extra_info) {
126+
self::logError('', output_log: defined('DEBUG_MODE'));
127+
self::logError('Build PHP extra info:', output_log: defined('DEBUG_MODE'));
128+
self::printArrayInfo($info);
129+
}
130+
131+
// get the full builder options if possible
132+
if ($e->getBuildPHPInfo()) {
133+
$info = $e->getBuildPHPInfo();
134+
self::logError('', output_log: defined('DEBUG_MODE'));
135+
self::logError('Builder function: ' . ConsoleColor::yellow($info['builder_function']), output_log: defined('DEBUG_MODE'));
124136
}
125137

126138
self::logError("\n----------------------------------------\n");
@@ -142,20 +154,67 @@ public static function handleSPCException(SPCException $e): void
142154
public static function handleDefaultException(\Throwable $e): void
143155
{
144156
$class = get_class($e);
145-
self::logError("Unhandled exception {$class}: {$e->getMessage()}\n\t{$e->getMessage()}\n");
157+
self::logError("Unhandled exception {$class}:\n\t{$e->getMessage()}\n");
146158
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');
159+
self::logError(ConsoleColor::gray($e->getTraceAsString()) . PHP_EOL, 4);
160+
self::logError('⚠ Please report this exception to: https://github.com/crazywhalecc/static-php-cli/issues');
161+
}
162+
163+
public static function bindBuilder(?BuilderBase $bind_builder): void
164+
{
165+
self::$builder = $bind_builder;
149166
}
150167

151-
private static function logError($message, int $indent_space = 0): void
168+
public static function bindBuildPhpExtraInfo(array $build_php_extra_info): void
169+
{
170+
self::$build_php_extra_info = $build_php_extra_info;
171+
}
172+
173+
private static function logError($message, int $indent_space = 0, bool $output_log = true): void
152174
{
153175
$spc_log = fopen(SPC_OUTPUT_LOG, 'a');
154176
$msg = explode("\n", (string) $message);
155177
foreach ($msg as $v) {
156178
$line = str_pad($v, strlen($v) + $indent_space, ' ', STR_PAD_LEFT);
157179
fwrite($spc_log, strip_ansi_colors($line) . PHP_EOL);
158-
echo ConsoleColor::red($line) . PHP_EOL;
180+
if ($output_log) {
181+
echo ConsoleColor::red($line) . PHP_EOL;
182+
}
183+
}
184+
}
185+
186+
/**
187+
* Print array info to console and log.
188+
*/
189+
private static function printArrayInfo(array $info): void
190+
{
191+
$log_output = defined('DEBUG_MODE');
192+
$maxlen = 0;
193+
foreach ($info as $k => $v) {
194+
$maxlen = max(strlen($k), $maxlen);
195+
}
196+
foreach ($info as $k => $v) {
197+
if (is_string($v)) {
198+
if ($v === '') {
199+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('""'), 4, $log_output);
200+
} else {
201+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4, $log_output);
202+
}
203+
} elseif (is_array($v) && !is_assoc_array($v)) {
204+
if ($v === []) {
205+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow('[]'), 4, $log_output);
206+
continue;
207+
}
208+
$first = array_shift($v);
209+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4, $log_output);
210+
foreach ($v as $vs) {
211+
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4, $log_output);
212+
}
213+
} elseif (is_bool($v) || is_null($v)) {
214+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::cyan($v === true ? 'true' : ($v === false ? 'false' : 'null')), 4, $log_output);
215+
} else {
216+
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow(json_encode($v, JSON_PRETTY_PRINT)), 4, $log_output);
217+
}
159218
}
160219
}
161220
}

src/SPC/exception/SPCException.php

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
namespace SPC\exception;
66

77
use SPC\builder\BuilderBase;
8-
use SPC\builder\freebsd\BSDBuilder;
98
use SPC\builder\freebsd\library\BSDLibraryBase;
109
use SPC\builder\LibraryBase;
1110
use SPC\builder\linux\library\LinuxLibraryBase;
12-
use SPC\builder\linux\LinuxBuilder;
1311
use SPC\builder\macos\library\MacOSLibraryBase;
14-
use SPC\builder\macos\MacOSBuilder;
1512
use SPC\builder\windows\library\WindowsLibraryBase;
16-
use SPC\builder\windows\WindowsBuilder;
1713

1814
/**
1915
* Base class for SPC exceptions.
@@ -24,8 +20,6 @@
2420
*/
2521
abstract class SPCException extends \Exception
2622
{
27-
private static ?array $build_php_extra_info = null;
28-
2923
private ?array $library_info = null;
3024

3125
private ?array $extension_info = null;
@@ -40,11 +34,6 @@ public function __construct(string $message = '', int $code = 0, ?\Throwable $pr
4034
$this->loadStackTraceInfo();
4135
}
4236

43-
public static function bindBuildPHPExtraInfo(array $indent_texts): void
44-
{
45-
self::$build_php_extra_info = $indent_texts;
46-
}
47-
4837
public function bindExtensionInfo(array $extension_info): void
4938
{
5039
$this->extension_info = $extension_info;
@@ -55,11 +44,6 @@ public function addExtraLogFile(string $key, string $filename): void
5544
$this->extra_log_files[$key] = $filename;
5645
}
5746

58-
public function getBuildPHPExtraInfo(): ?array
59-
{
60-
return self::$build_php_extra_info;
61-
}
62-
6347
/**
6448
* Returns an array containing information about the SPC module.
6549
*
@@ -82,8 +66,7 @@ public function getLibraryInfo(): ?array
8266
* Returns an array containing information about the PHP build process.
8367
*
8468
* @return null|array{
85-
* builder_class: string,
86-
* os: string,
69+
* builder_function: string,
8770
* file: null|string,
8871
* line: null|int,
8972
* } an array containing PHP build information
@@ -124,7 +107,7 @@ private function loadStackTraceInfo(): void
124107
}
125108

126109
// Check if the class is a subclass of LibraryBase
127-
if (!$this->library_info && is_subclass_of($frame['class'], LibraryBase::class)) {
110+
if (!$this->library_info && is_a($frame['class'], LibraryBase::class, true)) {
128111
try {
129112
$reflection = new \ReflectionClass($frame['class']);
130113
if ($reflection->hasConstant('NAME')) {
@@ -152,21 +135,9 @@ private function loadStackTraceInfo(): void
152135
}
153136

154137
// 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-
}
138+
if (!$this->build_php_info && is_a($frame['class'], BuilderBase::class, true)) {
160139
$this->build_php_info = [
161-
'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-
},
140+
'builder_function' => $frame['function'],
170141
'file' => $frame['file'] ?? null,
171142
'line' => $frame['line'] ?? null,
172143
];

0 commit comments

Comments
 (0)