From 7fa6fd08d4a3b74173525c60e7cd2308f24d89ba Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 5 Dec 2025 14:39:05 +0800 Subject: [PATCH] Add HostedPackageBin downloader and enhance artifact handling --- src/StaticPHP/Artifact/ArtifactDownloader.php | 27 ++++---- .../Downloader/Type/GitHubRelease.php | 20 ++++++ .../Downloader/Type/GitHubTokenSetupTrait.php | 5 ++ .../Downloader/Type/HostedPackageBin.php | 63 +++++++++++++++++++ src/StaticPHP/Command/SPCConfigCommand.php | 2 +- src/StaticPHP/Config/ConfigValidator.php | 8 ++- 6 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 src/StaticPHP/Artifact/Downloader/Type/HostedPackageBin.php diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index b28c11dc..9600068d 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -12,6 +12,7 @@ use StaticPHP\Artifact\Downloader\Type\FileList; use StaticPHP\Artifact\Downloader\Type\Git; use StaticPHP\Artifact\Downloader\Type\GitHubRelease; use StaticPHP\Artifact\Downloader\Type\GitHubTarball; +use StaticPHP\Artifact\Downloader\Type\HostedPackageBin; use StaticPHP\Artifact\Downloader\Type\LocalDir; use StaticPHP\Artifact\Downloader\Type\PhpRelease; use StaticPHP\Artifact\Downloader\Type\PIE; @@ -35,6 +36,19 @@ use ZM\Logger\ConsoleColor; */ class ArtifactDownloader { + public const array DOWNLOADERS = [ + 'bitbuckettag' => BitBucketTag::class, + 'filelist' => FileList::class, + 'git' => Git::class, + 'ghrel' => GitHubRelease::class, + 'ghtar', 'ghtagtar' => GitHubTarball::class, + 'local' => LocalDir::class, + 'pie' => PIE::class, + 'url' => Url::class, + 'php-release' => PhpRelease::class, + 'hosted' => HostedPackageBin::class, + ]; + /** @var array Artifact objects */ protected array $artifacts = []; @@ -355,18 +369,7 @@ class ArtifactDownloader foreach ($queue as $item) { try { $instance = null; - $call = match ($item['config']['type']) { - 'bitbuckettag' => BitBucketTag::class, - 'filelist' => FileList::class, - 'git' => Git::class, - 'ghrel' => GitHubRelease::class, - 'ghtar', 'ghtagtar' => GitHubTarball::class, - 'local' => LocalDir::class, - 'pie' => PIE::class, - 'url' => Url::class, - 'php-release' => PhpRelease::class, - default => null, - }; + $call = self::DOWNLOADERS[$item['config']['type']] ?? null; $type_display_name = match (true) { $item['lock'] === 'source' && ($callback = $artifact->getCustomSourceCallback()) !== null => 'user defined source downloader', $item['lock'] === 'binary' && ($callback = $artifact->getCustomBinaryCallback()) !== null => 'user defined binary downloader', diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php index 2e8a499e..731e8297 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubRelease.php @@ -21,6 +21,26 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface private ?string $version = null; + public function getGitHubReleases(string $name, string $repo, bool $prefer_stable = true): array + { + logger()->debug("Fetching {$name} GitHub releases from {$repo}"); + $url = str_replace('{repo}', $repo, self::API_URL); + $headers = $this->getGitHubTokenHeaders(); + $data2 = default_shell()->executeCurl($url, headers: $headers); + $data = json_decode($data2 ?: '', true); + if (!is_array($data)) { + throw new DownloaderException("Failed to get GitHub release API info for {$repo} from {$url}"); + } + $releases = []; + foreach ($data as $release) { + if ($prefer_stable && $release['prerelease'] === true) { + continue; + } + $releases[] = $release; + } + return $releases; + } + /** * Get the latest GitHub release assets for a given repository. * match_asset is provided, only return the asset that matches the regex. diff --git a/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php b/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php index d773bde7..90c42507 100644 --- a/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php +++ b/src/StaticPHP/Artifact/Downloader/Type/GitHubTokenSetupTrait.php @@ -7,6 +7,11 @@ namespace StaticPHP\Artifact\Downloader\Type; trait GitHubTokenSetupTrait { public function getGitHubTokenHeaders(): array + { + return self::getGitHubTokenHeadersStatic(); + } + + public static function getGitHubTokenHeadersStatic(): array { // GITHUB_TOKEN support if (($token = getenv('GITHUB_TOKEN')) !== false && ($user = getenv('GITHUB_USER')) !== false) { diff --git a/src/StaticPHP/Artifact/Downloader/Type/HostedPackageBin.php b/src/StaticPHP/Artifact/Downloader/Type/HostedPackageBin.php new file mode 100644 index 00000000..25d6098a --- /dev/null +++ b/src/StaticPHP/Artifact/Downloader/Type/HostedPackageBin.php @@ -0,0 +1,63 @@ + '{name}-{arch}-{os}-{libc}-{libcver}.txz', + 'darwin' => '{name}-{arch}-{os}.txz', + 'windows' => '{name}-{arch}-{os}.tgz', + ]; + + private static array $release_info = []; + + public static function getReleaseInfo(): array + { + if (empty(self::$release_info)) { + $rel = (new GitHubRelease())->getGitHubReleases('hosted', self::BASE_REPO); + if (empty($rel)) { + throw new DownloaderException('No releases found for hosted package-bin'); + } + self::$release_info = $rel[0]; + } + return self::$release_info; + } + + public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult + { + $info = self::getReleaseInfo(); + $replace = [ + '{name}' => $name, + '{arch}' => SystemTarget::getTargetArch(), + '{os}' => strtolower(SystemTarget::getTargetOS()), + '{libc}' => SystemTarget::getLibc() ?? 'default', + '{libcver}' => SystemTarget::getLibcVersion() ?? 'default', + ]; + $find_str = str_replace(array_keys($replace), array_values($replace), self::ASSET_MATCHES[strtolower(SystemTarget::getTargetOS())]); + foreach ($info['assets'] as $asset) { + if ($asset['name'] === $find_str) { + $download_url = $asset['browser_download_url']; + $filename = $asset['name']; + $version = ltrim($info['tag_name'], 'v'); + logger()->debug("Downloading hosted package-bin {$name} version {$version} from GitHub: {$download_url}"); + $path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename; + $headers = $this->getGitHubTokenHeaders(); + default_shell()->executeCurlDownload($download_url, $path, headers: $headers, retries: $downloader->getRetry()); + return DownloadResult::archive($filename, $config, extract: $config['extract'] ?? null, version: $version); + } + } + throw new DownloaderException("No matching asset found for hosted package-bin {$name} with criteria: {$find_str}"); + } +} diff --git a/src/StaticPHP/Command/SPCConfigCommand.php b/src/StaticPHP/Command/SPCConfigCommand.php index 0a242c60..f8afd0e4 100644 --- a/src/StaticPHP/Command/SPCConfigCommand.php +++ b/src/StaticPHP/Command/SPCConfigCommand.php @@ -33,7 +33,7 @@ class SPCConfigCommand extends BaseCommand { // transform string to array $libraries = parse_comma_list($this->getOption('with-libs')); - $libraries = array_merge($libraries, $this->getOption('with-packages')); + $libraries = array_merge($libraries, parse_comma_list($this->getOption('with-packages'))); // transform string to array $extensions = $this->getArgument('extensions') ? parse_extension_list($this->getArgument('extensions')) : []; $include_suggests = $this->getOption('with-suggests') ?: $this->getOption('with-suggested-libs') || $this->getOption('with-suggested-exts'); diff --git a/src/StaticPHP/Config/ConfigValidator.php b/src/StaticPHP/Config/ConfigValidator.php index 3ddb9bab..4de0529f 100644 --- a/src/StaticPHP/Config/ConfigValidator.php +++ b/src/StaticPHP/Config/ConfigValidator.php @@ -137,8 +137,14 @@ class ConfigValidator ]; continue; } - // TODO: expand hosted to static-php hosted download urls if ($v === 'hosted') { + $data[$name][$k] = [ + 'linux-x86_64' => ['type' => 'hosted'], + 'linux-aarch64' => ['type' => 'hosted'], + 'windows-x86_64' => ['type' => 'hosted'], + 'macos-x86_64' => ['type' => 'hosted'], + 'macos-aarch64' => ['type' => 'hosted'], + ]; continue; } if (is_assoc_array($v)) {