Browse Source

Move partial update handling to pool builder

Nils Adermann 5 years ago
parent
commit
da84763f03

+ 133 - 0
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('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
+            }
+
+            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('<warning>Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>');
+                            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;
+    }
 }
 

+ 15 - 0
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;

+ 5 - 132
src/Composer/Installer.php

@@ -356,12 +356,12 @@ class Installer
             if (!$lockedRepository) {
                 $this->io->writeError('<error>Cannot update only a partial set of packages without a lock file present.</error>', true, IOInterface::QUIET);
                 return 1;
-            }
+            }/*
             $this->whitelistUpdateDependencies(
                 $lockedRepository,
                 $this->package->getRequires(),
                 $this->package->getDevRequires()
-            );
+            );*/
         }
 
         $this->io->writeError('<info>Loading composer repositories with package information</info>');
@@ -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('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
-            }
-
-            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('<warning>Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>');
-                            continue;
-                        }
-
-                        $packageQueue->enqueue($requirePackage);
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * Replace local repositories with InstalledArrayRepository instances
      *