diff --git a/src/StaticPHP/Artifact/Artifact.php b/src/StaticPHP/Artifact/Artifact.php index 8bdd86a8..841775e3 100644 --- a/src/StaticPHP/Artifact/Artifact.php +++ b/src/StaticPHP/Artifact/Artifact.php @@ -27,6 +27,9 @@ class Artifact /** @var null|callable Bind custom source fetcher callback */ protected mixed $custom_source_callback = null; + /** @var null|callable Bind custom source check-update callback */ + protected mixed $custom_source_check_update_callback = null; + /** @var array Bind custom binary fetcher callbacks */ protected mixed $custom_binary_callbacks = []; @@ -408,6 +411,19 @@ class Artifact return $this->custom_source_callback ?? null; } + /** + * Set custom source check-update callback. + */ + public function setCustomSourceCheckUpdateCallback(callable $callback): void + { + $this->custom_source_check_update_callback = $callback; + } + + public function getCustomSourceCheckUpdateCallback(): ?callable + { + return $this->custom_source_check_update_callback ?? null; + } + public function getCustomBinaryCallback(): ?callable { $current_platform = SystemTarget::getCurrentPlatformString(); diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index 219dfb8b..86c06c19 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -332,17 +332,14 @@ class ArtifactDownloader throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name."); } if ($bare) { - $config = $artifact->getDownloadConfig('source'); - if (!is_array($config)) { - throw new WrongUsageException("Artifact '{$artifact_name}' has no source config for bare update check."); + [$first, $second] = $prefer_source + ? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)] + : [fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name), fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name)]; + $result = $first() ?? $second(); + if ($result !== null) { + return $result; } - $cls = $this->downloaders[$config['type']] ?? null; - if (!is_a($cls, CheckUpdateInterface::class, true)) { - throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking."); - } - /** @var CheckUpdateInterface $downloader */ - $downloader = new $cls(); - return $downloader->checkUpdate($artifact_name, $config, null, $this); + throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking."); } $cache = ApplicationContext::get(ArtifactCache::class); if ($prefer_source) { @@ -358,7 +355,15 @@ class ArtifactDownloader /** @var CheckUpdateInterface $downloader */ $downloader = new $cls(); return $downloader->checkUpdate($artifact_name, $info['config'], $info['version'], $this); - } // custom binary: delegate to registered check-update callback + } + // custom source: delegate to registered check-update callback + if (($info['lock_type'] ?? null) === 'source' && ($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) { + return ApplicationContext::invoke($callback, [ + ArtifactDownloader::class => $this, + 'old_version' => $info['version'], + ]); + } + // custom binary: delegate to registered check-update callback if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) { return ApplicationContext::invoke($callback, [ ArtifactDownloader::class => $this, @@ -383,6 +388,50 @@ class ArtifactDownloader return $this->options[$name] ?? $default; } + private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult + { + if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) { + return ApplicationContext::invoke($callback, [ + ArtifactDownloader::class => $this, + 'old_version' => null, + ]); + } + $config = $artifact->getDownloadConfig('source'); + if (!is_array($config)) { + return null; + } + $cls = $this->downloaders[$config['type']] ?? null; + if (!is_a($cls, CheckUpdateInterface::class, true)) { + return null; + } + /** @var CheckUpdateInterface $dl */ + $dl = new $cls(); + return $dl->checkUpdate($artifact_name, $config, null, $this); + } + + private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult + { + // custom binary callback takes precedence over config-based binary + if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) { + return ApplicationContext::invoke($callback, [ + ArtifactDownloader::class => $this, + 'old_version' => null, + ]); + } + $binary_config = $artifact->getDownloadConfig('binary'); + $platform_config = is_array($binary_config) ? ($binary_config[SystemTarget::getCurrentPlatformString()] ?? null) : null; + if (!is_array($platform_config)) { + return null; + } + $cls = $this->downloaders[$platform_config['type']] ?? null; + if (!is_a($cls, CheckUpdateInterface::class, true)) { + return null; + } + /** @var CheckUpdateInterface $dl */ + $dl = new $cls(); + return $dl->checkUpdate($artifact_name, $platform_config, null, $this); + } + private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false, bool $interactive = true): int { $queue = $this->generateQueue($artifact); diff --git a/src/StaticPHP/Attribute/Artifact/CustomSourceCheckUpdate.php b/src/StaticPHP/Attribute/Artifact/CustomSourceCheckUpdate.php new file mode 100644 index 00000000..df6e07d6 --- /dev/null +++ b/src/StaticPHP/Attribute/Artifact/CustomSourceCheckUpdate.php @@ -0,0 +1,11 @@ +getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { self::processCustomSourceAttribute($ref, $method, $class_instance); + self::processCustomSourceCheckUpdateAttribute($ref, $method, $class_instance); self::processCustomBinaryAttribute($ref, $method, $class_instance); self::processCustomBinaryCheckUpdateAttribute($ref, $method, $class_instance); self::processSourceExtractAttribute($ref, $method, $class_instance); @@ -100,6 +102,24 @@ class ArtifactLoader } } + /** + * Process #[CustomSourceCheckUpdate] attribute. + */ + private static function processCustomSourceCheckUpdateAttribute(\ReflectionClass $ref, \ReflectionMethod $method, object $class_instance): void + { + $attributes = $method->getAttributes(CustomSourceCheckUpdate::class); + foreach ($attributes as $attribute) { + /** @var CustomSourceCheckUpdate $instance */ + $instance = $attribute->newInstance(); + $artifact_name = $instance->artifact_name; + if (isset(self::$artifacts[$artifact_name])) { + self::$artifacts[$artifact_name]->setCustomSourceCheckUpdateCallback([$class_instance, $method->getName()]); + } else { + throw new ValidationException("Artifact '{$artifact_name}' not found for #[CustomSourceCheckUpdate] on '{$ref->getName()}::{$method->getName()}'"); + } + } + } + /** * Process #[CustomBinary] attribute. */