| @@ -19,14 +19,34 @@ | ||
| 19 | 19 | * This class is copied in every Composer installed project and available to all |
| 20 | 20 | * |
| 21 | 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions |
| 22 | 22 | * |
| 23 | - * To require it's presence, you can require `composer-runtime-api ^2.0` | |
| 23 | + * To require its presence, you can require `composer-runtime-api ^2.0` | |
| 24 | + * | |
| 25 | + * @final | |
| 24 | 26 | */ |
| 25 | 27 | class InstalledVersions |
| 26 | 28 | { |
| 29 | + /** | |
| 30 | + * @var mixed[]|null | |
| 31 | + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null | |
| 32 | + */ | |
| 27 | 33 | private static $installed; |
| 34 | + | |
| 35 | + /** | |
| 36 | + * @var bool | |
| 37 | + */ | |
| 38 | + private static $installedIsLocalDir; | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * @var bool|null | |
| 42 | + */ | |
| 28 | 43 | private static $canGetVendors; |
| 44 | + | |
| 45 | + /** | |
| 46 | + * @var array[] | |
| 47 | + * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |
| 48 | + */ | |
| 29 | 49 | private static $installedByVendor = array(); |
| 30 | 50 | |
| 31 | 51 | /** |
| 32 | 52 | * Returns a list of all package names which are present, either by being installed, replaced or provided |
| @@ -82,9 +102,9 @@ | ||
| 82 | 102 | public static function isInstalled($packageName, $includeDevRequirements = true) |
| 83 | 103 | { |
| 84 | 104 | foreach (self::getInstalled() as $installed) { |
| 85 | 105 | if (isset($installed['versions'][$packageName])) { |
| 86 | - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); | |
| 106 | + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; | |
| 87 | 107 | } |
| 88 | 108 | } |
| 89 | 109 | |
| 90 | 110 | return false; |
| @@ -103,9 +123,9 @@ | ||
| 103 | 123 | * @return bool |
| 104 | 124 | */ |
| 105 | 125 | public static function satisfies(VersionParser $parser, $packageName, $constraint) |
| 106 | 126 | { |
| 107 | - $constraint = $parser->parseConstraints($constraint); | |
| 127 | + $constraint = $parser->parseConstraints((string) $constraint); | |
| 108 | 128 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); |
| 109 | 129 | |
| 110 | 130 | return $provided->matches($constraint); |
| 111 | 131 | } |
| @@ -227,9 +247,9 @@ | ||
| 227 | 247 | } |
| 228 | 248 | |
| 229 | 249 | /** |
| 230 | 250 | * @return array |
| 231 | - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string} | |
| 251 | + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} | |
| 232 | 252 | */ |
| 233 | 253 | public static function getRootPackage() |
| 234 | 254 | { |
| 235 | 255 | $installed = self::getInstalled(); |
| @@ -241,9 +261,9 @@ | ||
| 241 | 261 | * Returns the raw installed.php data for custom implementations |
| 242 | 262 | * |
| 243 | 263 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. |
| 244 | 264 | * @return array[] |
| 245 | - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} | |
| 265 | + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} | |
| 246 | 266 | */ |
| 247 | 267 | public static function getRawData() |
| 248 | 268 | { |
| 249 | 269 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); |
| @@ -264,9 +284,9 @@ | ||
| 264 | 284 | /** |
| 265 | 285 | * Returns the raw data of all installed.php which are currently loaded for custom implementations |
| 266 | 286 | * |
| 267 | 287 | * @return array[] |
| 268 | - * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> | |
| 288 | + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |
| 269 | 289 | */ |
| 270 | 290 | public static function getAllRawData() |
| 271 | 291 | { |
| 272 | 292 | return self::getInstalled(); |
| @@ -287,19 +307,25 @@ | ||
| 287 | 307 | * |
| 288 | 308 | * @param array[] $data A vendor/composer/installed.php data set |
| 289 | 309 | * @return void |
| 290 | 310 | * |
| 291 | - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data | |
| 311 | + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data | |
| 292 | 312 | */ |
| 293 | 313 | public static function reload($data) |
| 294 | 314 | { |
| 295 | 315 | self::$installed = $data; |
| 296 | 316 | self::$installedByVendor = array(); |
| 317 | + | |
| 318 | + // when using reload, we disable the duplicate protection to ensure that self::$installed data is | |
| 319 | + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, | |
| 320 | + // so we have to assume it does not, and that may result in duplicate data being returned when listing | |
| 321 | + // all installed packages for example | |
| 322 | + self::$installedIsLocalDir = false; | |
| 297 | 323 | } |
| 298 | 324 | |
| 299 | 325 | /** |
| 300 | 326 | * @return array[] |
| 301 | - * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> | |
| 327 | + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |
| 302 | 328 | */ |
| 303 | 329 | private static function getInstalled() |
| 304 | 330 | { |
| 305 | 331 | if (null === self::$canGetVendors) { |
| @@ -306,19 +332,29 @@ | ||
| 306 | 332 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); |
| 307 | 333 | } |
| 308 | 334 | |
| 309 | 335 | $installed = array(); |
| 336 | + $copiedLocalDir = false; | |
| 310 | 337 | |
| 311 | 338 | if (self::$canGetVendors) { |
| 339 | + $selfDir = strtr(__DIR__, '\\', '/'); | |
| 312 | 340 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { |
| 341 | + $vendorDir = strtr($vendorDir, '\\', '/'); | |
| 313 | 342 | if (isset(self::$installedByVendor[$vendorDir])) { |
| 314 | 343 | $installed[] = self::$installedByVendor[$vendorDir]; |
| 315 | 344 | } elseif (is_file($vendorDir.'/composer/installed.php')) { |
| 316 | - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; | |
| 317 | - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { | |
| 318 | - self::$installed = $installed[count($installed) - 1]; | |
| 345 | + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |
| 346 | + $required = require $vendorDir.'/composer/installed.php'; | |
| 347 | + self::$installedByVendor[$vendorDir] = $required; | |
| 348 | + $installed[] = $required; | |
| 349 | + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { | |
| 350 | + self::$installed = $required; | |
| 351 | + self::$installedIsLocalDir = true; | |
| 319 | 352 | } |
| 320 | 353 | } |
| 354 | + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { | |
| 355 | + $copiedLocalDir = true; | |
| 356 | + } | |
| 321 | 357 | } |
| 322 | 358 | } |
| 323 | 359 | |
| 324 | 360 | if (null === self::$installed) { |
| @@ -324,14 +360,19 @@ | ||
| 324 | 360 | if (null === self::$installed) { |
| 325 | 361 | // only require the installed.php file if this file is loaded from its dumped location, |
| 326 | 362 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 |
| 327 | 363 | if (substr(__DIR__, -8, 1) !== 'C') { |
| 328 | - self::$installed = require __DIR__ . '/installed.php'; | |
| 364 | + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |
| 365 | + $required = require __DIR__ . '/installed.php'; | |
| 366 | + self::$installed = $required; | |
| 329 | 367 | } else { |
| 330 | 368 | self::$installed = array(); |
| 331 | 369 | } |
| 332 | 370 | } |
| 333 | - $installed[] = self::$installed; | |
| 371 | + | |
| 372 | + if (self::$installed !== array() && !$copiedLocalDir) { | |
| 373 | + $installed[] = self::$installed; | |
| 374 | + } | |
| 334 | 375 | |
| 335 | 376 | return $installed; |
| 336 | 377 | } |
| 337 | 378 | } |