Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use StaticPHP\Artifact\Downloader\Type\GitHubTarball;
use StaticPHP\Artifact\Downloader\Type\HostedPackageBin;
use StaticPHP\Artifact\Downloader\Type\LocalDir;
use StaticPHP\Artifact\Downloader\Type\PECL;
use StaticPHP\Artifact\Downloader\Type\PhpRelease;
use StaticPHP\Artifact\Downloader\Type\PIE;
use StaticPHP\Artifact\Downloader\Type\Url;
Expand All @@ -24,6 +25,7 @@
'ghtagtar' => GitHubTarball::class,
'local' => LocalDir::class,
'pie' => PIE::class,
'pecl' => PECL::class,
'url' => Url::class,
'php-release' => PhpRelease::class,
'hosted' => HostedPackageBin::class,
Expand Down
29 changes: 29 additions & 0 deletions config/pkg/ext/builtin-extensions.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
ext-bcmath:
type: php-extension
ext-mbregex:
type: php-extension
depends:
- onig
- ext-mbstring
php-extension:
arg-type: custom
build-shared: false
build-static: true
display-name: mbstring
ext-mbstring:
type: php-extension
php-extension:
arg-type: custom
ext-openssl:
type: php-extension
depends:
Expand All @@ -10,6 +24,21 @@ ext-openssl:
arg-type: custom
arg-type@windows: with
build-with-php: true
ext-phar:
type: php-extension
depends:
- zlib
ext-readline:
type: php-extension
depends:
- libedit
php-extension:
support:
Windows: wip
BSD: wip
arg-type: with-path
build-shared: false
build-static: true
ext-zlib:
type: php-extension
depends:
Expand Down
6 changes: 2 additions & 4 deletions config/pkg/ext/ext-amqp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ ext-amqp:
type: php-extension
artifact:
source:
type: url
url: 'https://pecl.php.net/get/amqp'
extract: php-src/ext/amqp
filename: amqp.tgz
type: pecl
name: amqp
metadata:
license-files: [LICENSE]
license: PHP-3.01
Expand Down
6 changes: 2 additions & 4 deletions config/pkg/ext/ext-apcu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ ext-apcu:
type: php-extension
artifact:
source:
type: url
url: 'https://pecl.php.net/get/APCu'
extract: php-src/ext/apcu
filename: apcu.tgz
type: pecl
name: APCu
metadata:
license-files: [LICENSE]
license: PHP-3.01
6 changes: 6 additions & 0 deletions config/pkg/ext/ext-ast.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ext-ast:
type: php-extension
artifact:
source:
type: pecl
name: ast
10 changes: 0 additions & 10 deletions config/pkg/ext/ext-mbregex.yml

This file was deleted.

4 changes: 0 additions & 4 deletions config/pkg/ext/ext-mbstring.yml

This file was deleted.

4 changes: 0 additions & 4 deletions config/pkg/ext/ext-phar.yml

This file was deleted.

11 changes: 0 additions & 11 deletions config/pkg/ext/ext-readline.yml

This file was deleted.

4 changes: 2 additions & 2 deletions src/StaticPHP/Artifact/ArtifactCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public function lock(Artifact|string $artifact, string $lock_type, DownloadResul
throw new SPCInternalException("Invalid lock type '{$lock_type}' for artifact {$artifact_name}");
}
// save cache to file
file_put_contents($this->cache_file, json_encode($this->cache, JSON_PRETTY_PRINT));
file_put_contents($this->cache_file, json_encode($this->cache, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}

/**
Expand Down Expand Up @@ -281,7 +281,7 @@ public function removeBinary(string $artifact_name, string $platform, bool $dele
*/
public function save(): void
{
file_put_contents($this->cache_file, json_encode($this->cache, JSON_PRETTY_PRINT));
file_put_contents($this->cache_file, json_encode($this->cache, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}

private function isObjectDownloaded(?array $object, bool $compare_hash = false): bool
Expand Down
4 changes: 2 additions & 2 deletions src/StaticPHP/Artifact/Downloader/Type/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function download(string $name, array $config, ArtifactDownloader $downlo
if (isset($config['rev'])) {
default_shell()->executeGitClone($config['url'], $config['rev'], $path, $shallow, $config['submodules'] ?? null);
$shell = PHP_OS_FAMILY === 'Windows' ? cmd(false) : shell(false);
$hash_result = $shell->execWithResult(SPC_GIT_EXEC . ' -C ' . escapeshellarg($path) . ' rev-parse HEAD');
$hash_result = $shell->execWithResult(SPC_GIT_EXEC . ' -C ' . escapeshellarg($path) . ' rev-parse --short HEAD');
$hash = ($hash_result[0] === 0 && !empty($hash_result[1])) ? trim($hash_result[1][0]) : '';
$version = $hash !== '' ? "dev-{$config['rev']}+{$hash}" : "dev-{$config['rev']}";
return DownloadResult::git($name, $config, extract: $config['extract'] ?? null, version: $version, downloader: static::class);
Expand Down Expand Up @@ -80,7 +80,7 @@ public function checkUpdate(string $name, array $config, ?string $old_version, A
if ($result[0] !== 0 || empty($result[1])) {
throw new DownloaderException("Failed to ls-remote from {$config['url']}");
}
$new_hash = substr($result[1][0], 0, 40);
$new_hash = substr($result[1][0], 0, 7);
$new_version = "dev-{$config['rev']}+{$new_hash}";
// Extract stored hash from "dev-{rev}+{hash}", null if bare mode or old format without hash
$old_hash = ($old_version !== null && str_contains($old_version, '+')) ? substr(strrchr($old_version, '+'), 1) : null;
Expand Down
79 changes: 79 additions & 0 deletions src/StaticPHP/Artifact/Downloader/Type/PECL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace StaticPHP\Artifact\Downloader\Type;

use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Exception\DownloaderException;

/* pecl */
class PECL implements DownloadTypeInterface, CheckUpdateInterface
{
private const string PECL_BASE_URL = 'https://pecl.php.net';

/** REST API: returns XML with <r><v>VERSION</v><s>STATE</s></r> per release */
private const string PECL_REST_URL = 'https://pecl.php.net/rest/r/%s/allreleases.xml';

public function checkUpdate(string $name, array $config, ?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
[, $version] = $this->fetchPECLInfo($name, $config, $downloader);
return new CheckUpdateResult(
old: $old_version,
new: $version,
needUpdate: $old_version === null || version_compare($version, $old_version, '>'),
);
}

public function download(string $name, array $config, ArtifactDownloader $downloader): DownloadResult
{
[$filename, $version] = $this->fetchPECLInfo($name, $config, $downloader);
$url = self::PECL_BASE_URL . '/get/' . $filename;
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
logger()->debug("Downloading {$name} from URL: {$url}");
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
$extract = $config['extract'] ?? ('php-src/ext/' . $this->getExtractName($name));
return DownloadResult::archive($filename, $config, $extract, version: $version, downloader: static::class);
}

protected function fetchPECLInfo(string $name, array $config, ArtifactDownloader $downloader): array
{
$peclName = strtolower($config['name'] ?? $this->getExtractName($name));
$url = sprintf(self::PECL_REST_URL, $peclName);
logger()->debug("Fetching PECL release list for {$name} from REST API");
$xml = default_shell()->executeCurl($url, retries: $downloader->getRetry());
if ($xml === false) {
throw new DownloaderException("Failed to fetch PECL release list for {$name}");
}
// Match <r><v>VERSION</v><s>STATE</s></r>
preg_match_all('/<r><v>(?P<version>[^<]+)<\/v><s>(?P<state>[^<]+)<\/s><\/r>/', $xml, $matches);
if (empty($matches['version'])) {
throw new DownloaderException("Failed to parse PECL release list for {$name}");
}
$versions = [];
logger()->debug('Matched ' . count($matches['version']) . " releases for {$name} from PECL");
foreach ($matches['version'] as $i => $version) {
if ($matches['state'][$i] !== 'stable') {
continue;
}
$versions[$version] = $peclName . '-' . $version . '.tgz';
}
if (empty($versions)) {
throw new DownloaderException("No stable releases found for {$name} on PECL");
}
uksort($versions, 'version_compare');
$filename = end($versions);
$version = array_key_last($versions);
return [$filename, $version, $versions];
}

/**
* Derive the lowercase PECL package / extract name from the artifact name.
* e.g. "ext-apcu" -> "apcu", "ext-ast" -> "ast"
*/
private function getExtractName(string $name): string
{
return strtolower(preg_replace('/^ext-/i', '', $name));
}
}
1 change: 1 addition & 0 deletions src/StaticPHP/Config/ConfigValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class ConfigValidator
'bitbuckettag' => [['repo'], ['extract']],
'local' => [['dirname'], ['extract']],
'pie' => [['repo'], ['extract']],
'pecl' => [['name'], ['extract']],
'php-release' => [[], ['extract']],
'custom' => [[], ['func']],
];
Expand Down
9 changes: 5 additions & 4 deletions src/StaticPHP/Runtime/Shell/DefaultShell.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function exec(string $cmd): static
/**
* Execute a cURL command to fetch data from a URL.
*/
public function executeCurl(string $url, string $method = 'GET', array $headers = [], array $hooks = [], int $retries = 0): false|string
public function executeCurl(string $url, string $method = 'GET', array $headers = [], array $hooks = [], int $retries = 0, bool $compressed = false): false|string
{
foreach ($hooks as $hook) {
$hook($method, $url, $headers);
Expand All @@ -39,7 +39,8 @@ public function executeCurl(string $url, string $method = 'GET', array $headers
};
$header_arg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$retry_arg = $retries > 0 ? "--retry {$retries}" : '';
$cmd = SPC_CURL_EXEC . " -sfSL {$retry_arg} {$method_arg} {$header_arg} {$url_arg}";
$compressed_arg = $compressed ? '--compressed' : '';
$cmd = SPC_CURL_EXEC . " -sfSL --max-time 3600 {$retry_arg} {$compressed_arg} {$method_arg} {$header_arg} {$url_arg}";

$this->logCommandInfo($cmd);
$result = $this->passthru($cmd, capture_output: true, throw_on_error: false);
Expand Down Expand Up @@ -72,7 +73,7 @@ public function executeCurlDownload(string $url, string $path, array $headers =
$header_arg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$retry_arg = $retries > 0 ? "--retry {$retries}" : '';
$check = $this->console_putput ? '#' : 's';
$cmd = clean_spaces(SPC_CURL_EXEC . " -{$check}fSL {$retry_arg} {$header_arg} -o {$path_arg} {$url_arg}");
$cmd = clean_spaces(SPC_CURL_EXEC . " -{$check}fSL --max-time 3600 {$retry_arg} {$header_arg} -o {$path_arg} {$url_arg}");
$this->logCommandInfo($cmd);
logger()->debug('[CURL DOWNLOAD] ' . $cmd);
$this->passthru($cmd, $this->console_putput, capture_output: false, throw_on_error: true);
Expand All @@ -93,7 +94,7 @@ public function executeGitClone(string $url, string $branch, string $path, bool
$path_arg = escapeshellarg($path);
$shallow_arg = $shallow ? '--depth 1 --single-branch' : '';
$submodules_arg = ($submodules === null && $shallow) ? '--recursive --shallow-submodules' : ($submodules === null ? '--recursive' : '');
$cmd = clean_spaces("{$git} clone --config core.autocrlf=false --branch {$branch_arg} {$shallow_arg} {$submodules_arg} {$url_arg} {$path_arg}");
$cmd = clean_spaces("{$git} clone -c http.lowSpeedLimit=1 -c http.lowSpeedTime=3600 --config core.autocrlf=false --branch {$branch_arg} {$shallow_arg} {$submodules_arg} {$url_arg} {$path_arg}");
$this->logCommandInfo($cmd);
logger()->debug("[GIT CLONE] {$cmd}");
$this->passthru($cmd, $this->console_putput);
Expand Down