Skip to content
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ This repository contains commands to help you work with the RoadRunner, such as:
Using the `preset` option can create an example configuration file with popular plugins for different typical tasks.
For example, with web preset: `get-binary --preset web`.
Available presets: `web` (contains plugins `http`, `jobs`).
- `download-protoc-binary` - allows to install the latest version of the `protoc-gen-php-grpc` file compatible with
your environment (operating system, processor architecture, runtime, etc...).
- `versions` - displays a list of available RoadRunner binary versions.

Testing:
Expand Down
2 changes: 2 additions & 0 deletions bin/rr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

declare(strict_types=1);

use Spiral\RoadRunner\Console\DownloadProtocBinaryCommand;
use Spiral\RoadRunner\Console\GetBinaryCommand;
use Spiral\RoadRunner\Console\VersionsCommand;
use Spiral\RoadRunner\Version;
Expand Down Expand Up @@ -68,5 +69,6 @@ $app = new Symfony\Component\Console\Application('RoadRunner CLI', Version::curr

$app->add(new GetBinaryCommand());
$app->add(new VersionsCommand());
$app->add(new DownloadProtocBinaryCommand());

$app->run();
233 changes: 233 additions & 0 deletions src/DownloadProtocBinaryCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<?php

declare(strict_types=1);

namespace Spiral\RoadRunner\Console;

use Spiral\RoadRunner\Console\Archive\ArchiveInterface;
use Spiral\RoadRunner\Console\Archive\Factory;
use Spiral\RoadRunner\Console\Command\ArchitectureOption;
use Spiral\RoadRunner\Console\Command\InstallationLocationOption;
use Spiral\RoadRunner\Console\Command\OperatingSystemOption;
use Spiral\RoadRunner\Console\Command\StabilityOption;
use Spiral\RoadRunner\Console\Command\VersionFilterOption;
use Spiral\RoadRunner\Console\Repository\AssetInterface;
use Spiral\RoadRunner\Console\Repository\ReleaseInterface;
use Spiral\RoadRunner\Console\Repository\ReleasesCollection;
use Spiral\RoadRunner\Console\Repository\RepositoryInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\StyleInterface;

/**
* protoc-gen-php-grpc file download command.
*/
final class DownloadProtocBinaryCommand extends Command
{
private const ERROR_ENVIRONMENT = 'Could not find any available protoc-gen-php-grpc binary version which meets criterion (--%s=%s --%s=%s --%s=%s). Available: %s';

private OperatingSystemOption $os;
private ArchitectureOption $arch;
private VersionFilterOption $version;
private StabilityOption $stability;
private InstallationLocationOption $location;

public function __construct(string $name = null)
{
parent::__construct($name ?? 'download-protoc-binary');

$this->os = new OperatingSystemOption($this);
$this->arch = new ArchitectureOption($this);
$this->version = new VersionFilterOption($this);
$this->location = new InstallationLocationOption($this);
$this->stability = new StabilityOption($this);
}

public function getDescription(): string
{
return 'Install or update protoc-gen-php-grpc binary';
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$io = $this->io($input, $output);

$target = $this->location->get($input, $io);
$repository = $this->getRepository();

$output->writeln('');
$output->writeln(' Environment:');
$output->writeln(\sprintf(' - Version: <info>%s</info>', $this->version->get($input, $io)));
$output->writeln(\sprintf(' - Stability: <info>%s</info>', $this->stability->get($input, $io)));
$output->writeln(\sprintf(' - Operating System: <info>%s</info>', $this->os->get($input, $io)));
$output->writeln(\sprintf(' - Architecture: <info>%s</info>', $this->arch->get($input, $io)));
$output->writeln('');

// List of all available releases
$releases = $this->version->find($input, $io, $repository);

/**
* @var AssetInterface $asset
* @var ReleaseInterface $release
*/
[$asset, $release] = $this->findAsset($repository, $releases, $input, $io);

// Installation
$output->writeln(
\sprintf(" - <info>%s</info>", $release->getRepositoryName()) .
\sprintf(' (<comment>%s</comment>):', $release->getVersion()) .
' Downloading...'
);

if ($output->isVerbose()) {
$output->writeln(\sprintf(" -- <info>%s</info>", $asset->getName()));
}

// Install rr binary
$file = $this->installBinary($target, $release, $asset, $io, $output);

// Success
if ($file === null) {
$io->warning('protoc-gen-php-grpc has not been installed');

return 1;
}

return 0;
}

private function installBinary(
string $target,
ReleaseInterface $release,
AssetInterface $asset,
StyleInterface $io,
OutputInterface $out
): ?\SplFileInfo {
$extractor = $this->assetToArchive($asset, $out)
->extract([
'protoc-gen-php-grpc.exe' => $target . '/protoc-gen-php-grpc.exe',
'protoc-gen-php-grpc' => $target . '/protoc-gen-php-grpc',
]);

$file = null;
while ($extractor->valid()) {
$file = $extractor->current();

if (!$this->checkExisting($file, $io)) {
$extractor->send(false);
continue;
}

// Success
$path = $file->getRealPath() ?: $file->getPathname();
$message = 'protoc-gen-php-grpc (<comment>%s</comment>) has been installed into <info>%s</info>';
$message = \sprintf($message, $release->getVersion(), $path);
$out->writeln($message);

$extractor->next();

if (!$file->isExecutable()) {
@chmod($file->getRealPath(), 0755);
}
}

return $file;
}

private function checkExisting(\SplFileInfo $bin, StyleInterface $io): bool
{
if (\is_file($bin->getPathname())) {
$io->warning('protoc-gen-php-grpc binary file already exists!');

if (!$io->confirm('Do you want overwrite it?', false)) {
$io->note('Skipping protoc-gen-php-grpc installation...');

return false;
}
}

return true;
}

private function findAsset(
RepositoryInterface $repo,
ReleasesCollection $releases,
InputInterface $in,
StyleInterface $io
): array {
$osOption = $this->os->get($in, $io);
$archOption = $this->arch->get($in, $io);
$stabilityOption = $this->stability->get($in, $io);

/** @var ReleaseInterface[] $filtered */
$filtered = $releases
->minimumStability($stabilityOption)
->withAssets();

foreach ($filtered as $release) {
$asset = $release->getAssets()
->filter(static fn (AssetInterface $asset): bool =>
0 === \strncmp($asset->getName(), 'protoc-gen-php-grpc', \strlen('protoc-gen-php-grpc'))
)
->whereArchitecture($archOption)
->whereOperatingSystem($osOption)
->first();

if ($asset === null) {
$io->warning(
\vsprintf('%s %s does not contain available assembly (further search in progress)', [
$repo->getName(),
$release->getVersion(),
])
);

continue;
}

return [$asset, $release];
}

$message = \vsprintf(self::ERROR_ENVIRONMENT, [
$this->os->getName(),
$osOption,
$this->arch->getName(),
$archOption,
$this->stability->getName(),
$stabilityOption,
$this->version->choices($releases),
]);

throw new \UnexpectedValueException($message);
}

private function assetToArchive(AssetInterface $asset, OutputInterface $out, string $temp = null): ArchiveInterface
{
$factory = new Factory();

$progress = new ProgressBar($out);
$progress->setFormat(' [%bar%] %percent:3s%% (%size%Kb/%total%Kb)');
$progress->setMessage('0.00', 'size');
$progress->setMessage('?.??', 'total');
$progress->display();

try {
return $factory->fromAsset($asset, function (int $size, int $total) use ($progress) {
if ($progress->getMaxSteps() !== $total) {
$progress->setMaxSteps($total);
}

if ($progress->getStartTime() === 0) {
$progress->start();
}

$progress->setMessage(\number_format($size / 1000, 2), 'size');
$progress->setMessage(\number_format($total / 1000, 2), 'total');

$progress->setProgress($size);
}, $temp);
} finally {
$progress->clear();
}
}
}