Quellcode durchsuchen

Sort local repo transaction as topological as possible

Nils Adermann vor 5 Jahren
Ursprung
Commit
b700aa3d62

+ 97 - 3
src/Composer/DependencyResolver/LocalRepoTransaction.php

@@ -72,8 +72,6 @@ class LocalRepoTransaction
                 $operations[] = new Operation\InstallOperation($package);
                 unset($removeMap[$package->getName()]);
             }
-
-
 /*
             if (isset($lockedPackages[$package->getName()])) {
                 die("Alias?");
@@ -85,7 +83,10 @@ class LocalRepoTransaction
             $operations[] = new Operation\UninstallOperation($package, null);
         }
 
+        $operations = $this->sortOperations($operations);
         $operations = $this->movePluginsToFront($operations);
+        // TODO fix this:
+        // we have to do this again here even though sortOperations did it because moving plugins moves them before uninstalls
         $operations = $this->moveUninstallsToFront($operations);
 
         // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place?
@@ -109,6 +110,99 @@ class LocalRepoTransaction
         return $operations;
     }
 
+    // TODO is there a more efficient / better way to do get a "good" install order?
+    public function sortOperations(array $operations)
+    {
+        $packageQueue = $this->lockedRepository->getPackages();
+
+        $packageQueue[] = null; // null is a cycle marker
+
+        $weights = array();
+        $foundWeighables = false;
+
+        // This is sort of a topological sort, the weight represents the distance from a leaf (1 == is leaf)
+        // Since we can have cycles in the dep graph, any node which doesn't have an acyclic connection to all
+        // leaves it's connected to, cannot be assigned a weight and will be unsorted
+        while (!empty($packageQueue)) {
+            $package = array_shift($packageQueue);
+
+            // one full cycle
+            if ($package === null) {
+                // if we were able to assign some weights, keep going
+                if ($foundWeighables) {
+                    $foundWeighables = false;
+                    $packageQueue[] = null;
+                    continue;
+                } else {
+                    foreach ($packageQueue as $package) {
+                        $weights[$package->getName()] = PHP_INT_MAX;
+                    }
+                    // no point in continuing, we are in a cycle
+                    break;
+                }
+            }
+
+            $requires = array_filter(array_keys($package->getRequires()), function ($req) {
+                return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req);
+            });
+
+            $maxWeight = 0;
+            foreach ($requires as $require) {
+                if (!isset($weights[$require])) {
+                    $maxWeight = null;
+
+                    // needs more calculation, so add to end of queue
+                    $packageQueue[] = $package;
+                    break;
+                }
+
+                $maxWeight = max((int) $maxWeight, $weights[$require]);
+            }
+            if ($maxWeight !== null) {
+                $foundWeighables = true;
+                $weights[$package->getName()] = $maxWeight + 1;
+            }
+        }
+
+        // TODO do we have any alias ops in the local repo transaction?
+        usort($operations, function ($opA, $opB) use ($weights) {
+            // uninstalls come first, if there are multiple, sort by name
+            if ($opA instanceof Operation\UninstallOperation) {
+                $packageA = $opA->getPackage();
+                if ($opB instanceof Operation\UninstallOperation) {
+                    return strcmp($packageA->getName(), $opB->getPackage()->getName());
+                }
+                return -1;
+            } elseif ($opB instanceof Operation\UninstallOperation) {
+                return 1;
+            }
+
+
+            if ($opA instanceof Operation\InstallOperation) {
+                $packageA = $opA->getPackage();
+            } elseif ($opA instanceof Operation\UpdateOperation) {
+                $packageA = $opA->getTargetPackage();
+            }
+
+            if ($opB instanceof Operation\InstallOperation) {
+                $packageB = $opB->getPackage();
+            } elseif ($opB instanceof Operation\UpdateOperation) {
+                $packageB = $opB->getTargetPackage();
+            }
+
+            $weightA = $weights[$packageA->getName()];
+            $weightB = $weights[$packageB->getName()];
+
+            if ($weightA === $weightB) {
+                return strcmp($packageA->getName(),  $packageB->getName());
+            } else {
+                return $weightA < $weightB ? -1 : 1;
+            }
+        });
+
+        return $operations;
+    }
+
     /**
      * Workaround: if your packages depend on plugins, we must be sure
      * that those are installed / updated first; else it would lead to packages
@@ -176,7 +270,7 @@ class LocalRepoTransaction
     {
         $uninstOps = array();
         foreach ($operations as $idx => $op) {
-            if ($op instanceof UninstallOperation || $op instanceof MarkAliasUninstalledOperation) {
+            if ($op instanceof UninstallOperation) {
                 $uninstOps[] = $op;
                 unset($operations[$idx]);
             }

+ 1 - 1
tests/Composer/Test/InstallerTest.php

@@ -97,7 +97,7 @@ class InstallerTest extends TestCase
             }));
 
         $tempLockData = null;
-        $locker = new Locker($io, $lockJsonMock, $repositoryManager, $installationManager, '{}');
+        $locker = new Locker($io, $lockJsonMock, $installationManager, '{}');
 
         $autoloadGenerator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')->disableOriginalConstructor()->getMock();