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
21 changes: 21 additions & 0 deletions src/Package/Artifact/go_xcaddy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\GlobalEnvManager;
Expand Down Expand Up @@ -65,6 +67,25 @@ public function downBinary(ArtifactDownloader $downloader): DownloadResult
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: "{$pkgroot}/go-xcaddy", verified: true, version: $version);
}

#[CustomBinaryCheckUpdate('go-xcaddy', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
[$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text') ?: '');
if ($version === '') {
throw new DownloaderException('Failed to get latest Go version from https://go.dev/VERSION?m=text');
}
return new CheckUpdateResult(
old: $old_version,
new: $version,
needUpdate: $old_version === null || $version !== $old_version,
);
}

#[AfterBinaryExtract('go-xcaddy', [
'linux-x86_64',
'linux-aarch64',
Expand Down
29 changes: 29 additions & 0 deletions src/Package/Artifact/zig.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Runtime\SystemTarget;

Expand Down Expand Up @@ -59,6 +61,33 @@ public function downBinary(ArtifactDownloader $downloader): DownloadResult
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/zig', verified: true, version: $latest_version);
}

#[CustomBinaryCheckUpdate('zig', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
$index_json = default_shell()->executeCurl('https://ziglang.org/download/index.json', retries: $downloader->getRetry());
$index_json = json_decode($index_json ?: '', true);
$latest_version = null;
foreach ($index_json as $version => $data) {
if ($version !== 'master') {
$latest_version = $version;
break;
}
}
if (!$latest_version) {
throw new DownloaderException('Could not determine latest Zig version');
}
return new CheckUpdateResult(
old: $old_version,
new: $latest_version,
needUpdate: $old_version === null || version_compare($latest_version, $old_version, '>'),
);
}

#[AfterBinaryExtract('zig', [
'linux-x86_64',
'linux-aarch64',
Expand Down
37 changes: 37 additions & 0 deletions src/StaticPHP/Artifact/Artifact.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ 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<string, callable> Bind custom binary fetcher callbacks */
protected mixed $custom_binary_callbacks = [];

/** @var array<string, callable> Bind custom binary check-update callbacks */
protected array $custom_binary_check_update_callbacks = [];

/** @var null|callable Bind custom source extract callback (completely takes over extraction) */
protected mixed $source_extract_callback = null;

Expand Down Expand Up @@ -405,6 +411,19 @@ public function getCustomSourceCallback(): ?callable
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();
Expand Down Expand Up @@ -433,6 +452,24 @@ public function setCustomBinaryCallback(string $target_os, callable $callback):
$this->custom_binary_callbacks[$target_os] = $callback;
}

/**
* Set custom binary check-update callback for a specific target OS.
*
* @param string $target_os Target OS platform string (e.g. linux-x86_64)
* @param callable $callback Custom binary check-update callback
*/
public function setCustomBinaryCheckUpdateCallback(string $target_os, callable $callback): void
{
ConfigValidator::validatePlatformString($target_os);
$this->custom_binary_check_update_callbacks[$target_os] = $callback;
}

public function getCustomBinaryCheckUpdateCallback(): ?callable
{
$current_platform = SystemTarget::getCurrentPlatformString();
return $this->custom_binary_check_update_callbacks[$current_platform] ?? null;
}

// ==================== Extraction Callbacks ====================

/**
Expand Down
10 changes: 8 additions & 2 deletions src/StaticPHP/Artifact/ArtifactCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class ArtifactCache
* filename?: string,
* dirname?: string,
* extract: null|'&custom'|string,
* hash: null|string
* hash: null|string,
* downloader: null|string
* },
* binary: array{
* windows-x86_64?: null|array{
Expand All @@ -28,7 +29,8 @@ class ArtifactCache
* dirname?: string,
* extract: null|'&custom'|string,
* hash: null|string,
* version?: null|string
* version?: null|string,
* downloader: null|string
* }
* }
* }>
Expand Down Expand Up @@ -108,6 +110,7 @@ public function lock(Artifact|string $artifact, string $lock_type, DownloadResul
'hash' => sha1_file(DOWNLOAD_PATH . '/' . $download_result->filename),
'version' => $download_result->version,
'config' => $download_result->config,
'downloader' => $download_result->downloader,
];
} elseif ($download_result->cache_type === 'file') {
$obj = [
Expand All @@ -118,6 +121,7 @@ public function lock(Artifact|string $artifact, string $lock_type, DownloadResul
'hash' => sha1_file(DOWNLOAD_PATH . '/' . $download_result->filename),
'version' => $download_result->version,
'config' => $download_result->config,
'downloader' => $download_result->downloader,
];
} elseif ($download_result->cache_type === 'git') {
$obj = [
Expand All @@ -128,6 +132,7 @@ public function lock(Artifact|string $artifact, string $lock_type, DownloadResul
'hash' => trim(exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $download_result->dirname) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD')),
'version' => $download_result->version,
'config' => $download_result->config,
'downloader' => $download_result->downloader,
];
} elseif ($download_result->cache_type === 'local') {
$obj = [
Expand All @@ -138,6 +143,7 @@ public function lock(Artifact|string $artifact, string $lock_type, DownloadResul
'hash' => null,
'version' => $download_result->version,
'config' => $download_result->config,
'downloader' => $download_result->downloader,
];
}
if ($obj === null) {
Expand Down
94 changes: 94 additions & 0 deletions src/StaticPHP/Artifact/ArtifactDownloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Psr\Log\LogLevel;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateInterface;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Artifact\Downloader\Type\DownloadTypeInterface;
use StaticPHP\Artifact\Downloader\Type\Git;
use StaticPHP\Artifact\Downloader\Type\LocalDir;
Expand Down Expand Up @@ -323,6 +325,54 @@ public function download(bool $interactive = true): void
}
}

public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
{
$artifact = ArtifactLoader::getArtifactInstance($artifact_name);
if ($artifact === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name.");
}
if ($bare) {
[$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;
}
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking.");
}
$cache = ApplicationContext::get(ArtifactCache::class);
if ($prefer_source) {
$info = $cache->getSourceInfo($artifact_name) ?? $cache->getBinaryInfo($artifact_name, SystemTarget::getCurrentPlatformString());
} else {
$info = $cache->getBinaryInfo($artifact_name, SystemTarget::getCurrentPlatformString()) ?? $cache->getSourceInfo($artifact_name);
}
if ($info === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' is not downloaded yet, cannot check update.");
}
if (is_a($info['downloader'] ?? null, CheckUpdateInterface::class, true)) {
$cls = $info['downloader'];
/** @var CheckUpdateInterface $downloader */
$downloader = new $cls();
return $downloader->checkUpdate($artifact_name, $info['config'], $info['version'], $this);
}

if (($info['lock_type'] ?? null) === 'source' && ($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => $info['version'],
]);
}

if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => $info['version'],
]);
}
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking, exit.");
}

public function getRetry(): int
{
return $this->retry;
Expand All @@ -338,6 +388,50 @@ public function getOption(string $name, mixed $default = null): mixed
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);
Expand Down
26 changes: 16 additions & 10 deletions src/StaticPHP/Artifact/Downloader/DownloadResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class DownloadResult
* @param bool $verified Whether the download has been verified (hash check)
* @param null|string $version Version of the downloaded artifact (e.g., "1.2.3", "v2.0.0")
* @param array $metadata Additional metadata (e.g., commit hash, release notes, etc.)
* @param null|string $downloader Class name of the downloader that performed this download
*/
private function __construct(
public readonly string $cache_type,
Expand All @@ -27,6 +28,7 @@ private function __construct(
public bool $verified = false,
public readonly ?string $version = null,
public readonly array $metadata = [],
public readonly ?string $downloader = null,
) {
switch ($this->cache_type) {
case 'archive':
Expand Down Expand Up @@ -59,22 +61,24 @@ public static function archive(
mixed $extract = null,
bool $verified = false,
?string $version = null,
array $metadata = []
array $metadata = [],
?string $downloader = null,
): DownloadResult {
// judge if it is archive or just a pure file
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata);
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
}

public static function file(
string $filename,
array $config,
bool $verified = false,
?string $version = null,
array $metadata = []
array $metadata = [],
?string $downloader = null,
): DownloadResult {
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata);
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
}

/**
Expand All @@ -85,9 +89,9 @@ public static function file(
* @param null|string $version Version string (tag, branch, or commit)
* @param array $metadata Additional metadata (e.g., commit hash)
*/
public static function git(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = []): DownloadResult
public static function git(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = [], ?string $downloader = null): DownloadResult
{
return new self('git', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
return new self('git', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata, downloader: $downloader);
}

/**
Expand All @@ -98,9 +102,9 @@ public static function git(string $dirname, array $config, mixed $extract = null
* @param null|string $version Version string if known
* @param array $metadata Additional metadata
*/
public static function local(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = []): DownloadResult
public static function local(string $dirname, array $config, mixed $extract = null, ?string $version = null, array $metadata = [], ?string $downloader = null): DownloadResult
{
return new self('local', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata);
return new self('local', config: $config, dirname: $dirname, extract: $extract, version: $version, metadata: $metadata, downloader: $downloader);
}

/**
Expand Down Expand Up @@ -136,7 +140,8 @@ public function withVersion(string $version): self
$this->extract,
$this->verified,
$version,
$this->metadata
$this->metadata,
$this->downloader,
);
}

Expand All @@ -154,7 +159,8 @@ public function withMeta(string $key, mixed $value): self
$this->extract,
$this->verified,
$this->version,
array_merge($this->metadata, [$key => $value])
array_merge($this->metadata, [$key => $value]),
$this->downloader,
);
}

Expand Down
Loading