Skip to content

Commit 722bb31

Browse files
committed
Introduce AttributeMapper for managing extensions and doctor attributes
1 parent e28580d commit 722bb31

File tree

14 files changed

+336
-263
lines changed

14 files changed

+336
-263
lines changed

src/SPC/builder/BuilderBase.php

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,14 @@ abstract class BuilderBase
4646
/**
4747
* Convert libraries to class
4848
*
49-
* @param array<string> $sorted_libraries Libraries to build (if not empty, must sort first)
50-
* @throws FileSystemException
51-
* @throws RuntimeException
52-
* @throws WrongUsageException
49+
* @param array<string> $sorted_libraries Libraries to build (if not empty, must sort first)
50+
*
5351
* @internal
5452
*/
5553
abstract public function proveLibs(array $sorted_libraries);
5654

5755
/**
5856
* Set-Up libraries
59-
*
60-
* @throws FileSystemException
61-
* @throws RuntimeException
62-
* @throws WrongUsageException
6357
*/
6458
public function setupLibs(): void
6559
{
@@ -139,9 +133,6 @@ public function getExts(bool $including_shared = true): array
139133

140134
/**
141135
* Check if there is a cpp extensions or libraries.
142-
*
143-
* @throws FileSystemException
144-
* @throws WrongUsageException
145136
*/
146137
public function hasCpp(): bool
147138
{
@@ -174,15 +165,10 @@ public function setLibsOnly(bool $status = true): void
174165
/**
175166
* Verify the list of "ext" extensions for validity and declare an Extension object to check the dependencies of the extensions.
176167
*
177-
* @throws FileSystemException
178-
* @throws RuntimeException
179-
* @throws \ReflectionException
180-
* @throws \Throwable|WrongUsageException
181168
* @internal
182169
*/
183170
public function proveExts(array $static_extensions, array $shared_extensions = [], bool $skip_check_deps = false, bool $skip_extract = false): void
184171
{
185-
CustomExt::loadCustomExt();
186172
// judge ext
187173
foreach ($static_extensions as $ext) {
188174
// if extension does not support static build, throw exception
@@ -213,7 +199,7 @@ public function proveExts(array $static_extensions, array $shared_extensions = [
213199
}
214200

215201
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
216-
$class = CustomExt::getExtClass($extension);
202+
$class = AttributeMapper::getExtensionClassByName($extension) ?? Extension::class;
217203
/** @var Extension $ext */
218204
$ext = new $class($extension, $this);
219205
if (in_array($extension, $static_extensions)) {
@@ -247,11 +233,6 @@ abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE);
247233
*/
248234
abstract public function testPHP(int $build_target = BUILD_TARGET_NONE);
249235

250-
/**
251-
* @throws WrongUsageException
252-
* @throws RuntimeException
253-
* @throws FileSystemException
254-
*/
255236
public function buildSharedExts(): void
256237
{
257238
$lines = file(BUILD_BIN_PATH . '/php-config');
@@ -284,9 +265,6 @@ public function buildSharedExts(): void
284265
/**
285266
* Generate extension enable arguments for configure.
286267
* e.g. --enable-mbstring
287-
*
288-
* @throws FileSystemException
289-
* @throws WrongUsageException
290268
*/
291269
public function makeStaticExtensionArgs(): string
292270
{
@@ -321,9 +299,6 @@ public function isLibsOnly(): bool
321299

322300
/**
323301
* Get PHP Version ID from php-src/main/php_version.h
324-
*
325-
* @throws RuntimeException
326-
* @throws WrongUsageException
327302
*/
328303
public function getPHPVersionID(): int
329304
{

src/SPC/builder/linux/library/imap.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ public function patchPhpConfig(): bool
3939
return false;
4040
}
4141

42-
/**
43-
* @throws RuntimeException
44-
*/
4542
protected function build(): void
4643
{
4744
if ($this->builder->getLib('openssl')) {

src/SPC/command/BaseCommand.php

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,32 +69,24 @@ public function initialize(InputInterface $input, OutputInterface $output): void
6969
}
7070
}
7171

72-
/**
73-
* @throws WrongUsageException
74-
*/
7572
abstract public function handle(): int;
7673

7774
protected function execute(InputInterface $input, OutputInterface $output): int
7875
{
7976
$this->input = $input;
8077
$this->output = $output;
8178

82-
global $ob_logger;
83-
if ($input->getOption('debug') || $output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
84-
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$input->getOption('no-ansi'));
85-
define('DEBUG_MODE', true);
86-
} else {
87-
$ob_logger = new ConsoleLogger(decorated: !$input->getOption('no-ansi'));
88-
}
79+
// init log
80+
$this->initLogFiles();
8981

90-
// windows fallback
91-
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
92-
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {
93-
$helper = new QuestionHelper();
94-
$case = $prompt->default ? ' [Y/n] ' : ' [y/N] ';
95-
$question = new ConfirmationQuestion($prompt->label . $case, $prompt->default);
96-
return $helper->ask($input, $output, $question);
97-
});
82+
// init logger
83+
$this->initConsoleLogger();
84+
85+
// load attribute maps
86+
AttributeMapper::init();
87+
88+
// init windows fallback
89+
$this->initWindowsPromptFallback($input, $output);
9890

9991
// init GlobalEnv
10092
if (!$this instanceof BuildCommand) {
@@ -178,4 +170,58 @@ protected function parseExtensionList(array|string $ext_list): array
178170
return true;
179171
}));
180172
}
173+
174+
/**
175+
* Initialize spc log files.
176+
*/
177+
private function initLogFiles(): void
178+
{
179+
$log_dir = SPC_LOGS_DIR;
180+
if (!file_exists($log_dir)) {
181+
mkdir($log_dir, 0755, true);
182+
} elseif (!$this->getOption('preserve-log')) {
183+
// Clean up old log files
184+
$files = glob($log_dir . '/*.log');
185+
foreach ($files as $file) {
186+
if (is_file($file)) {
187+
unlink($file);
188+
}
189+
}
190+
}
191+
}
192+
193+
/**
194+
* Initialize console logger.
195+
*/
196+
private function initConsoleLogger(): void
197+
{
198+
global $ob_logger;
199+
if ($this->input->getOption('debug') || $this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
200+
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$this->input->getOption('no-ansi'));
201+
define('DEBUG_MODE', true);
202+
} else {
203+
$ob_logger = new ConsoleLogger(decorated: !$this->input->getOption('no-ansi'));
204+
}
205+
$log_file_fd = fopen(SPC_OUTPUT_LOG, 'a');
206+
$ob_logger->addLogCallback(function ($level, $output) use ($log_file_fd) {
207+
if ($log_file_fd) {
208+
fwrite($log_file_fd, strip_ansi_colors($output) . "\n");
209+
}
210+
return true;
211+
});
212+
}
213+
214+
/**
215+
* Initialize Windows prompt fallback for laravel-prompts.
216+
*/
217+
private function initWindowsPromptFallback(InputInterface $input, OutputInterface $output): void
218+
{
219+
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
220+
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {
221+
$helper = new QuestionHelper();
222+
$case = $prompt->default ? ' [Y/n] ' : ' [y/N] ';
223+
$question = new ConfirmationQuestion($prompt->label . $case, $prompt->default);
224+
return $helper->ask($input, $output, $question);
225+
});
226+
}
181227
}

src/SPC/command/DoctorCommand.php

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace SPC\command;
66

7-
use SPC\doctor\CheckListHandler;
87
use SPC\doctor\CheckResult;
9-
use SPC\exception\RuntimeException;
8+
use SPC\doctor\DoctorHandler;
9+
use SPC\util\AttributeMapper;
1010
use Symfony\Component\Console\Attribute\AsCommand;
11+
use Symfony\Component\Console\Input\InputOption;
12+
use ZM\Logger\ConsoleColor;
1113

1214
use function Laravel\Prompts\confirm;
1315

@@ -16,72 +18,78 @@ class DoctorCommand extends BaseCommand
1618
{
1719
public function configure(): void
1820
{
19-
$this->addOption('auto-fix', null, null, 'Automatically fix failed items (if possible)');
21+
$this->addOption('auto-fix', null, InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
2022
}
2123

2224
public function handle(): int
2325
{
24-
try {
25-
$checker = new CheckListHandler();
26-
// skipped items
27-
$skip_items = array_filter(explode(',', getenv('SPC_SKIP_DOCTOR_CHECK_ITEMS') ?: ''));
26+
$fix_policy = match ($this->input->getOption('auto-fix')) {
27+
'never' => FIX_POLICY_DIE,
28+
true, null => FIX_POLICY_AUTOFIX,
29+
default => FIX_POLICY_PROMPT,
30+
};
31+
$fix_map = AttributeMapper::getDoctorFixMap();
2832

29-
$fix_policy = $this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT;
30-
foreach ($checker->runChecks() as $check) {
31-
if ($check->limit_os !== null && $check->limit_os !== PHP_OS_FAMILY) {
32-
continue;
33-
}
33+
foreach (DoctorHandler::getValidCheckList() as $check) {
34+
// output
35+
$this->output->write("Checking <comment>{$check->item_name}</comment> ... ");
3436

35-
$this->output->write('Checking <comment>' . $check->item_name . '</comment> ... ');
37+
// null => skipped
38+
if (($result = call_user_func($check->callback)) === null) {
39+
$this->output->writeln('skipped');
40+
continue;
41+
}
42+
// invalid return value => skipped
43+
if (!$result instanceof CheckResult) {
44+
$this->output->writeln('<error>Skipped due to invalid return value</error>');
45+
continue;
46+
}
47+
// true => OK
48+
if ($result->isOK()) {
49+
/* @phpstan-ignore-next-line */
50+
$this->output->writeln($result->getMessage() ?? (string) ConsoleColor::green(''));
51+
continue;
52+
}
3653

37-
// check if this item is skipped
38-
if (in_array($check->item_name, $skip_items) || ($result = call_user_func($check->callback)) === null) {
39-
$this->output->writeln('skipped');
40-
} elseif ($result instanceof CheckResult) {
41-
if ($result->isOK()) {
42-
$this->output->writeln($result->getMessage() ?? 'ok');
43-
continue;
44-
}
54+
// Failed => output error message
55+
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
56+
// If the result is not fixable, fail immediately
57+
if ($result->getFixItem() === '') {
58+
$this->output->writeln('This check item can not be fixed !');
59+
return static::FAILURE;
60+
}
61+
if (!isset($fix_map[$result->getFixItem()])) {
62+
$this->output->writeln("<error>Internal error: Unknown fix item: {$result->getFixItem()}</error>");
63+
return static::FAILURE;
64+
}
4565

46-
// Failed
47-
$this->output->writeln('<error>' . $result->getMessage() . '</error>');
48-
switch ($fix_policy) {
49-
case FIX_POLICY_DIE:
50-
throw new RuntimeException('Some check items can not be fixed !');
51-
case FIX_POLICY_PROMPT:
52-
if ($result->getFixItem() !== '') {
53-
$question = confirm('Do you want to fix it?');
54-
if ($question) {
55-
$checker->emitFix($this->output, $result);
56-
} else {
57-
throw new RuntimeException('You cancelled fix');
58-
}
59-
} else {
60-
throw new RuntimeException('Some check items can not be fixed !');
61-
}
62-
break;
63-
case FIX_POLICY_AUTOFIX:
64-
if ($result->getFixItem() !== '') {
65-
$this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...');
66-
$checker->emitFix($this->output, $result);
67-
} else {
68-
throw new RuntimeException('Some check items can not be fixed !');
69-
}
70-
break;
71-
}
66+
// prompt for fix
67+
if ($fix_policy === FIX_POLICY_PROMPT) {
68+
if (!confirm('Do you want to fix it?')) {
69+
$this->output->writeln('<comment>You canceled fix.</comment>');
70+
return static::FAILURE;
71+
}
72+
if (DoctorHandler::emitFix($this->output, $result)) {
73+
$this->output->writeln('<info>Fix applied successfully!</info>');
74+
} else {
75+
$this->output->writeln('<error>Failed to apply fix!</error>');
76+
return static::FAILURE;
7277
}
7378
}
7479

75-
$this->output->writeln('<info>Doctor check complete !</info>');
76-
} catch (\Throwable $e) {
77-
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
78-
79-
if (extension_loaded('pcntl')) {
80-
pcntl_signal(SIGINT, SIG_IGN);
80+
// auto fix
81+
if ($fix_policy === FIX_POLICY_AUTOFIX) {
82+
$this->output->writeln('Automatically fixing ' . $result->getFixItem() . ' ...');
83+
if (DoctorHandler::emitFix($this->output, $result)) {
84+
$this->output->writeln('<info>Fix applied successfully!</info>');
85+
} else {
86+
$this->output->writeln('<error>Failed to apply fix!</error>');
87+
return static::FAILURE;
88+
}
8189
}
82-
return static::FAILURE;
8390
}
8491

92+
$this->output->writeln('<info>Doctor check complete !</info>');
8593
return static::SUCCESS;
8694
}
8795
}

src/SPC/doctor/AsFixItem.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace SPC\doctor;
66

7-
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
7+
#[\Attribute(\Attribute::TARGET_METHOD)]
88
class AsFixItem
99
{
1010
public function __construct(public string $name) {}

0 commit comments

Comments
 (0)