diff --git a/composer.json b/composer.json index 1a1005c73..bf534d19d 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "require": { "php": ">= 8.3", "ext-mbstring": "*", + "ext-simplexml": "*", "ext-zlib": "*", "laravel/prompts": "^0.1.12", "symfony/console": "^5.4 || ^6 || ^7", diff --git a/config/source.json b/config/source.json index 040197cb1..823789c72 100644 --- a/config/source.json +++ b/config/source.json @@ -8,30 +8,25 @@ "alt": false }, "amqp": { - "type": "url", - "url": "https://pecl.php.net/get/amqp", + "type": "pecl", "path": "php-src/ext/amqp", - "filename": "amqp.tgz", "license": { "type": "file", "path": "LICENSE" } }, "apcu": { - "type": "url", - "url": "https://pecl.php.net/get/APCu", + "type": "pecl", + "pecl": "APCu", "path": "php-src/ext/apcu", - "filename": "apcu.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ast": { - "type": "url", - "url": "https://pecl.php.net/get/ast", + "type": "pecl", "path": "php-src/ext/ast", - "filename": "ast.tgz", "license": { "type": "file", "path": "LICENSE" @@ -85,20 +80,16 @@ } }, "dio": { - "type": "url", - "url": "https://pecl.php.net/get/dio", + "type": "pecl", "path": "php-src/ext/dio", - "filename": "dio.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ev": { - "type": "url", - "url": "https://pecl.php.net/get/ev", + "type": "pecl", "path": "php-src/ext/ev", - "filename": "ev.tgz", "license": { "type": "file", "path": "LICENSE" @@ -115,10 +106,8 @@ } }, "ext-ds": { - "type": "url", - "url": "https://pecl.php.net/get/ds", + "type": "pecl", "path": "php-src/ext/ds", - "filename": "ds.tgz", "license": { "type": "file", "path": "LICENSE" @@ -134,10 +123,8 @@ } }, "ext-excimer": { - "type": "url", - "url": "https://pecl.php.net/get/excimer", + "type": "pecl", "path": "php-src/ext/excimer", - "filename": "excimer.tgz", "license": { "type": "file", "path": "LICENSE" @@ -162,10 +149,8 @@ } }, "ext-grpc": { - "type": "url", - "url": "https://pecl.php.net/get/grpc", + "type": "pecl", "path": "php-src/ext/grpc", - "filename": "grpc.tgz", "license": { "type": "file", "path": [ @@ -174,20 +159,16 @@ } }, "ext-imagick": { - "type": "url", - "url": "https://pecl.php.net/get/imagick", + "type": "pecl", "path": "php-src/ext/imagick", - "filename": "imagick.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ext-imap": { - "type": "url", - "url": "https://pecl.php.net/get/imap", + "type": "pecl", "path": "php-src/ext/imap", - "filename": "imap.tgz", "license": { "type": "file", "path": [ @@ -207,19 +188,15 @@ } }, "ext-maxminddb": { - "type": "url", - "url": "https://pecl.php.net/get/maxminddb", - "filename": "ext-maxminddb.tgz", + "type": "pecl", "license": { "type": "file", "path": "LICENSE" } }, "ext-memcache": { - "type": "url", - "url": "https://pecl.php.net/get/memcache", + "type": "pecl", "path": "php-src/ext/memcache", - "filename": "memcache.tgz", "license": { "type": "file", "path": "LICENSE" @@ -235,10 +212,8 @@ } }, "ext-simdjson": { - "type": "url", - "url": "https://pecl.php.net/get/simdjson", + "type": "pecl", "path": "php-src/ext/simdjson", - "filename": "simdjson.tgz", "license": { "type": "file", "path": "LICENSE" @@ -255,40 +230,32 @@ } }, "ext-ssh2": { - "type": "url", - "url": "https://pecl.php.net/get/ssh2", + "type": "pecl", "path": "php-src/ext/ssh2", - "filename": "ssh2.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ext-trader": { - "type": "url", - "url": "https://pecl.php.net/get/trader", + "type": "pecl", "path": "php-src/ext/trader", - "filename": "trader.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ext-uuid": { - "type": "url", - "url": "https://pecl.php.net/get/uuid", + "type": "pecl", "path": "php-src/ext/uuid", - "filename": "uuid.tgz", "license": { "type": "file", "path": "LICENSE" } }, "ext-uv": { - "type": "url", - "url": "https://pecl.php.net/get/uv", + "type": "pecl", "path": "php-src/ext/uv", - "filename": "uv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -305,9 +272,7 @@ } }, "ext-zip": { - "type": "url", - "url": "https://pecl.php.net/get/zip", - "filename": "ext-zip.tgz", + "type": "pecl", "license": { "type": "file", "path": "LICENSE" @@ -412,10 +377,8 @@ } }, "igbinary": { - "type": "url", - "url": "https://pecl.php.net/get/igbinary", + "type": "pecl", "path": "php-src/ext/igbinary", - "filename": "igbinary.tgz", "license": { "type": "file", "path": "COPYING" @@ -439,10 +402,8 @@ } }, "inotify": { - "type": "url", - "url": "https://pecl.php.net/get/inotify", + "type": "pecl", "path": "php-src/ext/inotify", - "filename": "inotify.tgz", "license": { "type": "file", "path": "LICENSE" @@ -844,10 +805,8 @@ } }, "memcached": { - "type": "url", - "url": "https://pecl.php.net/get/memcached", + "type": "pecl", "path": "php-src/ext/memcached", - "filename": "memcached.tgz", "license": { "type": "file", "path": "LICENSE" @@ -885,10 +844,8 @@ } }, "msgpack": { - "type": "url", - "url": "https://pecl.php.net/get/msgpack", + "type": "pecl", "path": "php-src/ext/msgpack", - "filename": "msgpack.tgz", "license": { "type": "file", "path": "LICENSE" @@ -988,39 +945,31 @@ } }, "opentelemetry": { - "type": "url", - "url": "https://pecl.php.net/get/opentelemetry", + "type": "pecl", "path": "php-src/ext/opentelemetry", - "filename": "opentelemetry.tgz", "license": { "type": "file", "path": "LICENSE" } }, "parallel": { - "type": "url", - "url": "https://pecl.php.net/get/parallel", + "type": "pecl", "path": "php-src/ext/parallel", - "filename": "parallel.tgz", "license": { "type": "file", "path": "LICENSE" } }, "pcov": { - "type": "url", - "url": "https://pecl.php.net/get/pcov", - "filename": "pcov.tgz", + "type": "pecl", "license": { "type": "file", "path": "LICENSE" } }, "pdo_sqlsrv": { - "type": "url", - "url": "https://pecl.php.net/get/pdo_sqlsrv", + "type": "pecl", "path": "php-src/ext/pdo_sqlsrv", - "filename": "pdo_sqlsrv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1053,10 +1002,8 @@ } }, "protobuf": { - "type": "url", - "url": "https://pecl.php.net/get/protobuf", + "type": "pecl", "path": "php-src/ext/protobuf", - "filename": "protobuf.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1115,10 +1062,8 @@ } }, "redis": { - "type": "url", - "url": "https://pecl.php.net/get/redis", + "type": "pecl", "path": "php-src/ext/redis", - "filename": "redis.tgz", "license": { "type": "file", "path": [ @@ -1155,10 +1100,8 @@ } }, "sqlsrv": { - "type": "url", - "url": "https://pecl.php.net/get/sqlsrv", + "type": "pecl", "path": "php-src/ext/sqlsrv", - "filename": "sqlsrv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1221,20 +1164,16 @@ } }, "xhprof": { - "type": "url", - "url": "https://pecl.php.net/get/xhprof", + "type": "pecl", "path": "php-src/ext/xhprof-src", - "filename": "xhprof.tgz", "license": { "type": "file", "path": "LICENSE" } }, "xlswriter": { - "type": "url", - "url": "https://pecl.php.net/get/xlswriter", + "type": "pecl", "path": "php-src/ext/xlswriter", - "filename": "xlswriter.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1252,10 +1191,8 @@ } }, "yac": { - "type": "url", - "url": "https://pecl.php.net/get/yac", + "type": "pecl", "path": "php-src/ext/yac", - "filename": "yac.tgz", "license": { "type": "file", "path": "LICENSE" diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index ccf61dd8d..474e8dbe1 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -16,6 +16,43 @@ */ class Downloader { + /** + * Get latest stable version from PECL + * + * @param string $name Source name + * @param array $source Source meta info: [pecl?] + * @return array [url, filename] + */ + public static function getPECLInfo(string $name, array $source): array + { + $package = $source['pecl'] ?? (str_starts_with($name, 'ext-') ? substr($name, 4) : $name); + $lp = strtolower($package); + $api_url = "https://pecl.php.net/rest/r/{$lp}/allreleases.xml"; + logger()->debug("Fetching {$name} source from PECL: {$api_url}"); + $xml = self::curlExec( + url: $api_url, + retries: self::getRetryAttempts() + ); + $dom = new \SimpleXMLElement($xml); + $version = null; + if ($source['prefer-stable'] ?? false) { + foreach ($dom->r as $release) { + if ((string) $release->s === 'stable') { + $version = (string) $release->v; + break; + } + } + } + $version ??= isset($dom->r[0]) ? (string) $dom->r[0]->v : null; + if ($version === null) { + throw new DownloaderException("failed to find any release for {$name} on PECL"); + } + $url = "https://pecl.php.net/get/{$package}-{$version}.tgz"; + $filename = "{$package}-{$version}.tgz"; + logger()->info("Found {$name} PECL version: {$version}"); + return [$url, $filename]; + } + /** * Get latest version from PIE config (Packagist) * @@ -612,6 +649,7 @@ private static function isAlreadyDownloaded(string $name, bool $force, int $down * @param array{ * url?: string, * repo?: string, + * pecl?: string, * rev?: string, * path?: string, * filename?: string, @@ -631,6 +669,10 @@ private static function downloadByType(string $type, string $name, array $conf, { try { switch ($type) { + case 'pecl': // PECL (latest stable) + [$url, $filename] = self::getPECLInfo($name, $conf); + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); + break; case 'pie': // Packagist [$url, $filename] = self::getPIEInfo($name, $conf); self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); diff --git a/src/SPC/util/ConfigValidator.php b/src/SPC/util/ConfigValidator.php index d9f751ef9..7f201b89a 100644 --- a/src/SPC/util/ConfigValidator.php +++ b/src/SPC/util/ConfigValidator.php @@ -22,7 +22,9 @@ class ConfigValidator 'regex' => 'string', // regex pattern 'rev' => 'string', // revision/branch 'repo' => 'string', // repository name + 'pecl' => 'string', // PECL package name 'match' => 'string', // match pattern (aaa*bbb) + 'query' => 'string', // query string for API requests 'filename' => 'string', // filename 'path' => 'string', // copy path 'extract' => 'string', // copy path (alias of path) @@ -73,12 +75,13 @@ class ConfigValidator private const array SOURCE_TYPE_FIELDS = [ 'filelist' => [['url', 'regex'], []], 'git' => [['url', 'rev'], ['path', 'extract', 'submodules']], - 'ghtagtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match']], - 'ghtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match']], + 'ghtagtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match', 'query']], + 'ghtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match', 'query']], 'ghrel' => [['repo', 'match'], ['path', 'extract', 'prefer-stable']], 'url' => [['url'], ['filename', 'path', 'extract']], 'bitbuckettag' => [['repo'], ['path', 'extract']], 'local' => [['dirname'], ['path', 'extract']], + 'pecl' => [[], ['pecl', 'path', 'prefer-stable']], 'pie' => [['repo'], ['path']], 'custom' => [[], ['func']], ];