From 822fd640d0fdd611e3b0efb93501a94098a741fa Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Oct 2022 13:57:14 +0200 Subject: [PATCH] Add warning when outdated command rejects an existing version due to platform requirements, fixes #11016 (#11113) --- src/Composer/Command/ShowCommand.php | 12 ++- .../Package/Version/VersionSelector.php | 15 ++-- .../Composer/Test/Command/ShowCommandTest.php | 82 +++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 47ef25de9..dd32e6537 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -1379,7 +1379,17 @@ EOT } } - $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter); + if ($this->getIO()->isVerbose()) { + $showWarnings = true; + } else { + $showWarnings = static function (PackageInterface $candidate) use ($package): bool { + if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) { + return false; + } + return version_compare($candidate->getVersion(), $package->getVersion(), '<='); + }; + } + $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings); while ($candidate instanceof AliasPackage) { $candidate = $candidate->getAliasOf(); } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index 9cf4816f3..34f3a1f2b 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -65,9 +65,10 @@ class VersionSelector * @param string $targetPackageVersion * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter * @param IOInterface|null $io If passed, warnings will be output there in case versions cannot be selected due to platform requirements + * @param callable(PackageInterface):bool|bool $showWarnings * @return PackageInterface|false */ - public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null) + public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true) { if (!isset(BasePackage::$stabilities[$preferredStability])) { // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version @@ -114,6 +115,8 @@ class VersionSelector if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) { /** @var array $alreadyWarnedNames */ $alreadyWarnedNames = []; + /** @var array $alreadySeenNames */ + $alreadySeenNames = []; foreach ($candidates as $pkg) { $reqs = $pkg->getRequires(); @@ -137,14 +140,16 @@ class VersionSelector $reason = 'is missing from your platform'; } - if ($io !== null) { - $isFirst = !isset($alreadyWarnedNames[$pkg->getName()]); + $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]); + $alreadySeenNames[$pkg->getName()] = true; + if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) { + $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName()]); $alreadyWarnedNames[$pkg->getName()] = true; - $latest = $isFirst ? "'s latest version" : ''; + $latest = $isLatestVersion ? "'s latest version" : ''; $io->writeError( 'Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.', true, - $isFirst ? IOInterface::NORMAL : IOInterface::VERBOSE + $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE ); } diff --git a/tests/Composer/Test/Command/ShowCommandTest.php b/tests/Composer/Test/Command/ShowCommandTest.php index 540faeaba..e34bb193b 100644 --- a/tests/Composer/Test/Command/ShowCommandTest.php +++ b/tests/Composer/Test/Command/ShowCommandTest.php @@ -167,6 +167,88 @@ outdated/patch 1.0.0 ! 1.0.1', ]; } + public function testOutdatedFiltersAccordingToPlatformReqsAndWarns(): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.0.0'], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.1.0', 'require' => ['ext-missing' => '3']], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.2.0', 'require' => ['ext-missing' => '3']], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.3.0', 'require' => ['ext-missing' => '3']], + ], + ], + ], + ]); + + $this->createInstalledJson([ + $this->getPackage('vendor/package', '1.1.0'), + ]); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'outdated']); + self::assertSame("Cannot use vendor/package 1.1.0 as it requires ext-missing 3 which is missing from your platform. +Legend: +! patch or minor release available - update recommended +~ major release available - update possible + +Direct dependencies required in composer.json: +Everything up to date + +Transitive dependencies not required in composer.json: +vendor/package 1.1.0 ~ 1.0.0", trim($appTester->getDisplay(true))); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'outdated', '--verbose' => true]); + self::assertSame("Cannot use vendor/package's latest version 1.3.0 as it requires ext-missing 3 which is missing from your platform. +Cannot use vendor/package 1.2.0 as it requires ext-missing 3 which is missing from your platform. +Cannot use vendor/package 1.1.0 as it requires ext-missing 3 which is missing from your platform. +Legend: +! patch or minor release available - update recommended +~ major release available - update possible + +Direct dependencies required in composer.json: +Everything up to date + +Transitive dependencies not required in composer.json: +vendor/package 1.1.0 ~ 1.0.0", trim($appTester->getDisplay(true))); + } + + public function testOutdatedFiltersAccordingToPlatformReqsWithoutWarningForHigherVersions(): void + { + $this->initTempComposer([ + 'repositories' => [ + 'packages' => [ + 'type' => 'package', + 'package' => [ + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.0.0'], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.1.0'], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.2.0'], + ['name' => 'vendor/package', 'description' => 'generic description', 'version' => '1.3.0', 'require' => ['php' => '^99']], + ], + ], + ], + ]); + + $this->createInstalledJson([ + $this->getPackage('vendor/package', '1.1.0'), + ]); + + $appTester = $this->getApplicationTester(); + $appTester->run(['command' => 'outdated']); + self::assertSame("Legend: +! patch or minor release available - update recommended +~ major release available - update possible + +Direct dependencies required in composer.json: +Everything up to date + +Transitive dependencies not required in composer.json: +vendor/package 1.1.0 ! 1.2.0", trim($appTester->getDisplay(true))); + } + public function testShowPlatformOnlyShowsPlatformPackages(): void { $this->initTempComposer([