291 lines
8.9 KiB
PHP
291 lines
8.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PackageVersions;
|
|
|
|
use Composer\Composer;
|
|
use Composer\Config;
|
|
use Composer\EventDispatcher\EventSubscriberInterface;
|
|
use Composer\IO\IOInterface;
|
|
use Composer\Package\AliasPackage;
|
|
use Composer\Package\Locker;
|
|
use Composer\Package\PackageInterface;
|
|
use Composer\Package\RootPackageInterface;
|
|
use Composer\Plugin\PluginInterface;
|
|
use Composer\Script\Event;
|
|
use Composer\Script\ScriptEvents;
|
|
use Generator;
|
|
use RuntimeException;
|
|
|
|
use function array_key_exists;
|
|
use function array_merge;
|
|
use function chmod;
|
|
use function dirname;
|
|
use function file_exists;
|
|
use function file_put_contents;
|
|
use function is_writable;
|
|
use function iterator_to_array;
|
|
use function rename;
|
|
use function sprintf;
|
|
use function uniqid;
|
|
use function var_export;
|
|
|
|
final class Installer implements PluginInterface, EventSubscriberInterface
|
|
{
|
|
private static $generatedClassTemplate = <<<'PHP'
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PackageVersions;
|
|
|
|
use Composer\InstalledVersions;
|
|
use OutOfBoundsException;
|
|
|
|
class_exists(InstalledVersions::class);
|
|
|
|
/**
|
|
* This class is generated by composer/package-versions-deprecated, specifically by
|
|
* @see \PackageVersions\Installer
|
|
*
|
|
* This file is overwritten at every run of `composer install` or `composer update`.
|
|
*
|
|
* @deprecated in favor of the Composer\InstalledVersions class provided by Composer 2. Require composer-runtime-api:^2 to ensure it is present.
|
|
*/
|
|
%s
|
|
{
|
|
/**
|
|
* @deprecated please use {@see self::rootPackageName()} instead.
|
|
* This constant will be removed in version 2.0.0.
|
|
*/
|
|
const ROOT_PACKAGE_NAME = '%s';
|
|
|
|
/**
|
|
* Array of all available composer packages.
|
|
* Dont read this array from your calling code, but use the \PackageVersions\Versions::getVersion() method instead.
|
|
*
|
|
* @var array<string, string>
|
|
* @internal
|
|
*/
|
|
const VERSIONS = %s;
|
|
|
|
private function __construct()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @psalm-pure
|
|
*
|
|
* @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not
|
|
* cause any side effects here.
|
|
*/
|
|
public static function rootPackageName() : string
|
|
{
|
|
if (!self::composer2ApiUsable()) {
|
|
return self::ROOT_PACKAGE_NAME;
|
|
}
|
|
|
|
return InstalledVersions::getRootPackage()['name'];
|
|
}
|
|
|
|
/**
|
|
* @throws OutOfBoundsException If a version cannot be located.
|
|
*
|
|
* @psalm-param key-of<self::VERSIONS> $packageName
|
|
* @psalm-pure
|
|
*
|
|
* @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not
|
|
* cause any side effects here.
|
|
*/
|
|
public static function getVersion(string $packageName): string
|
|
{
|
|
if (self::composer2ApiUsable()) {
|
|
return InstalledVersions::getPrettyVersion($packageName)
|
|
. '@' . InstalledVersions::getReference($packageName);
|
|
}
|
|
|
|
if (isset(self::VERSIONS[$packageName])) {
|
|
return self::VERSIONS[$packageName];
|
|
}
|
|
|
|
throw new OutOfBoundsException(
|
|
'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files'
|
|
);
|
|
}
|
|
|
|
private static function composer2ApiUsable(): bool
|
|
{
|
|
if (!class_exists(InstalledVersions::class, false)) {
|
|
return false;
|
|
}
|
|
|
|
if (method_exists(InstalledVersions::class, 'getAllRawData')) {
|
|
$rawData = InstalledVersions::getAllRawData();
|
|
if (count($rawData) === 1 && count($rawData[0]) === 0) {
|
|
return false;
|
|
}
|
|
} else {
|
|
$rawData = InstalledVersions::getRawData();
|
|
if ($rawData === null || $rawData === []) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
PHP;
|
|
|
|
public function activate(Composer $composer, IOInterface $io)
|
|
{
|
|
// Nothing to do here, as all features are provided through event listeners
|
|
}
|
|
|
|
public function deactivate(Composer $composer, IOInterface $io)
|
|
{
|
|
// Nothing to do here, as all features are provided through event listeners
|
|
}
|
|
|
|
public function uninstall(Composer $composer, IOInterface $io)
|
|
{
|
|
// Nothing to do here, as all features are provided through event listeners
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public static function getSubscribedEvents(): array
|
|
{
|
|
return [ScriptEvents::POST_AUTOLOAD_DUMP => 'dumpVersionsClass'];
|
|
}
|
|
|
|
/**
|
|
* @throws RuntimeException
|
|
*/
|
|
public static function dumpVersionsClass(Event $composerEvent)
|
|
{
|
|
$composer = $composerEvent->getComposer();
|
|
$rootPackage = $composer->getPackage();
|
|
$versions = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage));
|
|
|
|
if (! array_key_exists('composer/package-versions-deprecated', $versions)) {
|
|
//plugin must be globally installed - we only want to generate versions for projects which specifically
|
|
//require composer/package-versions-deprecated
|
|
return;
|
|
}
|
|
|
|
$versionClass = self::generateVersionsClass($rootPackage->getName(), $versions);
|
|
|
|
self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO());
|
|
}
|
|
|
|
/**
|
|
* @param string[] $versions
|
|
*/
|
|
private static function generateVersionsClass(string $rootPackageName, array $versions): string
|
|
{
|
|
return sprintf(
|
|
self::$generatedClassTemplate,
|
|
'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-(
|
|
$rootPackageName,
|
|
var_export($versions, true)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @throws RuntimeException
|
|
*/
|
|
private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io)
|
|
{
|
|
$installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage())
|
|
. '/src/PackageVersions/Versions.php';
|
|
|
|
$installDir = dirname($installPath);
|
|
if (! file_exists($installDir)) {
|
|
$io->write('<info>composer/package-versions-deprecated:</info> Package not found (probably scheduled for removal); generation of version class skipped.');
|
|
|
|
return;
|
|
}
|
|
|
|
if (! is_writable($installDir)) {
|
|
$io->write(
|
|
sprintf(
|
|
'<info>composer/package-versions-deprecated:</info> %s is not writable; generation of version class skipped.',
|
|
$installDir
|
|
)
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$io->write('<info>composer/package-versions-deprecated:</info> Generating version class...');
|
|
|
|
$installPathTmp = $installPath . '_' . uniqid('tmp', true);
|
|
file_put_contents($installPathTmp, $versionClassSource);
|
|
chmod($installPathTmp, 0664);
|
|
rename($installPathTmp, $installPath);
|
|
|
|
$io->write('<info>composer/package-versions-deprecated:</info> ...done generating version class');
|
|
}
|
|
|
|
/**
|
|
* @throws RuntimeException
|
|
*/
|
|
private static function locateRootPackageInstallPath(
|
|
Config $composerConfig,
|
|
RootPackageInterface $rootPackage
|
|
): string {
|
|
if (self::getRootPackageAlias($rootPackage)->getName() === 'composer/package-versions-deprecated') {
|
|
return dirname($composerConfig->get('vendor-dir'));
|
|
}
|
|
|
|
return $composerConfig->get('vendor-dir') . '/composer/package-versions-deprecated';
|
|
}
|
|
|
|
private static function getRootPackageAlias(RootPackageInterface $rootPackage): PackageInterface
|
|
{
|
|
$package = $rootPackage;
|
|
|
|
while ($package instanceof AliasPackage) {
|
|
$package = $package->getAliasOf();
|
|
}
|
|
|
|
return $package;
|
|
}
|
|
|
|
/**
|
|
* @return Generator&string[]
|
|
*
|
|
* @psalm-return Generator<string, string>
|
|
*/
|
|
private static function getVersions(Locker $locker, RootPackageInterface $rootPackage): Generator
|
|
{
|
|
$lockData = $locker->getLockData();
|
|
|
|
$lockData['packages-dev'] = $lockData['packages-dev'] ?? [];
|
|
|
|
$packages = $lockData['packages'];
|
|
if (getenv('COMPOSER_DEV_MODE') !== '0') {
|
|
$packages = array_merge($packages, $lockData['packages-dev']);
|
|
}
|
|
foreach ($packages as $package) {
|
|
yield $package['name'] => $package['version'] . '@' . (
|
|
$package['source']['reference'] ?? $package['dist']['reference'] ?? ''
|
|
);
|
|
}
|
|
|
|
foreach ($rootPackage->getReplaces() as $replace) {
|
|
$version = $replace->getPrettyConstraint();
|
|
if ($version === 'self.version') {
|
|
$version = $rootPackage->getPrettyVersion();
|
|
}
|
|
|
|
yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference();
|
|
}
|
|
|
|
yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference();
|
|
}
|
|
}
|