Builder.php 7.50 KiB
<?php declare(strict_types=1);
/*
 * This file is part of "irstea/make-shim".
 * (c) 2019-2020 Irstea <dsi.poleis@irstea.fr>
 * For the full copyright and license information, please view the LICENSE.md
 * file that was distributed with this source code.
 */
namespace Irstea\MakeShim\Builder;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\StreamWrapper;
use Irstea\MakeShim\Packagist\Package;
use Irstea\MakeShim\Signature\NullVerifier;
use Irstea\MakeShim\Signature\VerifierInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use function Safe\file_put_contents;
use function Safe\json_encode;
use Webmozart\PathUtil\Path;
/**
 * Class Builder.
class Builder implements BuilderInterface, LoggerAwareInterface
    use LoggerAwareTrait;
    /**
     * @var string
    private $archiveUrlTemplate;
    /**
     * @var string|null
    private $signatureUrltemplate;
    /**
     * @var string
    private $path;
    /**
     * @var string
    private $packageName;
    /**
     * @var VerifierInterface
    private $verifier;
    /**
     * @var ClientInterface
    private $client;
    /**
     * Builder constructor.
     * @param string                 $packageName
     * @param string                 $path
     * @param string                 $archiveUrlTemplate
     * @param string|null            $signatureUrltemplate
     * @param ClientInterface        $client
     * @param VerifierInterface|null $verifier
     * @param LoggerInterface|null   $logger
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
public function __construct(string $packageName, string $path, string $archiveUrlTemplate, ?string $signatureUrltemplate, ClientInterface $client, VerifierInterface $verifier = null, LoggerInterface $logger = null) { $this->path = $path; $this->archiveUrlTemplate = $archiveUrlTemplate; $this->signatureUrltemplate = $signatureUrltemplate; $this->setLogger($logger ?: new NullLogger()); $this->packageName = $packageName; $this->verifier = $verifier ?: new NullVerifier(); $this->client = $client; } /** * {@inheritdoc} */ public function build(Package $package): void { $this->logger->notice(sprintf('Building shim for %s', $package)); $gitAttributes = []; $pharURL = $this->formatUrl($this->archiveUrlTemplate, ['version' => $package->getVersion()]); $binPath = Path::join($this->path, $package->getBinary()); $changed = $this->download($pharURL, $binPath); \Safe\chmod($binPath, 0755); $gitAttributes[] = Path::makeRelative($binPath, $this->path) . ' binary'; if ($this->signatureUrltemplate) { $sigUrl = $this->formatUrl($this->signatureUrltemplate, ['version' => $package->getVersion()]); $sigPath = Path::join($this->path, $package->getBinary() . '.asc'); $changed = $this->download($sigUrl, $sigPath) || $changed; $gitAttributes[] = Path::makeRelative($sigPath, $this->path) . ' binary'; if ($changed) { $this->verifier->verify($sigPath, $binPath); } else { $this->logger->debug('neither phar nor signature changed, skipping verification'); } } file_put_contents(Path::join($this->path, '.gitattributes'), implode("\n", $gitAttributes)); $this->generateReadMe($package); $this->generateComposerConfig($package); } /** * @param string $template * @param array $values * * @return string */ private function formatUrl(string $template, array $values): string { return str_replace( array_map( static function (string $name): string { return '%' . $name . '%'; }, array_keys($values) ), array_values($values), $template ); } /**
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
* @param string $source * @param string $dest * * @return bool */ private function download(string $source, string $dest): bool { $checksum = \file_exists($dest) ? sha1_file($dest) : ''; try { $response = $this->client->request('GET', $source); $sourceStream = StreamWrapper::getResource($response->getBody()); try { $destDir = Path::getDirectory($dest); if (!is_dir($destDir)) { \Safe\mkdir($destDir, 0750, true); } $destStream = \Safe\fopen($dest, 'wb'); try { $size = \Safe\stream_copy_to_stream($sourceStream, $destStream); $this->logger->debug("received $size bytes from $source"); } finally { fclose($destStream); } } finally { fclose($sourceStream); } } catch (GuzzleException $exception) { throw new \RuntimeException(sprintf('could not fetch %s: %s', $source, $exception->getMessage())); } return $checksum !== sha1_file($dest); } /** * @param Package $package */ private function generateReadMe(Package $package): void { $conf = $package->getConfiguration(); $url = $conf['homepage'] ?: sprintf('https://packagist.org/packages/%s', $package->getName()); $licenses = implode(', ', (array) $conf['license']); $lines = [ "# {$this->packageName} - " . $this->description($package) . '.', "This package is a drop-in replacement for [{$package->getName()}]($url), which provides its PHAR archive as a binary.", 'It is built automatically from the official PHAR.', '## Installation', "\tcomposer require {$this->packageName}", 'or:', "\tcomposer require --dev {$this->packageName}", '', '## Usage', 'As you would use the original package, i.e. something like:', "\tvendor/bin/" . Path::getFilename($package->getBinary()) . ' [options] [arguments]', '## License', 'This distribution retains the license of the original software: ' . $licenses, ]; file_put_contents( Path::join($this->path, 'README.md'), implode("\n\n", $lines) ); } /** * @param Package $package */
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
private function generateComposerConfig(Package $package): void { $config = $package->getConfiguration(); $config['name'] = $this->packageName; $config['description'] = ucfirst($this->description($package)); $config['archive'] = ['exclude' => ['.git*']]; $config['keywords'][] = 'shim'; $config['readme'] = 'README.md'; $config['replace'][$package->getName()] = 'self.version'; $config['bin'] = [$package->getBinary()]; $config['autoload'] = ['exclude-from-classmap' => $config['bin']]; unset($config['autoload-dev'], $config['require-dev']); $config = array_filter($config); $json = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE); file_put_contents(Path::join($this->path, 'composer.json'), $json); } /** * @param Package $package * * @return string */ private function description(Package $package): string { return "shim repository for {$package->getName()}"; } }