mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-02 14:25:41 +08:00
600 lines
26 KiB
PHP
600 lines
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace StaticPHP\Registry;
|
|
|
|
use StaticPHP\Attribute\Package\AfterStage;
|
|
use StaticPHP\Attribute\Package\BeforeStage;
|
|
use StaticPHP\Attribute\Package\BuildFor;
|
|
use StaticPHP\Attribute\Package\ConditionalOn;
|
|
use StaticPHP\Attribute\Package\CustomPhpConfigureArg;
|
|
use StaticPHP\Attribute\Package\Extension;
|
|
use StaticPHP\Attribute\Package\Info;
|
|
use StaticPHP\Attribute\Package\InitPackage;
|
|
use StaticPHP\Attribute\Package\Library;
|
|
use StaticPHP\Attribute\Package\PatchBeforeBuild;
|
|
use StaticPHP\Attribute\Package\ResolveBuild;
|
|
use StaticPHP\Attribute\Package\Stage;
|
|
use StaticPHP\Attribute\Package\Target;
|
|
use StaticPHP\Attribute\Package\Tool;
|
|
use StaticPHP\Attribute\Package\Validate;
|
|
use StaticPHP\Config\PackageConfig;
|
|
use StaticPHP\DI\ApplicationContext;
|
|
use StaticPHP\Exception\RegistryException;
|
|
use StaticPHP\Exception\WrongUsageException;
|
|
use StaticPHP\Package\LibraryPackage;
|
|
use StaticPHP\Package\Package;
|
|
use StaticPHP\Package\PackageInstaller;
|
|
use StaticPHP\Package\PhpExtensionPackage;
|
|
use StaticPHP\Package\TargetPackage;
|
|
use StaticPHP\Package\ToolPackage;
|
|
use StaticPHP\Util\FileSystem;
|
|
|
|
class PackageLoader
|
|
{
|
|
/** @var array<string, Package> */
|
|
private static ?array $packages = null;
|
|
|
|
private static array $before_stages = [];
|
|
|
|
private static array $after_stages = [];
|
|
|
|
/** @var array<string, true> Track loaded classes to prevent duplicates */
|
|
private static array $loaded_classes = [];
|
|
|
|
/**
|
|
* Annotation metadata keyed by package name, capturing the defining class and its method-level attributes.
|
|
*
|
|
* @var array<string, array{class: string, methods: array<string, list<array{attr: string, args: array<string, mixed>}>>}>
|
|
*/
|
|
private static array $annotation_map = [];
|
|
|
|
/**
|
|
* Source metadata for #[BeforeStage] hooks, keyed by target package name → stage name.
|
|
*
|
|
* @var array<string, array<string, list<array{class: string, method: string, only_when: ?string, conditionals: list<class-string>}>>>
|
|
*/
|
|
private static array $before_stage_meta = [];
|
|
|
|
/**
|
|
* Source metadata for #[AfterStage] hooks, keyed by target package name → stage name.
|
|
*
|
|
* @var array<string, array<string, list<array{class: string, method: string, only_when: ?string, conditionals: list<class-string>}>>>
|
|
*/
|
|
private static array $after_stage_meta = [];
|
|
|
|
/**
|
|
* Reverse index of #[BeforeStage] hooks, keyed by registering class → target package → stage.
|
|
* Enables O(1) "outbound hook" lookup: what stages does a given class hook into on other packages?
|
|
*
|
|
* @var array<string, array<string, array<string, list<array{method: string, only_when: ?string}>>>>
|
|
*/
|
|
private static array $class_before_stage_meta = [];
|
|
|
|
/**
|
|
* Reverse index of #[AfterStage] hooks, keyed by registering class → target package → stage.
|
|
*
|
|
* @var array<string, array<string, array<string, list<array{method: string, only_when: ?string}>>>>
|
|
*/
|
|
private static array $class_after_stage_meta = [];
|
|
|
|
public static function initPackageInstances(): void
|
|
{
|
|
if (self::$packages !== null) {
|
|
return;
|
|
}
|
|
// init packages instance from config
|
|
foreach (PackageConfig::getAll() as $name => $item) {
|
|
$pkg = match ($item['type']) {
|
|
'target', 'virtual-target' => new TargetPackage($name, $item['type']),
|
|
'library' => new LibraryPackage($name, $item['type']),
|
|
'php-extension' => new PhpExtensionPackage($name, $item['type']),
|
|
'tool' => new ToolPackage($name, $item['type']),
|
|
default => null,
|
|
};
|
|
if ($pkg !== null) {
|
|
self::$packages[$name] = $pkg;
|
|
} else {
|
|
throw new RegistryException("Package [{$name}] has unknown type [{$item['type']}]");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load package definitions from PSR-4 directory.
|
|
*
|
|
* @param string $dir Directory path
|
|
* @param string $base_namespace Base namespace for dir's PSR-4 mapping
|
|
* @param bool $auto_require Whether to auto-require PHP files (for external plugins not in autoload)
|
|
*/
|
|
public static function loadFromPsr4Dir(string $dir, string $base_namespace, bool $auto_require = false): void
|
|
{
|
|
self::initPackageInstances();
|
|
$classes = FileSystem::getClassesPsr4($dir, $base_namespace, auto_require: $auto_require);
|
|
foreach ($classes as $class) {
|
|
self::loadFromClass($class);
|
|
}
|
|
}
|
|
|
|
public static function hasPackage(string $name): bool
|
|
{
|
|
return isset(self::$packages[$name]);
|
|
}
|
|
|
|
/**
|
|
* Get a Package instance by its name.
|
|
*
|
|
* @param string $name The name of the package
|
|
* @return Package Returns the Package instance if found, otherwise null
|
|
*/
|
|
public static function getPackage(string $name): Package
|
|
{
|
|
if (!isset(self::$packages[$name])) {
|
|
throw new WrongUsageException("Package [{$name}] not found.");
|
|
}
|
|
return self::$packages[$name];
|
|
}
|
|
|
|
public static function getTargetPackage(string $name): TargetPackage
|
|
{
|
|
$pkg = self::getPackage($name);
|
|
if ($pkg instanceof TargetPackage) {
|
|
return $pkg;
|
|
}
|
|
throw new WrongUsageException("Package [{$name}] is not a TargetPackage.");
|
|
}
|
|
|
|
public static function getLibraryPackage(string $name): LibraryPackage
|
|
{
|
|
$pkg = self::getPackage($name);
|
|
if ($pkg instanceof LibraryPackage) {
|
|
return $pkg;
|
|
}
|
|
throw new WrongUsageException("Package [{$name}] is not a LibraryPackage.");
|
|
}
|
|
|
|
/**
|
|
* Get all loaded Package instances.
|
|
*/
|
|
public static function getPackages(array|string|null $type_filter = null): iterable
|
|
{
|
|
foreach (self::$packages as $name => $package) {
|
|
if ($type_filter === null) {
|
|
yield $name => $package;
|
|
} elseif ($package->getType() === $type_filter) {
|
|
yield $name => $package;
|
|
} elseif (is_array($type_filter) && in_array($package->getType(), $type_filter, true)) {
|
|
yield $name => $package;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init package instance from defined classes and attributes.
|
|
*
|
|
* @internal
|
|
*/
|
|
public static function loadFromClass(mixed $class): void
|
|
{
|
|
$refClass = new \ReflectionClass($class);
|
|
$class_name = $refClass->getName();
|
|
|
|
// Skip if already loaded to prevent duplicate registrations
|
|
if (isset(self::$loaded_classes[$class_name])) {
|
|
return;
|
|
}
|
|
self::$loaded_classes[$class_name] = true;
|
|
|
|
$attributes = $refClass->getAttributes();
|
|
foreach ($attributes as $attribute) {
|
|
$pkg = null;
|
|
|
|
$attribute_instance = $attribute->newInstance();
|
|
if ($attribute_instance instanceof Target === false &&
|
|
$attribute_instance instanceof Library === false &&
|
|
$attribute_instance instanceof Extension === false &&
|
|
$attribute_instance instanceof Tool === false) {
|
|
// not a package attribute
|
|
continue;
|
|
}
|
|
$package_type = PackageConfig::get($attribute_instance->name, 'type');
|
|
if ($package_type === null) {
|
|
throw new RegistryException("Package [{$attribute_instance->name}] not defined in config, but referenced from class {$class}, please check your config files.");
|
|
}
|
|
|
|
// if class has parent class and matches the attribute instance, use custom class
|
|
if ($refClass->getParentClass() !== false) {
|
|
if (is_a($class_name, Package::class, true)) {
|
|
self::$packages[$attribute_instance->name] = new $class_name($attribute_instance->name, $package_type);
|
|
}
|
|
}
|
|
|
|
$pkg = self::$packages[$attribute_instance->name] ?? null;
|
|
|
|
// Use the package instance if it's a Package subclass, otherwise create a new instance
|
|
$instance_class = is_a($class_name, Package::class, true) ? $pkg : $refClass->newInstance();
|
|
|
|
// validate package type matches
|
|
$pkg_type_attr = match ($attribute->getName()) {
|
|
Target::class => ['target', 'virtual-target'],
|
|
Library::class => ['library'],
|
|
Extension::class => ['php-extension'],
|
|
Tool::class => ['tool'],
|
|
default => null,
|
|
};
|
|
if (!in_array($package_type, $pkg_type_attr, true)) {
|
|
throw new RegistryException("Package [{$attribute_instance->name}] type mismatch: config type is [{$package_type}], but attribute type is [" . implode('|', $pkg_type_attr) . '].');
|
|
}
|
|
if ($pkg instanceof Package && !PackageConfig::isPackageExists($pkg->getName())) {
|
|
throw new RegistryException("Package [{$pkg->getName()}] config not found for class {$class}");
|
|
}
|
|
|
|
// init method attributes
|
|
$methods = $refClass->getMethods(\ReflectionMethod::IS_PUBLIC);
|
|
foreach ($methods as $method) {
|
|
$method_attributes = $method->getAttributes();
|
|
foreach ($method_attributes as $method_attribute) {
|
|
$method_instance = $method_attribute->newInstance();
|
|
match ($method_attribute->getName()) {
|
|
// #[BuildFor(PHP_OS_FAMILY)]
|
|
BuildFor::class => self::addBuildFunction($pkg, $method_instance, [$instance_class, $method->getName()]),
|
|
// #[BeforeBuild]
|
|
PatchBeforeBuild::class => self::addPatchBeforeBuildFunction($pkg, [$instance_class, $method->getName()]),
|
|
// #[CustomPhpConfigureArg(PHP_OS_FAMILY)]
|
|
CustomPhpConfigureArg::class => self::bindCustomPhpConfigureArg($pkg, $method_attribute->newInstance(), [$instance_class, $method->getName()]),
|
|
// #[Stage('stage_name')]
|
|
Stage::class => self::addStage($method, $pkg, $instance_class, $method_instance),
|
|
// #[InitPackage] (run now with package context)
|
|
InitPackage::class => ApplicationContext::invoke([$instance_class, $method->getName()], ['package' => $pkg]),
|
|
// #[InitBuild]
|
|
ResolveBuild::class => $pkg instanceof TargetPackage ? $pkg->setResolveBuildCallback([$instance_class, $method->getName()]) : null,
|
|
// #[Info]
|
|
Info::class => $pkg->setInfoCallback([$instance_class, $method->getName()]),
|
|
// #[Validate]
|
|
Validate::class => $pkg->setValidateCallback([$instance_class, $method->getName()]),
|
|
default => null,
|
|
};
|
|
|
|
// Capture annotation metadata for inspection (dev:info, future event-trace commands)
|
|
$meta_attr = self::annotationShortName($method_attribute->getName());
|
|
if ($meta_attr !== null) {
|
|
self::$annotation_map[$pkg->getName()]['methods'][$method->getName()][] = [
|
|
'attr' => $meta_attr,
|
|
'args' => self::annotationArgs($method_instance),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
// Record which class defines this package (set once; IS_REPEATABLE may loop more than once)
|
|
self::$annotation_map[$pkg->getName()]['class'] ??= $class_name;
|
|
// register package
|
|
self::$packages[$pkg->getName()] = $pkg;
|
|
}
|
|
|
|
// For classes without package attributes, create a simple instance for non-package stage callbacks
|
|
if (!isset($instance_class)) {
|
|
$instance_class = $refClass->newInstance();
|
|
}
|
|
|
|
// parse non-package available attributes
|
|
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
|
|
$method_attributes = $method->getAttributes();
|
|
foreach ($method_attributes as $method_attribute) {
|
|
$method_instance = $method_attribute->newInstance();
|
|
match ($method_attribute->getName()) {
|
|
// #[BeforeStage('package_name', 'stage')] and #[AfterStage('package_name', 'stage')]
|
|
BeforeStage::class => self::addBeforeStage($method, $pkg ?? null, $instance_class, $method_instance),
|
|
AfterStage::class => self::addAfterStage($method, $pkg ?? null, $instance_class, $method_instance),
|
|
|
|
default => null,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Get annotation metadata for a specific package.
|
|
*
|
|
* Returns null if no annotation class was loaded for this package (config-only package).
|
|
* The returned structure includes the defining class name, per-method attribute list,
|
|
* inbound BeforeStage/AfterStage hooks targeting this package, and outbound hooks that
|
|
* this package's class registers on other packages.
|
|
*
|
|
* @return null|array{
|
|
* class: string,
|
|
* methods: array<string, list<array{attr: string, args: array<string, mixed>}>>,
|
|
* before_stages: array<string, list<array{class: string, method: string, only_when: ?string}>>,
|
|
* after_stages: array<string, list<array{class: string, method: string, only_when: ?string}>>,
|
|
* outbound_before_stages: array<string, array<string, list<array{method: string, only_when: ?string}>>>,
|
|
* outbound_after_stages: array<string, array<string, list<array{method: string, only_when: ?string}>>>
|
|
* }
|
|
*/
|
|
public static function getPackageAnnotationInfo(string $name): ?array
|
|
{
|
|
$class_info = self::$annotation_map[$name] ?? null;
|
|
if ($class_info === null) {
|
|
return null;
|
|
}
|
|
$class = $class_info['class'];
|
|
return [
|
|
'class' => $class,
|
|
'methods' => $class_info['methods'],
|
|
'before_stages' => self::$before_stage_meta[$name] ?? [],
|
|
'after_stages' => self::$after_stage_meta[$name] ?? [],
|
|
'outbound_before_stages' => self::$class_before_stage_meta[$class] ?? [],
|
|
'outbound_after_stages' => self::$class_after_stage_meta[$class] ?? [],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get all annotation metadata keyed by package name.
|
|
* Useful for future event-trace commands or cross-package inspection.
|
|
*
|
|
* @return array<string, array{class: string, methods: array, before_stages: array, after_stages: array, outbound_before_stages: array, outbound_after_stages: array}>
|
|
*/
|
|
public static function getAllAnnotations(): array
|
|
{
|
|
$result = [];
|
|
foreach (self::$annotation_map as $name => $info) {
|
|
$class = $info['class'];
|
|
$result[$name] = [
|
|
'class' => $class,
|
|
'methods' => $info['methods'],
|
|
'before_stages' => self::$before_stage_meta[$name] ?? [],
|
|
'after_stages' => self::$after_stage_meta[$name] ?? [],
|
|
'outbound_before_stages' => self::$class_before_stage_meta[$class] ?? [],
|
|
'outbound_after_stages' => self::$class_after_stage_meta[$class] ?? [],
|
|
];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public static function getBeforeStageCallbacks(string $package_name, string $stage): iterable
|
|
{
|
|
// match condition
|
|
$installer = ApplicationContext::get(PackageInstaller::class);
|
|
$stages = self::$before_stages[$package_name][$stage] ?? [];
|
|
foreach ($stages as $entry) {
|
|
$callback = $entry[0];
|
|
$only_when_package_resolved = $entry[1] ?? null;
|
|
$conditionals = $entry[2] ?? [];
|
|
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
|
continue;
|
|
}
|
|
foreach ($conditionals as $class) {
|
|
if (!ApplicationContext::has($class)) {
|
|
continue 2;
|
|
}
|
|
}
|
|
yield $callback;
|
|
}
|
|
}
|
|
|
|
public static function getAfterStageCallbacks(string $package_name, string $stage): array
|
|
{
|
|
// match condition
|
|
$installer = ApplicationContext::get(PackageInstaller::class);
|
|
$stages = self::$after_stages[$package_name][$stage] ?? [];
|
|
$result = [];
|
|
foreach ($stages as $entry) {
|
|
$callback = $entry[0];
|
|
$only_when_package_resolved = $entry[1] ?? null;
|
|
$conditionals = $entry[2] ?? [];
|
|
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
|
continue;
|
|
}
|
|
foreach ($conditionals as $class) {
|
|
if (!ApplicationContext::has($class)) {
|
|
continue 2;
|
|
}
|
|
}
|
|
$result[] = $callback;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Register default stages for all PhpExtensionPackage instances.
|
|
* Should be called after all registries have been loaded.
|
|
*/
|
|
public static function registerAllDefaultStages(): void
|
|
{
|
|
foreach (self::$packages as $pkg) {
|
|
if ($pkg instanceof PhpExtensionPackage) {
|
|
$pkg->registerDefaultStages();
|
|
} elseif ($pkg instanceof LibraryPackage) {
|
|
$pkg->registerDefaultStages();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check loaded stage events for consistency.
|
|
*/
|
|
public static function checkLoadedStageEvents(): void
|
|
{
|
|
foreach (['BeforeStage' => self::$before_stages, 'AfterStage' => self::$after_stages] as $event_name => $ev_all) {
|
|
foreach ($ev_all as $package_name => $stages) {
|
|
// check package exists
|
|
if (!self::hasPackage($package_name)) {
|
|
throw new RegistryException(
|
|
"{$event_name} event registered for unknown package [{$package_name}]."
|
|
);
|
|
}
|
|
$pkg = self::getPackage($package_name);
|
|
foreach ($stages as $stage_name => $before_events) {
|
|
foreach ($before_events as $entry) {
|
|
$event_callable = $entry[0];
|
|
$only_when_package_resolved = $entry[1] ?? null;
|
|
// check only_when_package_resolved package exists
|
|
if ($only_when_package_resolved !== null && !self::hasPackage($only_when_package_resolved)) {
|
|
throw new RegistryException("{$event_name} event in package [{$package_name}] for stage [{$stage_name}] has unknown only_when_package_resolved package [{$only_when_package_resolved}].");
|
|
}
|
|
// check callable is valid
|
|
if (!is_callable($event_callable)) {
|
|
throw new RegistryException(
|
|
"{$event_name} event in package [{$package_name}] for stage [{$stage_name}] has invalid callable.",
|
|
);
|
|
}
|
|
}
|
|
// check stage exists
|
|
// Skip validation if the package has no build function for current OS
|
|
// (e.g., libedit has BeforeStage for 'build' but only BuildFor('Darwin'/'Linux'))
|
|
if (!$pkg->hasStage($stage_name) && $pkg->hasBuildFunctionForCurrentOS()) {
|
|
throw new RegistryException("Package stage [{$stage_name}] is not registered in package [{$package_name}].");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind a custom PHP configure argument callback to a php-extension package.
|
|
*/
|
|
private static function bindCustomPhpConfigureArg(Package $pkg, object $attr, callable $fn): void
|
|
{
|
|
if (!$pkg instanceof PhpExtensionPackage) {
|
|
throw new RegistryException("Class [{$pkg->getName()}] must implement PhpExtensionPackage for CustomPhpConfigureArg attribute.");
|
|
}
|
|
$pkg->addCustomPhpConfigureArgCallback($attr->os, $fn);
|
|
}
|
|
|
|
private static function addBuildFunction(Package $pkg, object $attr, callable $fn): void
|
|
{
|
|
$pkg->addBuildFunction($attr->os, $fn);
|
|
}
|
|
|
|
private static function addPatchBeforeBuildFunction(Package $pkg, callable $fn): void
|
|
{
|
|
$pkg->addPatchBeforeBuildCallback($fn);
|
|
}
|
|
|
|
private static function addStage(\ReflectionMethod $method, Package $pkg, object $instance_class, object $method_instance): void
|
|
{
|
|
$name = $method_instance->function;
|
|
if ($name === null) {
|
|
$name = $method->getName();
|
|
}
|
|
$pkg->addStage($name, [$instance_class, $method->getName()]);
|
|
}
|
|
|
|
private static function addBeforeStage(\ReflectionMethod $method, ?Package $pkg, mixed $instance_class, object $method_instance): void
|
|
{
|
|
/** @var BeforeStage $method_instance */
|
|
$stage = $method_instance->stage;
|
|
$stage = match (true) {
|
|
is_string($stage) => $stage,
|
|
count($stage) === 2 => $stage[1],
|
|
default => throw new RegistryException('Invalid stage definition in BeforeStage attribute.'),
|
|
};
|
|
if ($method_instance->package_name === '' && $pkg === null) {
|
|
throw new RegistryException('Package name must not be empty when no package context is available for BeforeStage attribute.');
|
|
}
|
|
$package_name = $method_instance->package_name === '' ? $pkg->getName() : $method_instance->package_name;
|
|
|
|
$conditionals = array_map(
|
|
fn (\ReflectionAttribute $a) => $a->newInstance()->class,
|
|
[...$method->getDeclaringClass()->getAttributes(ConditionalOn::class), ...$method->getAttributes(ConditionalOn::class)],
|
|
);
|
|
self::$before_stages[$package_name][$stage][] = [[$instance_class, $method->getName()], $method_instance->only_when_package_resolved, $conditionals];
|
|
$registering_class = get_class($instance_class);
|
|
self::$before_stage_meta[$package_name][$stage][] = [
|
|
'class' => $registering_class,
|
|
'method' => $method->getName(),
|
|
'only_when' => $method_instance->only_when_package_resolved,
|
|
'conditionals' => $conditionals,
|
|
];
|
|
self::$class_before_stage_meta[$registering_class][$package_name][$stage][] = [
|
|
'method' => $method->getName(),
|
|
'only_when' => $method_instance->only_when_package_resolved,
|
|
];
|
|
}
|
|
|
|
private static function addAfterStage(\ReflectionMethod $method, ?Package $pkg, mixed $instance_class, object $method_instance): void
|
|
{
|
|
$stage = $method_instance->stage;
|
|
$stage = match (true) {
|
|
is_string($stage) => $stage,
|
|
is_array($stage) && count($stage) === 2 => $stage[1],
|
|
default => throw new RegistryException('Invalid stage definition in AfterStage attribute.'),
|
|
};
|
|
if ($method_instance->package_name === '' && $pkg === null) {
|
|
throw new RegistryException('Package name must not be empty when no package context is available for AfterStage attribute.');
|
|
}
|
|
$package_name = $method_instance->package_name === '' ? $pkg->getName() : $method_instance->package_name;
|
|
|
|
$conditionals = array_map(
|
|
fn (\ReflectionAttribute $a) => $a->newInstance()->class,
|
|
[...$method->getDeclaringClass()->getAttributes(ConditionalOn::class), ...$method->getAttributes(ConditionalOn::class)],
|
|
);
|
|
self::$after_stages[$package_name][$stage][] = [[$instance_class, $method->getName()], $method_instance->only_when_package_resolved, $conditionals];
|
|
$registering_class = get_class($instance_class);
|
|
self::$after_stage_meta[$package_name][$stage][] = [
|
|
'class' => $registering_class,
|
|
'method' => $method->getName(),
|
|
'only_when' => $method_instance->only_when_package_resolved,
|
|
'conditionals' => $conditionals,
|
|
];
|
|
self::$class_after_stage_meta[$registering_class][$package_name][$stage][] = [
|
|
'method' => $method->getName(),
|
|
'only_when' => $method_instance->only_when_package_resolved,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Map a fully-qualified attribute class name to a short display name for metadata storage.
|
|
* Returns null for attributes that are not tracked in the annotation map.
|
|
*/
|
|
private static function annotationShortName(string $attr): ?string
|
|
{
|
|
return match ($attr) {
|
|
Stage::class => 'Stage',
|
|
BuildFor::class => 'BuildFor',
|
|
PatchBeforeBuild::class => 'PatchBeforeBuild',
|
|
CustomPhpConfigureArg::class => 'CustomPhpConfigureArg',
|
|
InitPackage::class => 'InitPackage',
|
|
ResolveBuild::class => 'ResolveBuild',
|
|
Info::class => 'Info',
|
|
Validate::class => 'Validate',
|
|
ConditionalOn::class => 'ConditionalOn',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract the meaningful constructor arguments from an attribute instance as a key-value array.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private static function annotationArgs(object $inst): array
|
|
{
|
|
return match (true) {
|
|
$inst instanceof Stage => array_filter(['function' => $inst->function], fn ($v) => $v !== null),
|
|
$inst instanceof BuildFor => ['os' => $inst->os],
|
|
$inst instanceof CustomPhpConfigureArg => array_filter(['os' => $inst->os], fn ($v) => $v !== ''),
|
|
default => [],
|
|
};
|
|
}
|
|
}
|