View file vendor/composer/package-versions-deprecated/src/PackageVersions/Installer.php

File size: 8.45Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace PackageVersions;
  6.  
  7. use Composer\Composer;
  8. use Composer\Config;
  9. use Composer\EventDispatcher\EventSubscriberInterface;
  10. use Composer\IO\IOInterface;
  11. use Composer\Package\AliasPackage;
  12. use Composer\Package\Locker;
  13. use Composer\Package\PackageInterface;
  14. use Composer\Package\RootPackageInterface;
  15. use Composer\Plugin\PluginInterface;
  16. use Composer\Script\Event;
  17. use Composer\Script\ScriptEvents;
  18. use Generator;
  19. use RuntimeException;
  20.  
  21. use function array_key_exists;
  22. use function array_merge;
  23. use function chmod;
  24. use function dirname;
  25. use function file_exists;
  26. use function file_put_contents;
  27. use function is_writable;
  28. use function iterator_to_array;
  29. use function rename;
  30. use function sprintf;
  31. use function uniqid;
  32. use function var_export;
  33.  
  34. final class Installer implements PluginInterface, EventSubscriberInterface
  35. {
  36. private static $generatedClassTemplate = <<<'PHP'
  37. <?php
  38.  
  39. declare(strict_types=1);
  40.  
  41. namespace PackageVersions;
  42.  
  43. use Composer\InstalledVersions;
  44. use OutOfBoundsException;
  45.  
  46. class_exists(InstalledVersions::class);
  47.  
  48. /**
  49. * This class is generated by composer/package-versions-deprecated, specifically by
  50. * @see \PackageVersions\Installer
  51. *
  52. * This file is overwritten at every run of `composer install` or `composer update`.
  53. *
  54. * @deprecated in favor of the Composer\InstalledVersions class provided by Composer 2. Require composer-runtime-api:^2 to ensure it is present.
  55. */
  56. %s
  57. {
  58. /**
  59. * @deprecated please use {@see self::rootPackageName()} instead.
  60. * This constant will be removed in version 2.0.0.
  61. */
  62. const ROOT_PACKAGE_NAME = '%s';
  63.  
  64. /**
  65. * Array of all available composer packages.
  66. * Dont read this array from your calling code, but use the \PackageVersions\Versions::getVersion() method instead.
  67. *
  68. * @var array<string, string>
  69. * @internal
  70. */
  71. const VERSIONS = %s;
  72.  
  73. private function __construct()
  74. {
  75. }
  76.  
  77. /**
  78. * @psalm-pure
  79. *
  80. * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not
  81. * cause any side effects here.
  82. */
  83. public static function rootPackageName() : string
  84. {
  85. if (!class_exists(InstalledVersions::class, false) || !InstalledVersions::getRawData()) {
  86. return self::ROOT_PACKAGE_NAME;
  87. }
  88.  
  89. return InstalledVersions::getRootPackage()['name'];
  90. }
  91.  
  92. /**
  93. * @throws OutOfBoundsException If a version cannot be located.
  94. *
  95. * @psalm-param key-of<self::VERSIONS> $packageName
  96. * @psalm-pure
  97. *
  98. * @psalm-suppress ImpureMethodCall we know that {@see InstalledVersions} interaction does not
  99. * cause any side effects here.
  100. */
  101. public static function getVersion(string $packageName): string
  102. {
  103. if (class_exists(InstalledVersions::class, false) && InstalledVersions::getRawData()) {
  104. return InstalledVersions::getPrettyVersion($packageName)
  105. . '@' . InstalledVersions::getReference($packageName);
  106. }
  107.  
  108. if (isset(self::VERSIONS[$packageName])) {
  109. return self::VERSIONS[$packageName];
  110. }
  111.  
  112. throw new OutOfBoundsException(
  113. 'Required package "' . $packageName . '" is not installed: check your ./vendor/composer/installed.json and/or ./composer.lock files'
  114. );
  115. }
  116. }
  117.  
  118. PHP;
  119.  
  120. public function activate(Composer $composer, IOInterface $io)
  121. {
  122. // Nothing to do here, as all features are provided through event listeners
  123. }
  124.  
  125. public function deactivate(Composer $composer, IOInterface $io)
  126. {
  127. // Nothing to do here, as all features are provided through event listeners
  128. }
  129.  
  130. public function uninstall(Composer $composer, IOInterface $io)
  131. {
  132. // Nothing to do here, as all features are provided through event listeners
  133. }
  134.  
  135. /**
  136. * {@inheritDoc}
  137. */
  138. public static function getSubscribedEvents(): array
  139. {
  140. return [ScriptEvents::POST_AUTOLOAD_DUMP => 'dumpVersionsClass'];
  141. }
  142.  
  143. /**
  144. * @throws RuntimeException
  145. */
  146. public static function dumpVersionsClass(Event $composerEvent)
  147. {
  148. $composer = $composerEvent->getComposer();
  149. $rootPackage = $composer->getPackage();
  150. $versions = iterator_to_array(self::getVersions($composer->getLocker(), $rootPackage));
  151.  
  152. if (! array_key_exists('composer/package-versions-deprecated', $versions)) {
  153. //plugin must be globally installed - we only want to generate versions for projects which specifically
  154. //require composer/package-versions-deprecated
  155. return;
  156. }
  157.  
  158. $versionClass = self::generateVersionsClass($rootPackage->getName(), $versions);
  159.  
  160. self::writeVersionClassToFile($versionClass, $composer, $composerEvent->getIO());
  161. }
  162.  
  163. /**
  164. * @param string[] $versions
  165. */
  166. private static function generateVersionsClass(string $rootPackageName, array $versions): string
  167. {
  168. return sprintf(
  169. self::$generatedClassTemplate,
  170. 'fin' . 'al ' . 'cla' . 'ss ' . 'Versions', // note: workaround for regex-based code parsers :-(
  171. $rootPackageName,
  172. var_export($versions, true)
  173. );
  174. }
  175.  
  176. /**
  177. * @throws RuntimeException
  178. */
  179. private static function writeVersionClassToFile(string $versionClassSource, Composer $composer, IOInterface $io)
  180. {
  181. $installPath = self::locateRootPackageInstallPath($composer->getConfig(), $composer->getPackage())
  182. . '/src/PackageVersions/Versions.php';
  183.  
  184. $installDir = dirname($installPath);
  185. if (! file_exists($installDir)) {
  186. $io->write('<info>composer/package-versions-deprecated:</info> Package not found (probably scheduled for removal); generation of version class skipped.');
  187.  
  188. return;
  189. }
  190.  
  191. if (! is_writable($installDir)) {
  192. $io->write(
  193. sprintf(
  194. '<info>composer/package-versions-deprecated:</info> %s is not writable; generation of version class skipped.',
  195. $installDir
  196. )
  197. );
  198.  
  199. return;
  200. }
  201.  
  202. $io->write('<info>composer/package-versions-deprecated:</info> Generating version class...');
  203.  
  204. $installPathTmp = $installPath . '_' . uniqid('tmp', true);
  205. file_put_contents($installPathTmp, $versionClassSource);
  206. chmod($installPathTmp, 0664);
  207. rename($installPathTmp, $installPath);
  208.  
  209. $io->write('<info>composer/package-versions-deprecated:</info> ...done generating version class');
  210. }
  211.  
  212. /**
  213. * @throws RuntimeException
  214. */
  215. private static function locateRootPackageInstallPath(
  216. Config $composerConfig,
  217. RootPackageInterface $rootPackage
  218. ): string {
  219. if (self::getRootPackageAlias($rootPackage)->getName() === 'composer/package-versions-deprecated') {
  220. return dirname($composerConfig->get('vendor-dir'));
  221. }
  222.  
  223. return $composerConfig->get('vendor-dir') . '/composer/package-versions-deprecated';
  224. }
  225.  
  226. private static function getRootPackageAlias(RootPackageInterface $rootPackage): PackageInterface
  227. {
  228. $package = $rootPackage;
  229.  
  230. while ($package instanceof AliasPackage) {
  231. $package = $package->getAliasOf();
  232. }
  233.  
  234. return $package;
  235. }
  236.  
  237. /**
  238. * @return Generator&string[]
  239. *
  240. * @psalm-return Generator<string, string>
  241. */
  242. private static function getVersions(Locker $locker, RootPackageInterface $rootPackage): Generator
  243. {
  244. $lockData = $locker->getLockData();
  245.  
  246. $lockData['packages-dev'] = $lockData['packages-dev'] ?? [];
  247.  
  248. $packages = $lockData['packages'];
  249. if (getenv('COMPOSER_DEV_MODE') !== '0') {
  250. $packages = array_merge($packages, $lockData['packages-dev']);
  251. }
  252. foreach ($packages as $package) {
  253. yield $package['name'] => $package['version'] . '@' . (
  254. $package['source']['reference'] ?? $package['dist']['reference'] ?? ''
  255. );
  256. }
  257.  
  258. foreach ($rootPackage->getReplaces() as $replace) {
  259. $version = $replace->getPrettyConstraint();
  260. if ($version === 'self.version') {
  261. $version = $rootPackage->getPrettyVersion();
  262. }
  263.  
  264. yield $replace->getTarget() => $version . '@' . $rootPackage->getSourceReference();
  265. }
  266.  
  267. yield $rootPackage->getName() => $rootPackage->getPrettyVersion() . '@' . $rootPackage->getSourceReference();
  268. }
  269. }