From 808aa032c2869a608d1cf9970f23203f055f410b Mon Sep 17 00:00:00 2001 From: Jason Woods Date: Wed, 29 Dec 2021 09:28:41 +0000 Subject: [PATCH] fix: Do not optimise away packages due to a requirement by a locked package that will be uninstalled Fixes #10394 --- .../DependencyResolver/PoolOptimizer.php | 10 ++++ ...ter-impossible-oackages-only-required.test | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-oackages-only-required.test diff --git a/src/Composer/DependencyResolver/PoolOptimizer.php b/src/Composer/DependencyResolver/PoolOptimizer.php index bfb10a589269..168f1fef0dab 100644 --- a/src/Composer/DependencyResolver/PoolOptimizer.php +++ b/src/Composer/DependencyResolver/PoolOptimizer.php @@ -397,6 +397,7 @@ private function optimizeImpossiblePackagesAway(Request $request, Pool $pool) } $packageIndex = array(); + $requireIndex = array(); foreach ($pool->getPackages() as $package) { $id = $package->id; @@ -415,9 +416,18 @@ private function optimizeImpossiblePackagesAway(Request $request, Pool $pool) } $packageIndex[$package->getName()][$package->id] = $package; + foreach ($package->getRequires() as $requiredPackage) { + $requireIndex[$requiredPackage->getTarget()] = true; + } } foreach ($request->getLockedPackages() as $package) { + // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply it's requirements + // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still + if (!isset($requireIndex[$package->getName()]) && !isset($request->getRequires()[$package->getName()])) { + continue; + } + foreach ($package->getRequires() as $link) { $require = $link->getTarget(); if (!isset($packageIndex[$require])) { diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-oackages-only-required.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-oackages-only-required.test new file mode 100644 index 000000000000..8b1db987630d --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-oackages-only-required.test @@ -0,0 +1,48 @@ +--TEST-- +When filtering packages from the pool that cannot meet the fixed/locked requirements, ensure that the requirements for a package that is not required anywhere is not used to filter, as it will be ultimately be removed. + +--REQUEST-- +{ + "require": { + "first/pkg": "*", + "root/req": "*" + }, + "locked": [ + {"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "*"}}, + {"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}}, + {"name": "third/pkg", "version": "1.0.0", "require": {"fourth/pkg": "1.0.0"}}, + {"name": "fourth/pkg", "version": "1.0.0"} + ], + "allowList": [ + "root/req" + ], + "allowTransitiveDeps": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "*"}}, + {"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}}, + {"name": "second/pkg", "version": "2.0.0"}, + {"name": "third/pkg", "version": "1.0.0", "require": {"fourth/pkg": "1.0.0"}}, + {"name": "third/pkg", "version": "2.0.0", "require": {"fourth/pkg": "2.0.0"}}, + {"name": "fourth/pkg", "version": "1.0.0"}, + {"name": "fourth/pkg", "version": "2.0.0"}, + {"name": "root/req", "version": "1.0.0", "require": {"second/pkg": "2.0.0"}, "require": {"fourth/pkg": "2.0.0"}} + ] +] + +--EXPECT-- +[ + "first/pkg-1.0.0.0 (locked)", + "second/pkg-1.0.0.0 (locked)", + "third/pkg-1.0.0.0 (locked)", + "root/req-1.0.0.0", + "fourth/pkg-1.0.0.0", + "fourth/pkg-2.0.0.0" +]