diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 718f340e7..7c17ba7ee 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -43,6 +43,8 @@ class PoolBuilder private $packages = array(); private $unacceptableFixedPackages = array(); + private $unfixList = array(); + public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, EventDispatcher $eventDispatcher = null) { $this->acceptableStabilities = $acceptableStabilities; @@ -54,6 +56,16 @@ class PoolBuilder public function buildPool(array $repositories, Request $request) { + if ($request->getUpdateAllowList()) { + $this->unfixList = $request->getUpdateAllowList(); + + foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) { + if (!$this->isUpdateable($lockedPackage)) { + $request->fixPackage($lockedPackage); + } + } + } + $loadNames = array(); foreach ($request->getFixedPackages() as $package) { $this->nameConstraints[$package->getName()] = null; @@ -218,6 +230,9 @@ class PoolBuilder if (!isset($this->loadedNames[$require])) { $loadNames[$require] = null; } + if (isset($request->getUpdateAllowList()[$package->getName()])) { + + } $linkConstraint = $link->getConstraint(); if ($linkConstraint && !($linkConstraint instanceof EmptyConstraint)) { @@ -235,5 +250,123 @@ class PoolBuilder return $loadNames; } + + /** + * Adds all dependencies of the update whitelist to the whitelist, too. + * + * Packages which are listed as requirements in the root package will be + * skipped including their dependencies, unless they are listed in the + * update whitelist themselves or $whitelistAllDependencies is true. + * + * @param RepositoryInterface $lockRepo Use the locked repo + * As we want the most accurate package list to work with, and installed + * repo might be empty but locked repo will always be current. + * @param array $rootRequires An array of links to packages in require of the root package + * @param array $rootDevRequires An array of links to packages in require-dev of the root package + */ + private function whitelistUpdateDependencies($lockRepo, array $rootRequires, array $rootDevRequires) + { + $rootRequires = array_merge($rootRequires, $rootDevRequires); + + $skipPackages = array(); + if (!$this->whitelistAllDependencies) { + foreach ($rootRequires as $require) { + $skipPackages[$require->getTarget()] = true; + } + } + + $installedRepo = new InstalledRepository(array($lockRepo)); + + $seen = array(); + + $rootRequiredPackageNames = array_keys($rootRequires); + + foreach ($this->updateWhitelist as $packageName => $void) { + $packageQueue = new \SplQueue; + $nameMatchesRequiredPackage = false; + + $depPackages = $installedRepo->findPackagesWithReplacersAndProviders($packageName); + $matchesByPattern = array(); + + // check if the name is a glob pattern that did not match directly + if (empty($depPackages)) { + // add any installed package matching the whitelisted name/pattern + $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); + foreach ($lockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { + $matchesByPattern[] = $installedRepo->findPackages($installedPackage['name']); + } + + // add root requirements which match the whitelisted name/pattern + $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); + foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { + if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { + $nameMatchesRequiredPackage = true; + break; + } + } + } + + if (!empty($matchesByPattern)) { + $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); + } + + if (count($depPackages) == 0 && !$nameMatchesRequiredPackage) { + $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); + } + + foreach ($depPackages as $depPackage) { + $packageQueue->enqueue($depPackage); + } + + while (!$packageQueue->isEmpty()) { + $package = $packageQueue->dequeue(); + if (isset($seen[spl_object_hash($package)])) { + continue; + } + + $seen[spl_object_hash($package)] = true; + $this->updateWhitelist[$package->getName()] = true; + + if (!$this->whitelistTransitiveDependencies && !$this->whitelistAllDependencies) { + continue; + } + + $requires = $package->getRequires(); + + foreach ($requires as $require) { + $requirePackages = $installedRepo->findPackagesWithReplacersAndProviders($require->getTarget()); + + foreach ($requirePackages as $requirePackage) { + if (isset($this->updateWhitelist[$requirePackage->getName()])) { + continue; + } + + if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) { + $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); + continue; + } + + $packageQueue->enqueue($requirePackage); + } + } + } + } + } + + /** + * @param PackageInterface $package + * @return bool + */ + private function isUpdateable(PackageInterface $package) + { + foreach ($this->unfixList as $pattern => $void) { + $patternRegexp = BasePackage::packageNameToRegexp($pattern); + if (preg_match($patternRegexp, $package->getName())) { + return true; + } + } + + return false; + } } diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index d4f1b0523..2e6cbfb57 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -27,6 +27,9 @@ class Request protected $requires = array(); protected $fixedPackages = array(); protected $unlockables = array(); + protected $updateAllowList = array(); + protected $updateAllowTransitiveDependencies = false; + protected $updateAllowTransitiveRootDependencies = false; public function __construct(LockArrayRepository $lockedRepository = null) { @@ -53,6 +56,18 @@ class Request } } + public function setUpdateAllowList($updateAllowList, $updateAllowTransitiveDependencies, $updateAllowTransitiveRootDependencies) + { + $this->updateAllowList = $updateAllowList; + $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies; + $this->updateAllowTransitiveRootDependencies = $updateAllowTransitiveRootDependencies; + } + + public function getUpdateAllowList() + { + return $this->updateAllowList; + } + public function getRequires() { return $this->requires; diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 72273f11d..0187420ff 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -356,12 +356,12 @@ class Installer if (!$lockedRepository) { $this->io->writeError('Cannot update only a partial set of packages without a lock file present.', true, IOInterface::QUIET); return 1; - } + }/* $this->whitelistUpdateDependencies( $lockedRepository, $this->package->getRequires(), $this->package->getDevRequires() - ); + );*/ } $this->io->writeError('Loading composer repositories with package information'); @@ -394,14 +394,9 @@ class Installer } } - // if the updateWhitelist is enabled, packages not in it are also fixed - // to the version specified in the lock - if ($this->updateWhitelist && $lockedRepository) { - foreach ($lockedRepository->getPackages() as $lockedPackage) { - if (!$this->isUpdateable($lockedPackage)) { - $request->fixPackage($lockedPackage); - } - } + // pass the allow list into the request, so the pool builder can apply it + if ($this->updateWhitelist) { + $request->setUpdateAllowList($this->updateWhitelist, $this->whitelistTransitiveDependencies, $this->whitelistAllDependencies); } $pool = $repositorySet->createPool($request, $this->eventDispatcher); @@ -847,26 +842,6 @@ class Installer return $normalizedAliases; } - /** - * @param PackageInterface $package - * @return bool - */ - private function isUpdateable(PackageInterface $package) - { - if (!$this->updateWhitelist) { - throw new \LogicException('isUpdateable should only be called when a whitelist is present'); - } - - foreach ($this->updateWhitelist as $whiteListedPattern => $void) { - $patternRegexp = BasePackage::packageNameToRegexp($whiteListedPattern); - if (preg_match($patternRegexp, $package->getName())) { - return true; - } - } - - return false; - } - /** * @param array $links * @return array @@ -883,108 +858,6 @@ class Installer return $platformReqs; } - /** - * Adds all dependencies of the update whitelist to the whitelist, too. - * - * Packages which are listed as requirements in the root package will be - * skipped including their dependencies, unless they are listed in the - * update whitelist themselves or $whitelistAllDependencies is true. - * - * @param RepositoryInterface $lockRepo Use the locked repo - * As we want the most accurate package list to work with, and installed - * repo might be empty but locked repo will always be current. - * @param array $rootRequires An array of links to packages in require of the root package - * @param array $rootDevRequires An array of links to packages in require-dev of the root package - */ - private function whitelistUpdateDependencies($lockRepo, array $rootRequires, array $rootDevRequires) - { - $rootRequires = array_merge($rootRequires, $rootDevRequires); - - $skipPackages = array(); - if (!$this->whitelistAllDependencies) { - foreach ($rootRequires as $require) { - $skipPackages[$require->getTarget()] = true; - } - } - - $installedRepo = new InstalledRepository(array($lockRepo)); - - $seen = array(); - - $rootRequiredPackageNames = array_keys($rootRequires); - - foreach ($this->updateWhitelist as $packageName => $void) { - $packageQueue = new \SplQueue; - $nameMatchesRequiredPackage = false; - - $depPackages = $installedRepo->findPackagesWithReplacersAndProviders($packageName); - $matchesByPattern = array(); - - // check if the name is a glob pattern that did not match directly - if (empty($depPackages)) { - // add any installed package matching the whitelisted name/pattern - $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); - foreach ($lockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { - $matchesByPattern[] = $installedRepo->findPackages($installedPackage['name']); - } - - // add root requirements which match the whitelisted name/pattern - $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); - foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { - if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { - $nameMatchesRequiredPackage = true; - break; - } - } - } - - if (!empty($matchesByPattern)) { - $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); - } - - if (count($depPackages) == 0 && !$nameMatchesRequiredPackage) { - $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); - } - - foreach ($depPackages as $depPackage) { - $packageQueue->enqueue($depPackage); - } - - while (!$packageQueue->isEmpty()) { - $package = $packageQueue->dequeue(); - if (isset($seen[spl_object_hash($package)])) { - continue; - } - - $seen[spl_object_hash($package)] = true; - $this->updateWhitelist[$package->getName()] = true; - - if (!$this->whitelistTransitiveDependencies && !$this->whitelistAllDependencies) { - continue; - } - - $requires = $package->getRequires(); - - foreach ($requires as $require) { - $requirePackages = $installedRepo->findPackagesWithReplacersAndProviders($require->getTarget()); - - foreach ($requirePackages as $requirePackage) { - if (isset($this->updateWhitelist[$requirePackage->getName()])) { - continue; - } - - if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) { - $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); - continue; - } - - $packageQueue->enqueue($requirePackage); - } - } - } - } - } - /** * Replace local repositories with InstalledArrayRepository instances *