mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-17 20:34:51 +08:00
Add parallel update checking and improve artifact update handling
This commit is contained in:
parent
abdaaab6e6
commit
84f6dab882
@ -282,6 +282,16 @@ class ArtifactCache
|
|||||||
logger()->debug("Removed binary cache entry for [{$artifact_name}] on platform [{$platform}]");
|
logger()->debug("Removed binary cache entry for [{$artifact_name}] on platform [{$platform}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the names of all artifacts that have at least one downloaded entry (source or binary).
|
||||||
|
*
|
||||||
|
* @return array<string> Artifact names
|
||||||
|
*/
|
||||||
|
public function getCachedArtifactNames(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->cache);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save cache to file.
|
* Save cache to file.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -362,7 +362,8 @@ class ArtifactDownloader
|
|||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking.");
|
// logger()->warning("Artifact '{$artifact_name}' downloader does not support update checking, skipping.");
|
||||||
|
return new CheckUpdateResult(old: null, new: null, needUpdate: false, unsupported: true);
|
||||||
}
|
}
|
||||||
$cache = ApplicationContext::get(ArtifactCache::class);
|
$cache = ApplicationContext::get(ArtifactCache::class);
|
||||||
if ($prefer_source) {
|
if ($prefer_source) {
|
||||||
@ -393,7 +394,33 @@ class ArtifactDownloader
|
|||||||
'old_version' => $info['version'],
|
'old_version' => $info['version'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
throw new WrongUsageException("Artifact '{$artifact_name}' downloader does not support update checking, exit.");
|
// logger()->warning("Artifact '{$artifact_name}' downloader does not support update checking, skipping.");
|
||||||
|
return new CheckUpdateResult(old: null, new: null, needUpdate: false, unsupported: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check updates for multiple artifacts, with optional parallel processing.
|
||||||
|
*
|
||||||
|
* @param array<string> $artifact_names Artifact names to check
|
||||||
|
* @param bool $prefer_source Whether to prefer source over binary
|
||||||
|
* @param bool $bare Check without requiring artifact to be downloaded first
|
||||||
|
* @param null|callable $onResult Called immediately with (string $name, CheckUpdateResult) as each result arrives
|
||||||
|
* @return array<string, CheckUpdateResult> Results keyed by artifact name
|
||||||
|
*/
|
||||||
|
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null): array
|
||||||
|
{
|
||||||
|
if ($this->parallel > 1 && count($artifact_names) > 1) {
|
||||||
|
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult);
|
||||||
|
}
|
||||||
|
$results = [];
|
||||||
|
foreach ($artifact_names as $name) {
|
||||||
|
$result = $this->checkUpdate($name, $prefer_source, $bare);
|
||||||
|
$results[$name] = $result;
|
||||||
|
if ($onResult !== null) {
|
||||||
|
($onResult)($name, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRetry(): int
|
public function getRetry(): int
|
||||||
@ -411,6 +438,61 @@ class ArtifactDownloader
|
|||||||
return $this->options[$name] ?? $default;
|
return $this->options[$name] ?? $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult): array
|
||||||
|
{
|
||||||
|
$results = [];
|
||||||
|
$fiber_pool = [];
|
||||||
|
$remaining = $artifact_names;
|
||||||
|
|
||||||
|
Shell::passthruCallback(function () {
|
||||||
|
\Fiber::suspend();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!empty($remaining) || !empty($fiber_pool)) {
|
||||||
|
// fill pool
|
||||||
|
while (count($fiber_pool) < $this->parallel && !empty($remaining)) {
|
||||||
|
$name = array_shift($remaining);
|
||||||
|
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare) {
|
||||||
|
return [$name, $this->checkUpdate($name, $prefer_source, $bare)];
|
||||||
|
});
|
||||||
|
$fiber->start();
|
||||||
|
$fiber_pool[$name] = $fiber;
|
||||||
|
}
|
||||||
|
// check pool
|
||||||
|
foreach ($fiber_pool as $fiber_name => $fiber) {
|
||||||
|
if ($fiber->isTerminated()) {
|
||||||
|
// getReturn() re-throws if the fiber threw — propagates immediately
|
||||||
|
[$artifact_name, $result] = $fiber->getReturn();
|
||||||
|
$results[$artifact_name] = $result;
|
||||||
|
if ($onResult !== null) {
|
||||||
|
($onResult)($artifact_name, $result);
|
||||||
|
}
|
||||||
|
unset($fiber_pool[$fiber_name]);
|
||||||
|
} else {
|
||||||
|
$fiber->resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// terminate all still-suspended fibers so their curl processes don't hang
|
||||||
|
foreach ($fiber_pool as $fiber) {
|
||||||
|
if (!$fiber->isTerminated()) {
|
||||||
|
try {
|
||||||
|
$fiber->throw($e);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
// ignore — we only care about stopping them
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
Shell::passthruCallback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
|
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
|
||||||
{
|
{
|
||||||
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
|
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
|
||||||
|
|||||||
@ -8,7 +8,8 @@ readonly class CheckUpdateResult
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ?string $old,
|
public ?string $old,
|
||||||
public string $new,
|
public ?string $new,
|
||||||
public bool $needUpdate,
|
public bool $needUpdate,
|
||||||
|
public bool $unsupported = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace StaticPHP\Command;
|
namespace StaticPHP\Command;
|
||||||
|
|
||||||
|
use StaticPHP\Artifact\ArtifactCache;
|
||||||
use StaticPHP\Artifact\ArtifactDownloader;
|
use StaticPHP\Artifact\ArtifactDownloader;
|
||||||
|
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
|
||||||
|
use StaticPHP\DI\ApplicationContext;
|
||||||
use StaticPHP\Exception\SPCException;
|
use StaticPHP\Exception\SPCException;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
@ -17,9 +20,10 @@ class CheckUpdateCommand extends BaseCommand
|
|||||||
|
|
||||||
public function configure(): void
|
public function configure(): void
|
||||||
{
|
{
|
||||||
$this->addArgument('artifact', InputArgument::REQUIRED, 'The name of the artifact(s) to check for updates, comma-separated');
|
$this->addArgument('artifact', InputArgument::OPTIONAL, 'The name of the artifact(s) to check for updates, comma-separated (default: all downloaded artifacts)');
|
||||||
$this->addOption('json', null, null, 'Output result in JSON format');
|
$this->addOption('json', null, null, 'Output result in JSON format');
|
||||||
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
|
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
|
||||||
|
$this->addOption('parallel', 'p', InputOption::VALUE_REQUIRED, 'Number of parallel update checks (default: 10)', 10);
|
||||||
|
|
||||||
// --with-php option for checking updates with a specific PHP version context
|
// --with-php option for checking updates with a specific PHP version context
|
||||||
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'PHP version in major.minor format (default 8.4)', '8.4');
|
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'PHP version in major.minor format (default 8.4)', '8.4');
|
||||||
@ -27,17 +31,27 @@ class CheckUpdateCommand extends BaseCommand
|
|||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$artifacts = parse_comma_list($this->input->getArgument('artifact'));
|
$artifact_arg = $this->input->getArgument('artifact');
|
||||||
|
if ($artifact_arg === null) {
|
||||||
|
$artifacts = ApplicationContext::get(ArtifactCache::class)->getCachedArtifactNames();
|
||||||
|
if (empty($artifacts)) {
|
||||||
|
$this->output->writeln('<comment>No downloaded artifacts found.</comment>');
|
||||||
|
return static::OK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$artifacts = parse_comma_list($artifact_arg);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$downloader = new ArtifactDownloader($this->input->getOptions());
|
$downloader = new ArtifactDownloader($this->input->getOptions());
|
||||||
$bare = (bool) $this->getOption('bare');
|
$bare = (bool) $this->getOption('bare');
|
||||||
if ($this->getOption('json')) {
|
if ($this->getOption('json')) {
|
||||||
|
$results = $downloader->checkUpdates($artifacts, bare: $bare);
|
||||||
$outputs = [];
|
$outputs = [];
|
||||||
foreach ($artifacts as $artifact) {
|
foreach ($results as $artifact => $result) {
|
||||||
$result = $downloader->checkUpdate($artifact, bare: $bare);
|
|
||||||
$outputs[$artifact] = [
|
$outputs[$artifact] = [
|
||||||
'need-update' => $result->needUpdate,
|
'need-update' => $result->needUpdate,
|
||||||
|
'unsupported' => $result->unsupported,
|
||||||
'old' => $result->old,
|
'old' => $result->old,
|
||||||
'new' => $result->new,
|
'new' => $result->new,
|
||||||
];
|
];
|
||||||
@ -45,15 +59,17 @@ class CheckUpdateCommand extends BaseCommand
|
|||||||
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||||
return static::OK;
|
return static::OK;
|
||||||
}
|
}
|
||||||
foreach ($artifacts as $artifact) {
|
$downloader->checkUpdates($artifacts, bare: $bare, onResult: function (string $artifact, CheckUpdateResult $result) {
|
||||||
$result = $downloader->checkUpdate($artifact, bare: $bare);
|
if ($result->unsupported) {
|
||||||
if (!$result->needUpdate) {
|
$this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>");
|
||||||
$this->output->writeln("Artifact <info>{$artifact}</info> is already up to date (<comment>{$result->new}</comment>)");
|
} elseif (!$result->needUpdate) {
|
||||||
|
$ver = $result->new ? "(<comment>{$result->new}</comment>)" : '';
|
||||||
|
$this->output->writeln("Artifact <info>{$artifact}</info> is already up to date {$ver}");
|
||||||
} else {
|
} else {
|
||||||
[$old, $new] = [$result->old ?? 'unavailable', $result->new ?? 'unknown'];
|
[$old, $new] = [$result->old ?? 'unavailable', $result->new ?? 'unknown'];
|
||||||
$this->output->writeln("Update available for <info>{$artifact}</info>: <comment>{$old}</comment> -> <comment>{$new}</comment>");
|
$this->output->writeln("Update available for <info>{$artifact}</info>: <comment>{$old}</comment> -> <comment>{$new}</comment>");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return static::OK;
|
return static::OK;
|
||||||
} catch (SPCException $e) {
|
} catch (SPCException $e) {
|
||||||
$e->setSimpleOutput();
|
$e->setSimpleOutput();
|
||||||
|
|||||||
@ -104,7 +104,7 @@ const SPC_DOWNLOAD_TYPE_DISPLAY_NAME = [
|
|||||||
'local' => 'local dir',
|
'local' => 'local dir',
|
||||||
'pie' => 'PHP Installer for Extensions (PIE)',
|
'pie' => 'PHP Installer for Extensions (PIE)',
|
||||||
'url' => 'url',
|
'url' => 'url',
|
||||||
'php-release' => 'php.net',
|
'php-release' => 'PHP website release',
|
||||||
'custom' => 'custom downloader',
|
'custom' => 'custom downloader',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user