mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-13 20:05:35 +08:00
Add DumpStagesCommand to dump package stages and their locations
This commit is contained in:
157
src/StaticPHP/Command/Dev/DumpStagesCommand.php
Normal file
157
src/StaticPHP/Command/Dev/DumpStagesCommand.php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace StaticPHP\Command\Dev;
|
||||||
|
|
||||||
|
use StaticPHP\Command\BaseCommand;
|
||||||
|
use StaticPHP\Registry\PackageLoader;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
|
#[AsCommand('dev:dump-stages', 'Dump all package stages with their file locations for quick indexing')]
|
||||||
|
class DumpStagesCommand extends BaseCommand
|
||||||
|
{
|
||||||
|
protected bool $no_motd = true;
|
||||||
|
|
||||||
|
public function configure(): void
|
||||||
|
{
|
||||||
|
$this->addArgument('packages', InputArgument::OPTIONAL, 'Comma-separated list of packages to dump, e.g. "openssl,zlib,curl". Dumps all packages if omitted.');
|
||||||
|
$this->addArgument('output', InputArgument::OPTIONAL, 'Output file path', ROOT_DIR . '/dump-stages.json');
|
||||||
|
$this->addOption('relative', 'r', InputOption::VALUE_NONE, 'Output file paths relative to ROOT_DIR');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$outputFile = $this->getArgument('output');
|
||||||
|
$useRelative = (bool) $this->getOption('relative');
|
||||||
|
|
||||||
|
$filterPackages = null;
|
||||||
|
if ($packagesArg = $this->getArgument('packages')) {
|
||||||
|
$filterPackages = array_flip(parse_comma_list($packagesArg));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach (PackageLoader::getPackages() as $name => $pkg) {
|
||||||
|
if ($filterPackages !== null && !isset($filterPackages[$name])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$entry = [
|
||||||
|
'type' => $pkg->getType(),
|
||||||
|
'stages' => [],
|
||||||
|
'before_stages' => [],
|
||||||
|
'after_stages' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Resolve main stages
|
||||||
|
foreach ($pkg->getStages() as $stageName => $callable) {
|
||||||
|
$location = $this->resolveCallableLocation($callable);
|
||||||
|
if ($location !== null && $useRelative) {
|
||||||
|
$location['file'] = $this->toRelativePath($location['file']);
|
||||||
|
}
|
||||||
|
$entry['stages'][$stageName] = $location;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[$name] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve before/after stage external callbacks
|
||||||
|
foreach (PackageLoader::getAllBeforeStages() as $pkgName => $stages) {
|
||||||
|
if ($filterPackages !== null && !isset($filterPackages[$pkgName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($stages as $stageName => $callbacks) {
|
||||||
|
foreach ($callbacks as [$callable, $onlyWhen]) {
|
||||||
|
$location = $this->resolveCallableLocation($callable);
|
||||||
|
if ($location !== null && $useRelative) {
|
||||||
|
$location['file'] = $this->toRelativePath($location['file']);
|
||||||
|
}
|
||||||
|
$entry_data = $location ?? [];
|
||||||
|
if ($onlyWhen !== null) {
|
||||||
|
$entry_data['only_when_package_resolved'] = $onlyWhen;
|
||||||
|
}
|
||||||
|
$result[$pkgName]['before_stages'][$stageName][] = $entry_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (PackageLoader::getAllAfterStages() as $pkgName => $stages) {
|
||||||
|
if ($filterPackages !== null && !isset($filterPackages[$pkgName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($stages as $stageName => $callbacks) {
|
||||||
|
foreach ($callbacks as [$callable, $onlyWhen]) {
|
||||||
|
$location = $this->resolveCallableLocation($callable);
|
||||||
|
if ($location !== null && $useRelative) {
|
||||||
|
$location['file'] = $this->toRelativePath($location['file']);
|
||||||
|
}
|
||||||
|
$entry_data = $location ?? [];
|
||||||
|
if ($onlyWhen !== null) {
|
||||||
|
$entry_data['only_when_package_resolved'] = $onlyWhen;
|
||||||
|
}
|
||||||
|
$result[$pkgName]['after_stages'][$stageName][] = $entry_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
file_put_contents($outputFile, $json . PHP_EOL);
|
||||||
|
|
||||||
|
$this->output->writeln('<info>Dumped stages for ' . count($result) . " package(s) to: {$outputFile}</info>");
|
||||||
|
return static::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the file, start line, class and method name of a callable using reflection.
|
||||||
|
*
|
||||||
|
* @return null|array{file: string, line: false|int, class: string, method: string}
|
||||||
|
*/
|
||||||
|
private function resolveCallableLocation(mixed $callable): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (is_array($callable) && count($callable) === 2) {
|
||||||
|
$ref = new \ReflectionMethod($callable[0], $callable[1]);
|
||||||
|
return [
|
||||||
|
'class' => $ref->getDeclaringClass()->getName(),
|
||||||
|
'method' => $ref->getName(),
|
||||||
|
'file' => (string) $ref->getFileName(),
|
||||||
|
'line' => $ref->getStartLine(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($callable instanceof \Closure) {
|
||||||
|
$ref = new \ReflectionFunction($callable);
|
||||||
|
$scopeClass = $ref->getClosureScopeClass();
|
||||||
|
return [
|
||||||
|
'class' => $scopeClass !== null ? $scopeClass->getName() : '{closure}',
|
||||||
|
'method' => '{closure}',
|
||||||
|
'file' => (string) $ref->getFileName(),
|
||||||
|
'line' => $ref->getStartLine(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (is_string($callable) && str_contains($callable, '::')) {
|
||||||
|
[$class, $method] = explode('::', $callable, 2);
|
||||||
|
$ref = new \ReflectionMethod($class, $method);
|
||||||
|
return [
|
||||||
|
'class' => $ref->getDeclaringClass()->getName(),
|
||||||
|
'method' => $ref->getName(),
|
||||||
|
'file' => (string) $ref->getFileName(),
|
||||||
|
'line' => $ref->getStartLine(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (\ReflectionException) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toRelativePath(string $absolutePath): string
|
||||||
|
{
|
||||||
|
$root = rtrim(ROOT_DIR, '/') . '/';
|
||||||
|
if (str_starts_with($absolutePath, $root)) {
|
||||||
|
return substr($absolutePath, strlen($root));
|
||||||
|
}
|
||||||
|
return $absolutePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace StaticPHP;
|
|||||||
|
|
||||||
use StaticPHP\Command\BuildLibsCommand;
|
use StaticPHP\Command\BuildLibsCommand;
|
||||||
use StaticPHP\Command\BuildTargetCommand;
|
use StaticPHP\Command\BuildTargetCommand;
|
||||||
|
use StaticPHP\Command\Dev\DumpStagesCommand;
|
||||||
use StaticPHP\Command\Dev\EnvCommand;
|
use StaticPHP\Command\Dev\EnvCommand;
|
||||||
use StaticPHP\Command\Dev\IsInstalledCommand;
|
use StaticPHP\Command\Dev\IsInstalledCommand;
|
||||||
use StaticPHP\Command\Dev\LintConfigCommand;
|
use StaticPHP\Command\Dev\LintConfigCommand;
|
||||||
@@ -67,6 +68,7 @@ class ConsoleApplication extends Application
|
|||||||
new EnvCommand(),
|
new EnvCommand(),
|
||||||
new LintConfigCommand(),
|
new LintConfigCommand(),
|
||||||
new PackLibCommand(),
|
new PackLibCommand(),
|
||||||
|
new DumpStagesCommand(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// add additional commands from registries
|
// add additional commands from registries
|
||||||
|
|||||||
@@ -128,6 +128,16 @@ abstract class Package
|
|||||||
$this->stages[$name] = $stage;
|
$this->stages[$name] = $stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all defined stages for this package.
|
||||||
|
*
|
||||||
|
* @return array<string, callable>
|
||||||
|
*/
|
||||||
|
public function getStages(): array
|
||||||
|
{
|
||||||
|
return $this->stages;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the package has a specific stage defined.
|
* Check if the package has a specific stage defined.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -240,6 +240,26 @@ class PackageLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered before-stage callbacks (raw).
|
||||||
|
*
|
||||||
|
* @return array<string, array<string, list<array{0: callable, 1: ?string}>>>
|
||||||
|
*/
|
||||||
|
public static function getAllBeforeStages(): array
|
||||||
|
{
|
||||||
|
return self::$before_stages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered after-stage callbacks (raw).
|
||||||
|
*
|
||||||
|
* @return array<string, array<string, list<array{0: callable, 1: ?string}>>>
|
||||||
|
*/
|
||||||
|
public static function getAllAfterStages(): array
|
||||||
|
{
|
||||||
|
return self::$after_stages;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getBeforeStageCallbacks(string $package_name, string $stage): iterable
|
public static function getBeforeStageCallbacks(string $package_name, string $stage): iterable
|
||||||
{
|
{
|
||||||
// match condition
|
// match condition
|
||||||
|
|||||||
Reference in New Issue
Block a user