Refactor all command class exception handling

This commit is contained in:
crazywhalecc
2025-08-06 20:45:16 +08:00
committed by Jerry Ma
parent f68f060be2
commit 333b776e77
10 changed files with 691 additions and 643 deletions

View File

@@ -4,18 +4,15 @@ declare(strict_types=1);
namespace SPC\builder; namespace SPC\builder;
use PharIo\FileSystem\File; use SPC\exception\BuildFailureException;
use SPC\exception\ExceptionHandler;
use SPC\exception\FileSystemException;
use SPC\exception\InterruptException; use SPC\exception\InterruptException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile; use SPC\store\LockFile;
use SPC\store\SourceManager; use SPC\store\SourceManager;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\CustomExt; use SPC\util\AttributeMapper;
abstract class BuilderBase abstract class BuilderBase
{ {
@@ -64,12 +61,11 @@ abstract class BuilderBase
match ($status) { match ($status) {
LIB_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] setup success, took ' . round(microtime(true) - $starttime, 2) . ' s'), LIB_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] setup success, took ' . round(microtime(true) - $starttime, 2) . ' s'),
LIB_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'), LIB_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
LIB_STATUS_BUILD_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
LIB_STATUS_INSTALL_FAILED => logger()->error('lib [' . $lib::NAME . '] install failed'), LIB_STATUS_INSTALL_FAILED => logger()->error('lib [' . $lib::NAME . '] install failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'), default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
}; };
if (in_array($status, [LIB_STATUS_BUILD_FAILED, LIB_STATUS_INSTALL_FAILED])) { if (in_array($status, [LIB_STATUS_BUILD_FAILED, LIB_STATUS_INSTALL_FAILED])) {
throw new RuntimeException('Library [' . $lib::NAME . '] setup failed.'); throw new BuildFailureException('Library [' . $lib::NAME . '] setup failed.');
} }
} }
} }
@@ -254,9 +250,8 @@ abstract class BuilderBase
} }
$ext->buildShared(); $ext->buildShared();
} }
} catch (RuntimeException $e) { } finally {
FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', $extension_dir_line); FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', $extension_dir_line);
throw $e;
} }
FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', $extension_dir_line); FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', $extension_dir_line);
FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', '# test "[$]$1" = "no" && $1=yes', 'test "[$]$1" = "no" && $1=yes'); FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', '# test "[$]$1" = "no" && $1=yes', 'test "[$]$1" = "no" && $1=yes');
@@ -311,7 +306,7 @@ abstract class BuilderBase
return intval($match[1]); return intval($match[1]);
} }
throw new RuntimeException('PHP version file format is malformed, please remove it and download again'); throw new WrongUsageException('PHP version file format is malformed, please remove "./source/php-src" dir and download/extract again');
} }
public function getPHPVersion(bool $exception_on_failure = true): string public function getPHPVersion(bool $exception_on_failure = true): string
@@ -329,7 +324,7 @@ abstract class BuilderBase
if (!$exception_on_failure) { if (!$exception_on_failure) {
return 'unknown'; return 'unknown';
} }
throw new RuntimeException('PHP version file format is malformed, please remove it and download again'); throw new WrongUsageException('PHP version file format is malformed, please remove it and download again');
} }
/** /**
@@ -476,7 +471,7 @@ abstract class BuilderBase
foreach ($patches as $patch) { foreach ($patches as $patch) {
try { try {
if (!file_exists($patch)) { if (!file_exists($patch)) {
throw new RuntimeException("Additional patch script file {$patch} not found!"); throw new WrongUsageException("Additional patch script file {$patch} not found!");
} }
logger()->debug('Running additional patch script: ' . $patch); logger()->debug('Running additional patch script: ' . $patch);
require $patch; require $patch;
@@ -489,11 +484,6 @@ abstract class BuilderBase
exit($e->getCode()); exit($e->getCode());
} catch (\Throwable $e) { } catch (\Throwable $e) {
logger()->critical('Patch script ' . $patch . ' failed to run.'); logger()->critical('Patch script ' . $patch . ' failed to run.');
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Please check with --debug option to see more details.');
}
throw $e; throw $e;
} }
} }

View File

@@ -9,8 +9,8 @@ use Laravel\Prompts\Prompt;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
use SPC\ConsoleApplication; use SPC\ConsoleApplication;
use SPC\exception\ExceptionHandler; use SPC\exception\ExceptionHandler;
use SPC\exception\ValidationException; use SPC\exception\SPCException;
use SPC\exception\WrongUsageException; use SPC\util\AttributeMapper;
use SPC\util\GlobalEnvManager; use SPC\util\GlobalEnvManager;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\QuestionHelper;
@@ -32,6 +32,7 @@ abstract class BaseCommand extends Command
parent::__construct($name); parent::__construct($name);
$this->addOption('debug', null, null, 'Enable debug mode'); $this->addOption('debug', null, null, 'Enable debug mode');
$this->addOption('no-motd', null, null, 'Disable motd'); $this->addOption('no-motd', null, null, 'Disable motd');
$this->addOption('preserve-log', null, null, 'Preserve log files, do not delete them on initialized');
} }
public function initialize(InputInterface $input, OutputInterface $output): void public function initialize(InputInterface $input, OutputInterface $output): void
@@ -93,31 +94,21 @@ abstract class BaseCommand extends Command
GlobalEnvManager::init(); GlobalEnvManager::init();
f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes'); f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes');
} }
if ($this->shouldExecute()) {
try { try {
// show raw argv list for logger()->debug // show raw argv list for logger()->debug
logger()->debug('argv: ' . implode(' ', $_SERVER['argv'])); logger()->debug('argv: ' . implode(' ', $_SERVER['argv']));
return $this->handle(); return $this->handle();
} catch (ValidationException|WrongUsageException $e) { } /* @noinspection PhpRedundantCatchClauseInspection */ catch (SPCException $e) {
$msg = explode("\n", $e->getMessage()); // Handle SPCException and log it
foreach ($msg as $v) { ExceptionHandler::handleSPCException($e);
logger()->error($v);
}
return static::FAILURE; return static::FAILURE;
} catch (\Throwable $e) { } catch (\Throwable $e) {
if ($this->getOption('debug')) { // Handle any other exceptions
ExceptionHandler::getInstance()->handle($e); ExceptionHandler::handleDefaultException($e);
} else {
$msg = explode("\n", $e->getMessage());
foreach ($msg as $v) {
logger()->error($v);
}
}
return static::FAILURE; return static::FAILURE;
} }
} }
return static::SUCCESS;
}
protected function getOption(string $name): mixed protected function getOption(string $name): mixed
{ {
@@ -129,11 +120,6 @@ abstract class BaseCommand extends Command
return $this->input->getArgument($name); return $this->input->getArgument($name);
} }
protected function shouldExecute(): bool
{
return true;
}
protected function logWithResult(bool $result, string $success_msg, string $fail_msg): int protected function logWithResult(bool $result, string $success_msg, string $fail_msg): int
{ {
if ($result) { if ($result) {

View File

@@ -50,7 +50,6 @@ class BuildLibsCommand extends BuildCommand
} }
} }
try {
// 构建对象 // 构建对象
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
// 只编译 library 的情况下,标记 // 只编译 library 的情况下,标记
@@ -68,14 +67,5 @@ class BuildLibsCommand extends BuildCommand
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info('Build libs complete, used ' . $time . ' s !'); logger()->info('Build libs complete, used ' . $time . ' s !');
return static::SUCCESS; return static::SUCCESS;
} catch (\Throwable $e) {
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Build failed with ' . get_class($e) . ': ' . $e->getMessage());
logger()->critical('Please check with --debug option to see more details.');
}
return static::FAILURE;
}
} }
} }

View File

@@ -5,8 +5,7 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\exception\ExceptionHandler; use SPC\exception\SPCException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
@@ -119,7 +118,6 @@ class BuildPHPCommand extends BuildCommand
logger()->warning('Some cases micro.sfx cannot be packed via UPX due to dynamic size bug, be aware!'); logger()->warning('Some cases micro.sfx cannot be packed via UPX due to dynamic size bug, be aware!');
} }
} }
try {
// create builder // create builder
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
$include_suggest_ext = $this->getOption('with-suggested-exts'); $include_suggest_ext = $this->getOption('with-suggested-exts');
@@ -167,6 +165,8 @@ 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);
logger()->notice('Build will start after 2s ...'); logger()->notice('Build will start after 2s ...');
sleep(2); sleep(2);
@@ -266,20 +266,6 @@ class BuildPHPCommand extends BuildCommand
$path = FileSystem::convertPath("{$build_root_path}/license/"); $path = FileSystem::convertPath("{$build_root_path}/license/");
logger()->info("License path{$fixed}: {$path}"); logger()->info("License path{$fixed}: {$path}");
return static::SUCCESS; return static::SUCCESS;
} catch (WrongUsageException $e) {
// WrongUsageException is not an exception, it's a user error, so we just print the error message
logger()->critical($e->getMessage());
logger()->error($e->getTraceAsString());
return static::FAILURE;
} catch (\Throwable $e) {
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Build failed with ' . get_class($e) . ': ' . $e->getMessage());
logger()->critical('Please check with --debug option to see more details.');
}
return static::FAILURE;
}
} }
/** /**

View File

@@ -4,9 +4,6 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile; use SPC\store\LockFile;
@@ -36,7 +33,6 @@ class DeleteDownloadCommand extends BaseCommand
public function handle(): int public function handle(): int
{ {
try {
// get source list that will be downloaded // get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources')))); $sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) { if (empty($sources)) {
@@ -81,12 +77,5 @@ class DeleteDownloadCommand extends BaseCommand
} }
logger()->info('Delete success!'); logger()->info('Delete success!');
return static::SUCCESS; return static::SUCCESS;
} catch (DownloaderException $e) {
logger()->error($e->getMessage());
return static::FAILURE;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());
return static::FAILURE;
}
} }
} }

View File

@@ -6,9 +6,7 @@ namespace SPC\command;
use SPC\builder\traits\UnixSystemUtilTrait; use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\SPCException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\LockFile; use SPC\store\LockFile;
@@ -87,7 +85,6 @@ class DownloadCommand extends BaseCommand
public function handle(): int public function handle(): int
{ {
try {
if ($this->getOption('clean')) { if ($this->getOption('clean')) {
return $this->_clean(); return $this->_clean();
} }
@@ -227,7 +224,7 @@ class DownloadCommand extends BaseCommand
logger()->info("[{$ni}/{$cnt}] Downloading source {$source}"); logger()->info("[{$ni}/{$cnt}] Downloading source {$source}");
try { try {
Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list)); Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list));
} /* @noinspection PhpRedundantCatchClauseInspection */ catch (DownloaderException|RuntimeException $e) { } catch (SPCException $e) {
// if `--no-alt` option is set, we will not download alternative sources // if `--no-alt` option is set, we will not download alternative sources
if ($this->getOption('no-alt')) { if ($this->getOption('no-alt')) {
throw $e; throw $e;
@@ -251,13 +248,6 @@ class DownloadCommand extends BaseCommand
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info('Download complete, used ' . $time . ' s !'); logger()->info('Download complete, used ' . $time . ' s !');
return static::SUCCESS; return static::SUCCESS;
} catch (DownloaderException $e) {
logger()->error($e->getMessage());
return static::FAILURE;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());
return static::FAILURE;
}
} }
private function downloadFromZip(string $path): int private function downloadFromZip(string $path): int
@@ -276,28 +266,24 @@ class DownloadCommand extends BaseCommand
} }
// unzip command check // unzip command check
if (PHP_OS_FAMILY !== 'Windows' && !$this->findCommand('unzip')) { if (PHP_OS_FAMILY !== 'Windows' && !$this->findCommand('unzip')) {
logger()->critical('Missing unzip command, you need to install it first !'); $this->output->writeln('Missing unzip command, you need to install it first !');
logger()->critical('You can use "bin/spc doctor" command to check and install required tools'); $this->output->writeln('You can use "bin/spc doctor" command to check and install required tools');
return static::FAILURE; return static::FAILURE;
} }
// create downloads // create downloads
try { if (PHP_OS_FAMILY === 'Windows') {
if (PHP_OS_FAMILY !== 'Windows') {
$abs_path = realpath($path);
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path));
} else {
// Windows TODO // Windows TODO
throw new WrongUsageException('Windows currently does not support --from-zip !'); $this->output->writeln('<error>Windows currently does not support --from-zip !</error>');
}
if (!file_exists(LockFile::LOCK_FILE)) {
throw new RuntimeException('.lock.json not exist in "downloads/"');
}
} catch (RuntimeException $e) {
logger()->critical('Extract failed: ' . $e->getMessage());
return static::FAILURE; return static::FAILURE;
} }
logger()->info('Extract success'); $abs_path = realpath($path);
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path));
if (!file_exists(LockFile::LOCK_FILE)) {
$this->output->writeln('<error>.lock.json not exist in "downloads/", please run "bin/spc download" first !</error>');
return static::FAILURE;
}
$this->output->writeln('<info>Extract success</info>');
return static::SUCCESS; return static::SUCCESS;
} }

View File

@@ -5,9 +5,6 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\builder\traits\UnixSystemUtilTrait; use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\PackageManager; use SPC\store\PackageManager;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
@@ -30,7 +27,6 @@ class InstallPkgCommand extends BaseCommand
public function handle(): int public function handle(): int
{ {
try {
// Use shallow-clone can reduce git resource download // Use shallow-clone can reduce git resource download
if ($this->getOption('shallow-clone')) { if ($this->getOption('shallow-clone')) {
define('GIT_SHALLOW_CLONE', true); define('GIT_SHALLOW_CLONE', true);
@@ -84,12 +80,5 @@ class InstallPkgCommand extends BaseCommand
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info('Install packages complete, used ' . $time . ' s !'); logger()->info('Install packages complete, used ' . $time . ' s !');
return static::SUCCESS; return static::SUCCESS;
} catch (DownloaderException $e) {
logger()->error($e->getMessage());
return static::FAILURE;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());
return static::FAILURE;
}
} }
} }

View File

@@ -7,10 +7,7 @@ namespace SPC\command\dev;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\LibraryBase; use SPC\builder\LibraryBase;
use SPC\command\BuildCommand; use SPC\command\BuildCommand;
use SPC\exception\ExceptionHandler; use SPC\exception\ValidationException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile; use SPC\store\LockFile;
@@ -30,7 +27,6 @@ class PackLibCommand extends BuildCommand
public function handle(): int public function handle(): int
{ {
try {
$lib_name = $this->getArgument('library'); $lib_name = $this->getArgument('library');
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
$builder->setLibsOnly(); $builder->setLibsOnly();
@@ -135,15 +131,6 @@ class PackLibCommand extends BuildCommand
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info('Build libs complete, used ' . $time . ' s !'); logger()->info('Build libs complete, used ' . $time . ' s !');
return static::SUCCESS; return static::SUCCESS;
} catch (\Throwable $e) {
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Build failed with ' . get_class($e) . ': ' . $e->getMessage());
logger()->critical('Please check with --debug option to see more details.');
}
return static::FAILURE;
}
} }
private function sanityCheckLib(LibraryBase $lib): void private function sanityCheckLib(LibraryBase $lib): void
@@ -152,7 +139,10 @@ class PackLibCommand extends BuildCommand
// config // config
foreach ($lib->getStaticLibs() as $static_lib) { foreach ($lib->getStaticLibs() as $static_lib) {
if (!file_exists(FileSystem::convertPath(BUILD_LIB_PATH . '/' . $static_lib))) { if (!file_exists(FileSystem::convertPath(BUILD_LIB_PATH . '/' . $static_lib))) {
throw new RuntimeException('Static library ' . $static_lib . ' not found in ' . BUILD_LIB_PATH); throw new ValidationException(
'Static library ' . $static_lib . ' not found in ' . BUILD_LIB_PATH,
validation_module: "Static library {$static_lib} existence check"
);
} }
} }
} }

View File

@@ -4,24 +4,167 @@ declare(strict_types=1);
namespace SPC\exception; namespace SPC\exception;
use ZM\Logger\ConsoleColor;
class ExceptionHandler class ExceptionHandler
{ {
protected mixed $whoops = null; 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,
];
private static ?ExceptionHandler $obj = null; public const array MINOR_LOG_EXCEPTIONS = [
InterruptException::class,
WrongUsageException::class,
];
public static function getInstance(): ExceptionHandler public static function handleSPCException(SPCException $e): void
{ {
if (self::$obj === null) { // XXX error: yyy
self::$obj = new self(); $head_msg = match ($class = get_class($e)) {
} BuildFailureException::class => "Build failed: {$e->getMessage()}",
return self::$obj; 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()}",
InterruptException::class => "⚠ Build interrupted by user: {$e->getMessage()}",
PatchException::class => "Patch apply failed: {$e->getMessage()}",
SPCInternalException::class => "SPC internal error: {$e->getMessage()}",
ValidationException::class => "Validation failed: {$e->getMessage()}",
WrongUsageException::class => $e->getMessage(),
default => "Unknown SPC exception {$class}: {$e->getMessage()}",
};
self::logError($head_msg);
// ----------------------------------------
$minor_logs = in_array($class, self::MINOR_LOG_EXCEPTIONS, true);
if ($minor_logs) {
return;
} }
public function handle(\Throwable $e): void self::logError("----------------------------------------\n");
// get the SPCException module
if ($php_info = $e->getBuildPHPInfo()) {
self::logError('✗ Failed module: ' . ConsoleColor::yellow("PHP builder {$php_info['builder_class']} for {$php_info['os']}"));
} elseif ($lib_info = $e->getLibraryInfo()) {
self::logError('✗ Failed module: ' . ConsoleColor::yellow("library {$lib_info['library_name']} builder for {$lib_info['os']}"));
} elseif ($ext_info = $e->getExtensionInfo()) {
self::logError('✗ Failed module: ' . ConsoleColor::yellow("shared extension {$ext_info['extension_name']} builder"));
} elseif (!in_array($class, self::KNOWN_EXCEPTIONS)) {
self::logError('✗ Failed From: ' . ConsoleColor::yellow('Unknown SPC module ' . $class));
}
self::logError('');
// get command execution info
if ($e instanceof ExecutionException) {
self::logError('✗ Failed command: ' . ConsoleColor::yellow($e->getExecutionCommand()));
if ($cd = $e->getCd()) {
self::logError('✗ Command executed in: ' . ConsoleColor::yellow($cd));
}
if ($env = $e->getEnv()) {
self::logError('✗ Command inline env variables:');
foreach ($env as $k => $v) {
self::logError(ConsoleColor::yellow("{$k}={$v}"), 4);
}
}
}
// validation error
if ($e instanceof ValidationException) {
self::logError('✗ Failed validation module: ' . ConsoleColor::yellow($e->getValidationModuleString()));
}
// environment error
if ($e instanceof EnvironmentException) {
self::logError('✗ Failed environment check: ' . ConsoleColor::yellow($e->getMessage()));
if (($solution = $e->getSolution()) !== null) {
self::logError('✗ Solution: ' . ConsoleColor::yellow($solution));
}
}
// get patch info
if ($e instanceof PatchException) {
self::logError("✗ Failed patch module: {$e->getPatchModule()}");
}
// get internal trace
if ($e instanceof SPCInternalException) {
self::logError('✗ Internal trace:');
self::logError(ConsoleColor::gray("{$e->getTraceAsString()}\n"), 4);
}
// get the full build info if possible
if (($info = $e->getBuildPHPExtraInfo()) && defined('DEBUG_MODE')) {
self::logError('✗ Build PHP extra info:');
$maxlen = 0;
foreach ($info as $k => $v) {
$maxlen = max(strlen($k), $maxlen);
}
foreach ($info as $k => $v) {
if (is_string($v)) {
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($v), 4);
} elseif (is_array($v) && !is_assoc_array($v)) {
$first = array_shift($v);
self::logError($k . ': ' . str_pad('', $maxlen - strlen($k)) . ConsoleColor::yellow($first), 4);
foreach ($v as $vs) {
self::logError(str_pad('', $maxlen + 2) . ConsoleColor::yellow($vs), 4);
}
}
}
}
self::logError("\n----------------------------------------\n");
// put getenv info to log
$env_log = fopen(SPC_ENV_LOG, 'a');
$env_info = getenv();
if ($env_info) {
foreach ($env_info as $k => $v) {
fwrite($env_log, $k . ' = ' . $v . PHP_EOL);
}
}
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.');
}
}
public static function handleDefaultException(\Throwable $e): void
{ {
logger()->error('Uncaught ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'); $class = get_class($e);
logger()->error($e->getTraceAsString()); self::logError("Unhandled exception {$class}: {$e->getMessage()}\n\t{$e->getMessage()}\n");
logger()->critical('You can report this exception to static-php-cli GitHub repo.'); self::logError('Stack trace:');
self::logError(ConsoleColor::gray($e->getTraceAsString()), 4);
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
{
$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);
echo ConsoleColor::red($line) . PHP_EOL;
}
} }
} }

View File

@@ -9,7 +9,6 @@ use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\LibraryBase; use SPC\builder\LibraryBase;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile; use SPC\store\LockFile;
@@ -88,7 +87,7 @@ class BuilderTest extends TestCase
if ($cnt !== 0) { if ($cnt !== 0) {
$this->assertEquals(intval($match[1]), $this->builder->getPHPVersionID()); $this->assertEquals(intval($match[1]), $this->builder->getPHPVersionID());
} else { } else {
$this->expectException(RuntimeException::class); $this->expectException(WrongUsageException::class);
$this->builder->getPHPVersionID(); $this->builder->getPHPVersionID();
} }
} else { } else {
@@ -105,7 +104,7 @@ class BuilderTest extends TestCase
if ($cnt !== 0) { if ($cnt !== 0) {
$this->assertEquals($match[1], $this->builder->getPHPVersion()); $this->assertEquals($match[1], $this->builder->getPHPVersion());
} else { } else {
$this->expectException(RuntimeException::class); $this->expectException(WrongUsageException::class);
$this->builder->getPHPVersion(); $this->builder->getPHPVersion();
} }
} else { } else {
@@ -246,7 +245,7 @@ class BuilderTest extends TestCase
public function testEmitPatchPointNotExists() public function testEmitPatchPointNotExists()
{ {
$this->expectOutputRegex('/failed to run/'); $this->expectOutputRegex('/failed to run/');
$this->expectException(RuntimeException::class); $this->expectException(WrongUsageException::class);
$this->builder->setOption('with-added-patch', ['/tmp/patch-point.not_exsssists.php']); $this->builder->setOption('with-added-patch', ['/tmp/patch-point.not_exsssists.php']);
$this->builder->emitPatchPoint('not-exists'); $this->builder->emitPatchPoint('not-exists');
} }