* @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 $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('composer/package-versions-deprecated: Package not found (probably scheduled for removal); generation of version class skipped.'); return; } if (! is_writable($installDir)) { $io->write( sprintf( 'composer/package-versions-deprecated: %s is not writable; generation of version class skipped.', $installDir ) ); return; } $io->write('composer/package-versions-deprecated: Generating version class...'); $installPathTmp = $installPath . '_' . uniqid('tmp', true); file_put_contents($installPathTmp, $versionClassSource); chmod($installPathTmp, 0664); rename($installPathTmp, $installPath); $io->write('composer/package-versions-deprecated: ...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 */ 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(); } }