1
0
Fork 0

Sort local repo transaction as topological as possible

pull/7936/head
Nils Adermann 2019-09-07 06:03:39 +02:00
parent f5e18250e6
commit b700aa3d62
2 changed files with 98 additions and 4 deletions

View File

@ -72,8 +72,6 @@ class LocalRepoTransaction
$operations[] = new Operation\InstallOperation($package); $operations[] = new Operation\InstallOperation($package);
unset($removeMap[$package->getName()]); unset($removeMap[$package->getName()]);
} }
/* /*
if (isset($lockedPackages[$package->getName()])) { if (isset($lockedPackages[$package->getName()])) {
die("Alias?"); die("Alias?");
@ -85,7 +83,10 @@ class LocalRepoTransaction
$operations[] = new Operation\UninstallOperation($package, null); $operations[] = new Operation\UninstallOperation($package, null);
} }
$operations = $this->sortOperations($operations);
$operations = $this->movePluginsToFront($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); $operations = $this->moveUninstallsToFront($operations);
// TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place? // 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; 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 * Workaround: if your packages depend on plugins, we must be sure
* that those are installed / updated first; else it would lead to packages * that those are installed / updated first; else it would lead to packages
@ -176,7 +270,7 @@ class LocalRepoTransaction
{ {
$uninstOps = array(); $uninstOps = array();
foreach ($operations as $idx => $op) { foreach ($operations as $idx => $op) {
if ($op instanceof UninstallOperation || $op instanceof MarkAliasUninstalledOperation) { if ($op instanceof UninstallOperation) {
$uninstOps[] = $op; $uninstOps[] = $op;
unset($operations[$idx]); unset($operations[$idx]);
} }

View File

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