From 892eaacedf34686b0f705521eb1673bc4e003ecc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 27 Sep 2023 11:28:33 +0200 Subject: [PATCH] Optimize show -a by loading only the requested package (#11659) Fixes #11648 --- .../DependencyResolver/PoolBuilder.php | 22 +++++++++++++--- src/Composer/DependencyResolver/Request.php | 26 ++++++++++++++++--- src/Composer/Installer.php | 16 +++++++----- src/Composer/Repository/RepositorySet.php | 6 +++++ .../DependencyResolver/PoolBuilderTest.php | 2 +- 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index aa6f5019b..d84d40906 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -102,11 +102,21 @@ class PoolBuilder * @var BasePackage[] */ private $unacceptableFixedOrLockedPackages = []; - /** @var string[] */ + /** @var array */ private $updateAllowList = []; /** @var array> */ private $skippedLoad = []; + /** + * If provided, only these package names are loaded + * + * This is a special-use functionality of the Request class to optimize the pool creation process + * when only a minimal subset of packages is needed and we do not need their dependencies. + * + * @var array|null + */ + private $restrictedPackagesList = null; + /** * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories * @@ -165,6 +175,8 @@ class PoolBuilder */ public function buildPool(array $repositories, Request $request): Pool { + $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null; + if ($request->getUpdateAllowList()) { $this->updateAllowList = $request->getUpdateAllowList(); $this->warnAboutNonMatchingUpdateAllowList($request); @@ -366,6 +378,10 @@ class PoolBuilder private function loadPackagesMarkedForLoading(Request $request, array $repositories): void { foreach ($this->packagesToLoad as $name => $constraint) { + if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) { + unset($this->packagesToLoad[$name]); + continue; + } $this->loadedPackages[$name] = $constraint; } @@ -559,7 +575,7 @@ class PoolBuilder */ private function isUpdateAllowed(BasePackage $package): bool { - foreach ($this->updateAllowList as $pattern => $void) { + foreach ($this->updateAllowList as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); if (Preg::isMatch($patternRegexp, $package->getName())) { return true; @@ -571,7 +587,7 @@ class PoolBuilder private function warnAboutNonMatchingUpdateAllowList(Request $request): void { - foreach ($this->updateAllowList as $pattern => $void) { + foreach ($this->updateAllowList as $pattern) { $patternRegexp = BasePackage::packageNameToRegexp($pattern); // update pattern matches a locked package? => all good foreach ($request->getLockedRepository()->getPackages() as $package) { diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index edd263086..300093f04 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -50,10 +50,12 @@ class Request protected $lockedPackages = []; /** @var array */ protected $fixedLockedPackages = []; - /** @var string[] */ + /** @var array */ protected $updateAllowList = []; /** @var false|self::UPDATE_* */ protected $updateAllowTransitiveDependencies = false; + /** @var non-empty-list|null */ + private $restrictedPackages = null; public function __construct(?LockArrayRepository $lockedRepository = null) { @@ -118,7 +120,7 @@ class Request } /** - * @param string[] $updateAllowList + * @param array $updateAllowList * @param false|self::UPDATE_* $updateAllowTransitiveDependencies */ public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void @@ -128,7 +130,7 @@ class Request } /** - * @return string[] + * @return array */ public function getUpdateAllowList(): array { @@ -233,4 +235,22 @@ class Request { return $this->lockedRepository; } + + /** + * Restricts the pool builder from loading other packages than those listed here + * + * @param non-empty-list $names + */ + public function restrictPackages(array $names): void + { + $this->restrictedPackages = $names; + } + + /** + * @return list + */ + public function getRestrictedPackages(): ?array + { + return $this->restrictedPackages; + } } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index ebf8c76bf..3e0f3a5df 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -180,7 +180,7 @@ class Installer /** * Array of package names/globs flagged for update * - * @var string[]|null + * @var non-empty-list|null */ protected $updateAllowList = null; /** @var Request::UPDATE_* */ @@ -242,7 +242,7 @@ class Installer gc_collect_cycles(); gc_disable(); - if ($this->updateAllowList && $this->updateMirrors) { + if ($this->updateAllowList !== null && $this->updateMirrors) { throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive."); } @@ -436,7 +436,7 @@ class Installer $lockedRepository = $this->locker->getLockedRepository(true); } } catch (\Seld\JsonLint\ParsingException $e) { - if ($this->updateAllowList || $this->updateMirrors) { + if ($this->updateAllowList !== null || $this->updateMirrors) { // in case we are doing a partial update or updating mirrors, the lock file is needed so we error throw $e; } @@ -444,7 +444,7 @@ class Installer // doing a full update } - if (($this->updateAllowList || $this->updateMirrors) && !$lockedRepository) { + if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) { $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', true, IOInterface::QUIET); return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE; @@ -467,7 +467,7 @@ class Installer $this->requirePackagesForUpdate($request, $lockedRepository, true); // pass the allow list into the request, so the pool builder can apply it - if ($this->updateAllowList) { + if ($this->updateAllowList !== null) { $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies); } @@ -1337,7 +1337,11 @@ class Installer */ public function setUpdateAllowList(array $packages): self { - $this->updateAllowList = array_flip(array_map('strtolower', $packages)); + if (count($packages) === 0) { + $this->updateAllowList = null; + } else { + $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages))); + } return $this; } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 81280b204..9940d0850 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -367,12 +367,18 @@ class RepositorySet { $request = new Request($lockedRepo); + $allowedPackages = []; foreach ($packageNames as $packageName) { if (PlatformRepository::isPlatformPackage($packageName)) { throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.'); } $request->requireName($packageName); + $allowedPackages[] = strtolower($packageName); + } + + if (count($allowedPackages) > 0) { + $request->restrictPackages($allowedPackages); } return $this->createPool($request, new NullIO()); diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index 433008ddc..1b66f4319 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -126,7 +126,7 @@ class PoolBuilderTest extends TestCase if (isset($requestData['allowTransitiveDeps']) && $requestData['allowTransitiveDeps']) { $transitiveDeps = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; } - $request->setUpdateAllowList(array_flip($requestData['allowList']), $transitiveDeps); + $request->setUpdateAllowList($requestData['allowList'], $transitiveDeps); } foreach ($fixed as $fixedPackage) {