diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php index 90c68a24..1e1c5930 100644 --- a/src/Package/Target/php.php +++ b/src/Package/Target/php.php @@ -45,7 +45,7 @@ use ZM\Logger\ConsoleColor; #[Target('php-cgi')] #[Target('php-embed')] #[Target('frankenphp')] -class php +class php extends TargetPackage { public static function getPHPVersionID(): int { @@ -350,6 +350,9 @@ class php shell()->cd($package->getSourceDir()) ->setEnv($this->makeVars($installer)) ->exec("make -j{$concurrency} cli"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php'); + $package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php'); } #[Stage] @@ -360,6 +363,9 @@ class php shell()->cd($package->getSourceDir()) ->setEnv($this->makeVars($installer)) ->exec("make -j{$concurrency} cgi"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi'); + $package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi'); } #[Stage] @@ -370,6 +376,9 @@ class php shell()->cd($package->getSourceDir()) ->setEnv($this->makeVars($installer)) ->exec("make -j{$concurrency} fpm"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm'); + $package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm'); } #[Stage] @@ -392,6 +401,7 @@ class php ->exec("make -j{$builder->concurrency} micro"); $builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', BUILD_BIN_PATH . '/micro.sfx'); + $package->setOutput('Binary path for micro SAPI', BUILD_BIN_PATH . '/micro.sfx'); } finally { if ($phar_patched) { SourcePatcher::unpatchMicroPhar(); @@ -432,12 +442,17 @@ class php } // deploy $builder->deployBinary($libphp_so, $libphp_so, false); + $package->setOutput('Library path for embed SAPI', $libphp_so); } // process shared extensions that built-with-php $increment_files = $diff->getChangedFiles(); foreach ($increment_files as $increment_file) { $builder->deployBinary($increment_file, $increment_file, false); + $files[] = basename($increment_file); + } + if (!empty($files)) { + $package->setOutput('Built shared extensions', implode(', ', $files)); } // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static ------------- @@ -524,6 +539,7 @@ class php logger()->debug('Patching phpize prefix'); FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'"); FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#'); + $this->setOutput('phpize script path for embed SAPI', BUILD_BIN_PATH . '/phpize'); } // patch php-config if (file_exists(BUILD_BIN_PATH . '/php-config')) { @@ -535,6 +551,7 @@ class php // move lstdc++ to the end of libs $php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str); FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str); + $this->setOutput('php-config script path for embed SAPI', BUILD_BIN_PATH . '/php-config'); } } diff --git a/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php b/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php deleted file mode 100644 index 2343954b..00000000 --- a/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php +++ /dev/null @@ -1,11 +0,0 @@ -getVersionWithCommit(); if (!$this->no_motd) { - echo str_replace('{version}', $version, self::$motd); + echo str_replace('{version}', '' . ConsoleColor::none("v{$version}"), '' . ConsoleColor::magenta(self::$motd)); } } diff --git a/src/StaticPHP/Command/BuildTargetCommand.php b/src/StaticPHP/Command/BuildTargetCommand.php index e66f514b..2756070b 100644 --- a/src/StaticPHP/Command/BuildTargetCommand.php +++ b/src/StaticPHP/Command/BuildTargetCommand.php @@ -51,6 +51,8 @@ class BuildTargetCommand extends BaseCommand $this->output->writeln("✔ BUILD SUCCESSFUL ({$usedtime} s)"); $this->output->writeln("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); + $installer->printBuildPackageOutputs(); + return static::SUCCESS; } } diff --git a/src/StaticPHP/DI/CallbackInvoker.php b/src/StaticPHP/DI/CallbackInvoker.php index f14f9468..fa11a7f1 100644 --- a/src/StaticPHP/DI/CallbackInvoker.php +++ b/src/StaticPHP/DI/CallbackInvoker.php @@ -26,6 +26,10 @@ class CallbackInvoker * 4. Default value * 5. Null (if nullable) * + * Note: For object values in context, the invoker automatically registers + * the object under all its parent classes and interfaces, allowing type hints + * to match any type in the inheritance hierarchy. + * * @param callable $callback The callback to invoke * @param array $context Context parameters (type => value or name => value) * @@ -35,6 +39,9 @@ class CallbackInvoker */ public function invoke(callable $callback, array $context = []): mixed { + // Expand context to include all parent classes and interfaces for objects + $context = $this->expandContextHierarchy($context); + $reflection = new \ReflectionFunction(\Closure::fromCallable($callback)); $args = []; @@ -95,4 +102,43 @@ class CallbackInvoker 'void', 'null', 'false', 'true', 'never', ], true); } + + /** + * Expand context to include all parent classes and interfaces for object values. + * This allows type hints to match any type in the object's inheritance hierarchy. + * + * @param array $context Original context array + * @return array Expanded context with all class hierarchy mappings + */ + private function expandContextHierarchy(array $context): array + { + $expanded = []; + + foreach ($context as $key => $value) { + // Keep the original key-value pair + $expanded[$key] = $value; + + // If value is an object, add mappings for all parent classes and interfaces + if (is_object($value)) { + $reflection = new \ReflectionClass($value); + + // Add concrete class + $expanded[$reflection->getName()] = $value; + + // Add all parent classes + while ($parent = $reflection->getParentClass()) { + $expanded[$parent->getName()] = $value; + $reflection = $parent; + } + + // Add all interfaces + $interfaces = (new \ReflectionClass($value))->getInterfaceNames(); + foreach ($interfaces as $interface) { + $expanded[$interface] = $value; + } + } + } + + return $expanded; + } } diff --git a/src/StaticPHP/Package/Package.php b/src/StaticPHP/Package/Package.php index 0317e1bc..6cad1fab 100644 --- a/src/StaticPHP/Package/Package.php +++ b/src/StaticPHP/Package/Package.php @@ -23,6 +23,9 @@ abstract class Package /** @var array $build_functions Build functions for different OS binding */ protected array $build_functions = []; + /** @var array */ + protected array $outputs = []; + /** * @param string $name Name of the package * @param string $type Type of the package @@ -69,6 +72,17 @@ abstract class Package return $ret; } + public function setOutput(string $key, string $value): static + { + $this->outputs[$key] = $value; + return $this; + } + + public function getOutputs(): array + { + return $this->outputs; + } + /** * Add a build function for a specific platform. * diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 9770a737..96316887 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -104,6 +104,16 @@ class PackageInstaller return $this; } + public function printBuildPackageOutputs(): void + { + foreach ($this->build_packages as $package) { + if (($outputs = $package->getOutputs()) !== []) { + InteractiveTerm::notice('Package ' . ConsoleColor::green($package->getName()) . ' outputs'); + $this->printArrayInfo(info: $outputs); + } + } + } + /** * Run the package installation process. */ diff --git a/src/StaticPHP/Registry/PackageLoader.php b/src/StaticPHP/Registry/PackageLoader.php index 15ff0f4a..0ef3fb8e 100644 --- a/src/StaticPHP/Registry/PackageLoader.php +++ b/src/StaticPHP/Registry/PackageLoader.php @@ -12,7 +12,6 @@ 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; @@ -166,16 +165,14 @@ class PackageLoader 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); - $instance_class = self::$packages[$attribute_instance->name]; } } - if (!isset($instance_class)) { - $instance_class = $refClass->newInstance(); - } - $pkg = self::$packages[$attribute_instance->name]; + // 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'], @@ -204,18 +201,13 @@ class PackageLoader // #[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::class => $pkg, - $pkg::class => $pkg, - ]), + 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()]), - // #[PatchBeforeBuild] - PatchBeforeBuild::class => $pkg->setPatchBeforeBuildCallback([$instance_class, $method->getName()]), default => null, }; } @@ -224,6 +216,7 @@ class PackageLoader 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(); }