diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 8f5f5cd15..01115fd65 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -94,6 +94,11 @@ class PoolBuilder * @phpstan-var array>> */ private $loadedPerRepo = []; + /** + * @var array[] + * @phpstan-var array + */ + private $optionalPackages = []; /** * @var BasePackage[] */ @@ -235,8 +240,9 @@ class PoolBuilder } } - while (!empty($this->packagesToLoad)) { + while ([] !== $this->packagesToLoad || [] !== $this->optionalPackages) { $this->loadPackagesMarkedForLoading($request, $repositories); + $this->loadOptionalPackages($request); } if (\count($this->temporaryConstraints) > 0) { @@ -483,7 +489,8 @@ class PoolBuilder if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) { $this->unlockPackage($request, $repositories, $replace); - $this->markPackageNameForLoading($request, $replace, $link->getConstraint()); + // Mark as optional - if no other package requires it, we don't need to load it + $this->markPackageNameForOptionalLoading($replace); } else { foreach ($skippedRootRequires as $rootRequire) { if (!isset($this->updateAllowWarned[$rootRequire])) { @@ -651,10 +658,12 @@ class PoolBuilder if (isset($requires[$lockedPackage->getName()])) { $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint()); } + foreach ($lockedPackage->getReplaces() as $replace) { if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) { $this->unlockPackage($request, $repositories, $replace->getTarget()); - $this->markPackageNameForLoading($request, $replace->getTarget(), $requires[$replace->getTarget()]->getConstraint()); + // Mark as optional - if no other package requires it, we don't need to load it + $this->markPackageNameForOptionalLoading($replace->getTarget()); } } } @@ -664,6 +673,28 @@ class PoolBuilder } } + private function markPackageNameForOptionalLoading(string $name): void + { + $this->optionalPackages[$name] = true; + } + + private function loadOptionalPackages(Request $request): void + { + if ([] === $this->optionalPackages) { + return; + } + + foreach ($this->packages as $package) { + foreach ($package->getRequires() as $link) { + if (isset($this->optionalPackages[$link->getTarget()])) { + $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint()); + } + } + } + + $this->optionalPackages = []; + } + /** * @param RepositoryInterface[] $repositories */ diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update-all.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update-all.test index 72e88141a..5ffd3ce14 100644 --- a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update-all.test +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update-all.test @@ -101,7 +101,6 @@ Check that replacers from additional repositories are loaded when doing a partia "indirect/replacer-1.0.0.0", "replacer/package-1.2.0.0", "replacer/package-1.0.0.0", - "base/package-1.0.0.0", "shared/dep-1.0.0.0", "shared/dep-1.2.0.0" ] @@ -112,6 +111,5 @@ Check that replacers from additional repositories are loaded when doing a partia "indirect/replacer-1.0.0.0", "replacer/package-1.2.0.0", "replacer/package-1.0.0.0", - "base/package-1.0.0.0", "shared/dep-1.2.0.0" ]