From 8c8d9efd87e108bd0f7907a59f12ed7d150e7095 Mon Sep 17 00:00:00 2001 From: Jason Woods Date: Wed, 8 Dec 2021 10:42:36 +0000 Subject: [PATCH] Filter impossible packages from the pool (#9620) Adds a new pass to the PoolOptimizer Co-authored-by: Jordi Boggiano --- .../DependencyResolver/PoolOptimizer.php | 67 ++++++++++++++++--- .../filter-impossible-packages.test | 54 +++++++++++++++ ...-always-but-not-their-transitive-deps.test | 3 +- .../partial-update-unfixing-locked-deps.test | 3 +- 4 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-packages.test diff --git a/src/Composer/DependencyResolver/PoolOptimizer.php b/src/Composer/DependencyResolver/PoolOptimizer.php index c05045a2bd52..bfb10a589269 100644 --- a/src/Composer/DependencyResolver/PoolOptimizer.php +++ b/src/Composer/DependencyResolver/PoolOptimizer.php @@ -75,7 +75,11 @@ public function optimize(Request $request, Pool $pool) { $this->prepare($request, $pool); - $optimizedPool = $this->optimizeByIdenticalDependencies($pool); + $this->optimizeByIdenticalDependencies($request, $pool); + + $this->optimizeImpossiblePackagesAway($request, $pool); + + $optimizedPool = $this->applyRemovalsToPool($pool); // No need to run this recursively at the moment // because the current optimizations cannot provide @@ -183,16 +187,13 @@ private function applyRemovalsToPool(Pool $pool) $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage); - // Reset package removals - $this->packagesToRemove = array(); - return $optimizedPool; } /** - * @return Pool + * @return void */ - private function optimizeByIdenticalDependencies(Pool $pool) + private function optimizeByIdenticalDependencies(Request $request, Pool $pool) { $identicalDefinitionsPerPackage = array(); $packageIdenticalDefinitionLookup = array(); @@ -273,8 +274,6 @@ private function optimizeByIdenticalDependencies(Pool $pool) } } } - - return $this->applyRemovalsToPool($pool); } /** @@ -383,4 +382,56 @@ private function keepPackage(BasePackage $package, $identicalDefinitionsPerPacka } } } + + /** + * Use the list of locked packages to constrain the loaded packages + * This will reduce packages with significant numbers of historical versions to a smaller number + * and reduce the resulting rule set that is generated + * + * @return void + */ + private function optimizeImpossiblePackagesAway(Request $request, Pool $pool) + { + if (count($request->getLockedPackages()) === 0) { + return; + } + + $packageIndex = array(); + + foreach ($pool->getPackages() as $package) { + $id = $package->id; + + // Do not remove irremovable packages + if (isset($this->irremovablePackages[$id])) { + continue; + } + // Do not remove a package aliased by another package, nor aliases + if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) { + continue; + } + // Do not remove locked packages + if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) { + continue; + } + + $packageIndex[$package->getName()][$package->id] = $package; + } + + foreach ($request->getLockedPackages() as $package) { + foreach ($package->getRequires() as $link) { + $require = $link->getTarget(); + if (!isset($packageIndex[$require])) { + continue; + } + + $linkConstraint = $link->getConstraint(); + foreach ($packageIndex[$require] as $id => $requiredPkg) { + if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) { + $this->markPackageForRemoval($id); + unset($packageIndex[$require][$id]); + } + } + } + } + } } diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-packages.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-packages.test new file mode 100644 index 000000000000..3dfea1ec44e2 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/filter-impossible-packages.test @@ -0,0 +1,54 @@ +--TEST-- +Do not load packages into the pool that cannot meet the fixed/locked requirements, when a loose requirement is encountered during update + +--REQUEST-- +{ + "require": { + "some/pkg": "*", + "root/req": "*" + }, + "locked": [ + {"name": "some/pkg", "version": "1.0.3", "require": {"dep/dep": "*"}, "id": 1}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}, "id": 2}, + {"name": "dep/dep", "version": "2.0.0", "id": 3} + ], + "allowList": [ + "some/pkg" + ], + "allowTransitiveDeps": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "some/pkg", "version": "1.0.4", "require": {"dep/dep": "*"}}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "dep/dep", "version": "1.0.0", "provide": {"other/pkg": "*"}}, + {"name": "dep/dep", "version": "1.0.1", "provide": {"other/pkg": "*"}}, + {"name": "dep/dep", "version": "1.0.2", "provide": {"other/pkg": "*"}}, + {"name": "dep/dep", "version": "2.0.0"}, + {"name": "dep/dep", "version": "2.0.1"} + ] +] + +--EXPECT-- +[ + 2, + "some/pkg-1.0.4.0", + "dep/dep-1.0.0.0", + "dep/dep-1.0.1.0", + "dep/dep-1.0.2.0", + "dep/dep-2.0.0.0", + "dep/dep-2.0.1.0" +] + +--EXPECT-OPTIMIZED-- +[ + 2, + "some/pkg-1.0.4.0", + "dep/dep-2.0.1.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixes-path-repos-always-but-not-their-transitive-deps.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixes-path-repos-always-but-not-their-transitive-deps.test index 10f2d7fd5df6..f1c5487ca7ad 100644 --- a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixes-path-repos-always-but-not-their-transitive-deps.test +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixes-path-repos-always-but-not-their-transitive-deps.test @@ -86,6 +86,5 @@ Partially updating one root requirement with transitive deps fully updates trans "symlinked/path-pkg-2.0.0.0", "root/update-1.0.4.0", "symlinked/transitive2-2.0.4.0", - "mirrored/transitive2-1.0.7.0", - "mirrored/transitive2-2.0.8.0" + "mirrored/transitive2-1.0.7.0" ] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-locked-deps.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-locked-deps.test index 984590843771..ed4372ca6fc0 100644 --- a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-locked-deps.test +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-locked-deps.test @@ -55,6 +55,5 @@ locked packages still need to be taking into account for loading all necessary v "root/req2-1.0.0.0 (locked)", "dep/pkg2-1.0.0.0", "dep/pkg2-1.2.0.0", - "dep/pkg1-1.0.1.0", - "dep/pkg1-2.0.0.0" + "dep/pkg1-1.0.1.0" ]