From f17df6d5a15d9f90fe4a21c213fa0691e28f57e4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 17 Sep 2024 13:31:33 +0200 Subject: [PATCH] Fix handling of platform packages in why-not command and partial updates, fixes #12104 (#12110) --- .../Command/BaseDependencyCommand.php | 21 +++++++++- .../DependencyResolver/PoolBuilder.php | 10 ++++- .../Command/BaseDependencyCommandTest.php | 42 +++++++++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index 2fb363979..bb2a64233 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -13,6 +13,7 @@ namespace Composer\Command; use Composer\Package\Link; +use Composer\Package\Package; use Composer\Package\PackageInterface; use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackage; @@ -24,6 +25,8 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Composer\Semver\Constraint\Bound; +use Composer\Util\Platform; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Composer\Package\Version\VersionParser; @@ -102,13 +105,27 @@ abstract class BaseDependencyCommand extends BaseCommand // If the version we ask for is not installed then we need to locate it in remote repos and add it. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. - if (!$installedRepo->findPackage($needle, $textConstraint)) { + $matchedPackage = $installedRepo->findPackage($needle, $textConstraint); + if (!$matchedPackage) { $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $installedRepo->addRepository(new InstalledArrayRepository([clone $match])); + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($textConstraint); + if ($constraint->getLowerBound() !== Bound::zero()) { + $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion()); + $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg])); + } } else { $this->getIO()->writeError('Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.'); } + } elseif (PlatformRepository::isPlatformPackage($needle)) { + $extraNotice = ''; + if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) { + $extraNotice = ' (version provided by config.platform)'; + } + $this->getIO()->writeError('Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.'); } // Include replaced packages for inverted lookups as they are then the actual starting point to consider @@ -154,7 +171,7 @@ abstract class BaseDependencyCommand extends BaseCommand $this->printTable($output, $results); } - if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT)) { + if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) { $composerCommand = 'update'; foreach ($composer->getPackage()->getRequires() as $rootRequirement) { diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 15bc35885..d3fbc521f 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -616,6 +616,8 @@ class PoolBuilder private function warnAboutNonMatchingUpdateAllowList(Request $request): void { foreach ($this->updateAllowList as $pattern) { + $matchedPlatformPackage = false; + $patternRegexp = BasePackage::packageNameToRegexp($pattern); // update pattern matches a locked package? => all good foreach ($request->getLockedRepository()->getPackages() as $package) { @@ -626,10 +628,16 @@ class PoolBuilder // update pattern matches a root require? => all good, probably a new package foreach ($request->getRequires() as $packageName => $constraint) { if (Preg::isMatch($patternRegexp, $packageName)) { + if (PlatformRepository::isPlatformPackage($packageName)) { + $matchedPlatformPackage = true; + continue; + } continue 2; } } - if (strpos($pattern, '*') !== false) { + if ($matchedPlatformPackage) { + $this->io->writeError('Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.'); + } elseif (strpos($pattern, '*') !== false) { $this->io->writeError('Pattern "' . $pattern . '" listed for update does not match any locked packages.'); } else { $this->io->writeError('Package "' . $pattern . '" listed for update is not locked.'); diff --git a/tests/Composer/Test/Command/BaseDependencyCommandTest.php b/tests/Composer/Test/Command/BaseDependencyCommandTest.php index c663b70f7..0fad665ae 100644 --- a/tests/Composer/Test/Command/BaseDependencyCommandTest.php +++ b/tests/Composer/Test/Command/BaseDependencyCommandTest.php @@ -15,6 +15,7 @@ namespace Composer\Test\Command; use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\MatchAllConstraint; +use Composer\Semver\Constraint\MultiConstraint; use Symfony\Component\Console\Command\Command; use UnexpectedValueException; use InvalidArgumentException; @@ -374,19 +375,25 @@ OUTPUT 'package' => [ ['name' => 'vendor1/package1', 'version' => '1.3.0'], ['name' => 'vendor2/package1', 'version' => '2.0.0'], - ['name' => 'vendor2/package2', 'version' => '1.0.0', 'require' => ['vendor2/package3' => '1.4.*']], + ['name' => 'vendor2/package2', 'version' => '1.0.0', 'require' => ['vendor2/package3' => '1.4.*', 'php' => '^8.2']], ['name' => 'vendor2/package3', 'version' => '1.4.0'], ['name' => 'vendor2/package3', 'version' => '1.5.0'] ], ], ], 'require' => [ - 'vendor1/package1' => '1.*' + 'vendor1/package1' => '1.*', + 'php' => '^8', ], 'require-dev' => [ 'vendor2/package1' => '2.*', 'vendor2/package2' => '^1' - ] + ], + 'config' => [ + 'platform' => [ + 'php' => '8.3.2', + ], + ], ]); $someRequiredPackage = self::getPackage('vendor1/package1', '1.3.0'); @@ -399,7 +406,14 @@ OUTPUT new MatchAllConstraint(), Link::TYPE_REQUIRE, '1.4.*' - ) + ), + 'php' => new Link( + 'vendor2/package2', + 'php', + new MultiConstraint([self::getVersionConstraint('>=', '8.2.0.0'), self::getVersionConstraint('<', '9.0.0.0-dev')]), + Link::TYPE_REQUIRE, + '^8.2' + ), ]); $secondDevNestedRequiredPackage = self::getPackage('vendor2/package3', '1.4.0'); @@ -466,6 +480,26 @@ OUTPUT vendor2/package2 1.0.0 requires vendor2/package3 (1.4.*) Not finding what you were looking for? Try calling `composer update "vendor2/package3:1.5.0" --dry-run` to get another view on the problem. OUTPUT +, + 1 + ]; + + yield 'all compatible with the inspected platform package (range matching installed)' => [ + ['package' => 'php', 'version' => '^8'], + << [ + ['package' => 'php', 'version' => '9.1.0'], + <<