diff --git a/README.md b/README.md index 6dfdf21..7a2a01d 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/bin/rr b/bin/rr index c01b3c1..6156730 100644 --- a/bin/rr +++ b/bin/rr @@ -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; @@ -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(); diff --git a/src/DownloadProtocBinaryCommand.php b/src/DownloadProtocBinaryCommand.php new file mode 100644 index 0000000..6c89569 --- /dev/null +++ b/src/DownloadProtocBinaryCommand.php @@ -0,0 +1,233 @@ +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: %s', $this->version->get($input, $io))); + $output->writeln(\sprintf(' - Stability: %s', $this->stability->get($input, $io))); + $output->writeln(\sprintf(' - Operating System: %s', $this->os->get($input, $io))); + $output->writeln(\sprintf(' - Architecture: %s', $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(" - %s", $release->getRepositoryName()) . + \sprintf(' (%s):', $release->getVersion()) . + ' Downloading...' + ); + + if ($output->isVerbose()) { + $output->writeln(\sprintf(" -- %s", $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 (%s) has been installed into %s'; + $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(); + } + } +}