From 25de5218c39957dc644eb9f03c265a0bb7060534 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Fri, 8 Nov 2019 15:56:46 +0100 Subject: [PATCH] Reunify lock and local repo transaction code and apply the same sorting --- .../LocalRepoTransaction.php | 275 +--------------- .../DependencyResolver/LockTransaction.php | 141 +------- src/Composer/DependencyResolver/Solver.php | 2 +- .../DependencyResolver/Transaction.php | 310 ++++++++++++++++++ src/Composer/Installer.php | 17 - .../Test/DependencyResolver/SolverTest.php | 31 +- .../Fixtures/installer/abandoned-listed.test | 2 +- .../installer/github-issues-4795-2.test | 2 +- .../Fixtures/installer/suggest-installed.test | 2 +- .../update-with-all-dependencies.test | 2 +- 10 files changed, 346 insertions(+), 438 deletions(-) create mode 100644 src/Composer/DependencyResolver/Transaction.php diff --git a/src/Composer/DependencyResolver/LocalRepoTransaction.php b/src/Composer/DependencyResolver/LocalRepoTransaction.php index 50056e953..b22f555e5 100644 --- a/src/Composer/DependencyResolver/LocalRepoTransaction.php +++ b/src/Composer/DependencyResolver/LocalRepoTransaction.php @@ -24,278 +24,13 @@ use Composer\Semver\Constraint\Constraint; /** * @author Nils Adermann */ -class LocalRepoTransaction +class LocalRepoTransaction extends Transaction { - /** @var array */ - protected $lockedPackages; - protected $lockedPackagesByName = array(); - - /** @var RepositoryInterface */ - protected $localRepository; - - /** @var array */ - protected $operations; - - /** - * Reassigns ids for all packages in the lockedrepository - */ public function __construct(RepositoryInterface $lockedRepository, $localRepository) { - $this->localRepository = $localRepository; - $this->setLockedPackageMaps($lockedRepository); - $this->operations = $this->calculateOperations(); - } - - private function setLockedPackageMaps($lockedRepository) - { - $packageSort = function (PackageInterface $a, PackageInterface $b) { - // sort alias packages by the same name behind their non alias version - if ($a->getName() == $b->getName() && $a instanceof AliasPackage != $b instanceof AliasPackage) { - return $a instanceof AliasPackage ? -1 : 1; - } - return strcmp($b->getName(), $a->getName()); - }; - - $id = 1; - $this->lockedPackages = array(); - foreach ($lockedRepository->getPackages() as $package) { - $package->id = $id++; - $this->lockedPackages[$package->id] = $package; - foreach ($package->getNames() as $name) { - $this->lockedPackagesByName[$name][] = $package; - } - } - - uasort($this->lockedPackages, $packageSort); - foreach ($this->lockedPackagesByName as $name => $packages) { - uasort($this->lockedPackagesByName[$name], $packageSort); - } - } - - public function getOperations() - { - return $this->operations; - } - - protected function calculateOperations() - { - $operations = array(); - - $localPackageMap = array(); - $removeMap = array(); - $localAliasMap = array(); - $removeAliasMap = array(); - foreach ($this->localRepository->getPackages() as $package) { - if ($package instanceof AliasPackage) { - $localAliasMap[$package->getName().'::'.$package->getVersion()] = $package; - $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package; - } else { - $localPackageMap[$package->getName()] = $package; - $removeMap[$package->getName()] = $package; - } - } - - $stack = $this->getRootPackages(); - - $visited = array(); - $processed = array(); - - while (!empty($stack)) { - $package = array_pop($stack); - - if (isset($processed[$package->id])) { - continue; - } - - if (!isset($visited[$package->id])) { - $visited[$package->id] = true; - - $stack[] = $package; - if ($package instanceof AliasPackage) { - $stack[] = $package->getAliasOf(); - } else { - foreach ($package->getRequires() as $link) { - $possibleRequires = $this->getLockedProviders($link); - - foreach ($possibleRequires as $require) { - $stack[] = $require; - } - } - } - } elseif (!isset($processed[$package->id])) { - $processed[$package->id] = true; - - if ($package instanceof AliasPackage) { - $aliasKey = $package->getName().'::'.$package->getVersion(); - if (isset($localAliasMap[$aliasKey])) { - unset($removeAliasMap[$aliasKey]); - } else { - $operations[] = new Operation\MarkAliasInstalledOperation($package); - } - } else { - if (isset($localPackageMap[$package->getName()])) { - $source = $localPackageMap[$package->getName()]; - - // do we need to update? - if ($package->getVersion() != $localPackageMap[$package->getName()]->getVersion()) { - $operations[] = new Operation\UpdateOperation($source, $package); - } elseif ($package->isDev() && $package->getSourceReference() !== $localPackageMap[$package->getName()]->getSourceReference()) { - $operations[] = new Operation\UpdateOperation($source, $package); - } - unset($removeMap[$package->getName()]); - } else { - $operations[] = new Operation\InstallOperation($package); - unset($removeMap[$package->getName()]); - } - } - } - } - - foreach ($removeMap as $name => $package) { - array_unshift($operations, new Operation\UninstallOperation($package, null)); - } - foreach ($removeAliasMap as $nameVersion => $package) { - $operations[] = new Operation\MarkAliasUninstalledOperation($package, null); - } - - $operations = $this->movePluginsToFront($operations); - // TODO fix this: - // we have to do this again here even though the above stack code 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? - /* - if ('update' === $jobType) { - $targetPackage = $operation->getTargetPackage(); - if ($targetPackage->isDev()) { - $initialPackage = $operation->getInitialPackage(); - if ($targetPackage->getVersion() === $initialPackage->getVersion() - && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) - && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) - ) { - $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); - $this->io->writeError('', true, IOInterface::DEBUG); - - continue; - } - } - }*/ - - return $operations; - } - - /** - * Determine which packages in the lock file are not required by any other packages in the lock file. - * - * These serve as a starting point to enumerate packages in a topological order despite potential cycles. - * If there are packages with a cycle on the top level the package with the lowest name gets picked - * - * @return array - */ - private function getRootPackages() - { - $roots = $this->lockedPackages; - - foreach ($this->lockedPackages as $packageId => $package) { - if (!isset($roots[$packageId])) { - continue; - } - - foreach ($package->getRequires() as $link) { - $possibleRequires = $this->getLockedProviders($link); - - foreach ($possibleRequires as $require) { - if ($require !== $package) { - unset($roots[$require->id]); - } - } - } - } - - return $roots; - } - - private function getLockedProviders(Link $link) - { - if (!isset($this->lockedPackagesByName[$link->getTarget()])) { - return array(); - } - return $this->lockedPackagesByName[$link->getTarget()]; - } - - /** - * Workaround: if your packages depend on plugins, we must be sure - * that those are installed / updated first; else it would lead to packages - * being installed multiple times in different folders, when running Composer - * twice. - * - * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, - * it at least fixes the symptoms and makes usage of composer possible (again) - * in such scenarios. - * - * @param Operation\OperationInterface[] $operations - * @return Operation\OperationInterface[] reordered operation list - */ - private function movePluginsToFront(array $operations) - { - $pluginsNoDeps = array(); - $pluginsWithDeps = array(); - $pluginRequires = array(); - - foreach (array_reverse($operations, true) as $idx => $op) { - if ($op instanceof Operation\InstallOperation) { - $package = $op->getPackage(); - } elseif ($op instanceof Operation\UpdateOperation) { - $package = $op->getTargetPackage(); - } else { - continue; - } - - // is this package a plugin? - $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; - - // is this a plugin or a dependency of a plugin? - if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) { - // get the package's requires, but filter out any platform requirements or 'composer-plugin-api' - $requires = array_filter(array_keys($package->getRequires()), function ($req) { - return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req); - }); - - // is this a plugin with no meaningful dependencies? - if ($isPlugin && !count($requires)) { - // plugins with no dependencies go to the very front - array_unshift($pluginsNoDeps, $op); - } else { - // capture the requirements for this package so those packages will be moved up as well - $pluginRequires = array_merge($pluginRequires, $requires); - // move the operation to the front - array_unshift($pluginsWithDeps, $op); - } - - unset($operations[$idx]); - } - } - - return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations); - } - - /** - * Removals of packages should be executed before installations in - * case two packages resolve to the same path (due to custom installers) - * - * @param Operation\OperationInterface[] $operations - * @return Operation\OperationInterface[] reordered operation list - */ - private function moveUninstallsToFront(array $operations) - { - $uninstOps = array(); - foreach ($operations as $idx => $op) { - if ($op instanceof UninstallOperation) { - $uninstOps[] = $op; - unset($operations[$idx]); - } - } - - return array_merge($uninstOps, $operations); + parent::__construct( + $localRepository->getPackages(), + $lockedRepository->getPackages() + ); } } diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 76468ddc6..b74cfbc78 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -23,12 +23,8 @@ use Composer\Test\Repository\ArrayRepositoryTest; /** * @author Nils Adermann */ -class LockTransaction +class LockTransaction extends Transaction { - protected $policy; - /** @var Pool */ - protected $pool; - /** * packages in current lock file, platform repo or otherwise present * @var array @@ -40,107 +36,32 @@ class LockTransaction * @var array */ protected $unlockableMap; - protected $decisions; - protected $resultPackages; /** * @var array */ - protected $operations; + protected $resultPackages; - public function __construct($policy, $pool, $presentMap, $unlockableMap, $decisions) + public function __construct(Pool $pool, $presentMap, $unlockableMap, $decisions) { - $this->policy = $policy; - $this->pool = $pool; $this->presentMap = $presentMap; $this->unlockableMap = $unlockableMap; - $this->decisions = $decisions; - $this->operations = $this->calculateOperations(); - } + $this->setResultPackages($pool, $decisions); + parent::__construct($this->presentMap, $this->resultPackages['all']); - /** - * @return OperationInterface[] - */ - public function getOperations() - { - return $this->operations; - } - - protected function calculateOperations() - { - $operations = array(); - $ignoreRemove = array(); - $lockMeansUpdateMap = $this->findPotentialUpdates(); - - foreach ($this->decisions as $i => $decision) { - $literal = $decision[Decisions::DECISION_LITERAL]; - $reason = $decision[Decisions::DECISION_REASON]; - - $package = $this->pool->literalToPackage($literal); - - // wanted & !present - if ($literal > 0 && !isset($this->presentMap[spl_object_hash($package)])) { - if (isset($lockMeansUpdateMap[spl_object_hash($package)]) && !$package instanceof AliasPackage) { - // TODO we end up here sometimes because we prefer the remote package now to get up to date metadata - // TODO define some level of identity here for what constitutes an update and what can be ignored? new kind of metadata only update? - $target = $lockMeansUpdateMap[spl_object_hash($package)]; - if ($package->getName() !== $target->getName() || $package->getVersion() !== $target->getVersion()) { - $operations[] = new Operation\UpdateOperation($target, $package, $reason); - } - - // avoid updates to one package from multiple origins - $ignoreRemove[spl_object_hash($lockMeansUpdateMap[spl_object_hash($package)])] = true; - unset($lockMeansUpdateMap[spl_object_hash($package)]); - } else { - if ($package instanceof AliasPackage) { - $operations[] = new Operation\MarkAliasInstalledOperation($package, $reason); - } else { - $operations[] = new Operation\InstallOperation($package, $reason); - } - } - } - } - - foreach ($this->decisions as $i => $decision) { - $literal = $decision[Decisions::DECISION_LITERAL]; - $reason = $decision[Decisions::DECISION_REASON]; - $package = $this->pool->literalToPackage($literal); - - if ($literal <= 0 && isset($this->presentMap[spl_object_hash($package)]) && !isset($ignoreRemove[spl_object_hash($package)])) { - if ($package instanceof AliasPackage) { - $operations[] = new Operation\MarkAliasUninstalledOperation($package, $reason); - } else { - $operations[] = new Operation\UninstallOperation($package, $reason); - } - } - } - - foreach ($this->presentMap as $package) { - if ($package->id === -1 && !isset($ignoreRemove[spl_object_hash($package)])) { - // TODO pass reason parameter to these two operations? - if ($package instanceof AliasPackage) { - $operations[] = new Operation\MarkAliasUninstalledOperation($package); - } else { - $operations[] = new Operation\UninstallOperation($package); - } - } - } - - $this->setResultPackages(); - - return $operations; } // TODO make this a bit prettier instead of the two text indexes? - public function setResultPackages() + public function setResultPackages(Pool $pool, Decisions $decisions) { - $this->resultPackages = array('non-dev' => array(), 'dev' => array()); - foreach ($this->decisions as $i => $decision) { + $this->resultPackages = array('all' => array(), 'non-dev' => array(), 'dev' => array()); + foreach ($decisions as $i => $decision) { $literal = $decision[Decisions::DECISION_LITERAL]; if ($literal > 0) { - $package = $this->pool->literalToPackage($literal); + $package = $pool->literalToPackage($literal); + $this->resultPackages['all'][] = $package; if (!isset($this->unlockableMap[$package->id])) { $this->resultPackages['non-dev'][] = $package; } @@ -191,46 +112,4 @@ class LockTransaction return $packages; } - - protected function findPotentialUpdates() - { - $lockMeansUpdateMap = array(); - - $packages = array(); - - foreach ($this->decisions as $i => $decision) { - $literal = $decision[Decisions::DECISION_LITERAL]; - $package = $this->pool->literalToPackage($literal); - - if ($literal <= 0 && isset($this->presentMap[spl_object_hash($package)])) { - $packages[spl_object_hash($package)] = $package; - } - } - - // some locked packages are not in the pool and thus, were not decided at all - foreach ($this->presentMap as $package) { - if ($package->id === -1) { - $packages[spl_object_hash($package)] = $package; - } - } - - foreach ($packages as $package) { - if ($package instanceof AliasPackage) { - continue; - } - - // TODO can't we just look at existing rules? - $updates = $this->policy->findUpdatePackages($this->pool, $package); - - $updatesAndPackage = array_merge(array($package), $updates); - - foreach ($updatesAndPackage as $update) { - if (!isset($lockMeansUpdateMap[spl_object_hash($update)])) { - $lockMeansUpdateMap[spl_object_hash($update)] = $package; - } - } - } - - return $lockMeansUpdateMap; - } } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 66f325446..ad635ff00 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -221,7 +221,7 @@ class Solver throw new SolverProblemsException($this->problems, $request->getPresentMap(true), $this->learnedPool); } - return new LockTransaction($this->policy, $this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions); + return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions); } /** diff --git a/src/Composer/DependencyResolver/Transaction.php b/src/Composer/DependencyResolver/Transaction.php new file mode 100644 index 000000000..f12aeccc5 --- /dev/null +++ b/src/Composer/DependencyResolver/Transaction.php @@ -0,0 +1,310 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver; + +use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\Package\AliasPackage; +use Composer\Package\Link; +use Composer\Package\PackageInterface; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Semver\Constraint\Constraint; + +/** + * @author Nils Adermann + */ +abstract class Transaction +{ + /** + * @var array + */ + protected $operations; + + /** + * Packages present at the beginning of the transaction + * @var array + */ + protected $presentPackages; + + /** + * Package set resulting from this transaction + * @var array + */ + protected $resultPackageMap; + + /** + * @var array + */ + protected $resultPackagesByName = array(); + + public function __construct($presentPackages, $resultPackages) + { + $this->presentPackages = $presentPackages; + $this->setResultPackageMaps($resultPackages); + $this->operations = $this->calculateOperations(); + + } + + public function getOperations() + { + return $this->operations; + } + + private function setResultPackageMaps($resultPackages) + { + $packageSort = function (PackageInterface $a, PackageInterface $b) { + // sort alias packages by the same name behind their non alias version + if ($a->getName() == $b->getName() && $a instanceof AliasPackage != $b instanceof AliasPackage) { + return $a instanceof AliasPackage ? -1 : 1; + } + return strcmp($b->getName(), $a->getName()); + }; + + $this->resultPackageMap = array(); + foreach ($resultPackages as $package) { + $this->resultPackageMap[spl_object_hash($package)] = $package; + foreach ($package->getNames() as $name) { + $this->resultPackagesByName[$name][] = $package; + } + } + + uasort($this->resultPackageMap, $packageSort); + foreach ($this->resultPackagesByName as $name => $packages) { + uasort($this->resultPackagesByName[$name], $packageSort); + } + } + + protected function calculateOperations() + { + $operations = array(); + + $presentPackageMap = array(); + $removeMap = array(); + $presentAliasMap = array(); + $removeAliasMap = array(); + foreach ($this->presentPackages as $package) { + if ($package instanceof AliasPackage) { + $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package; + } else { + $presentPackageMap[$package->getName()] = $package; + $removeMap[$package->getName()] = $package; + } + } + + $stack = $this->getRootPackages(); + + $visited = array(); + $processed = array(); + + while (!empty($stack)) { + $package = array_pop($stack); + + if (isset($processed[spl_object_hash($package)])) { + continue; + } + + if (!isset($visited[spl_object_hash($package)])) { + $visited[spl_object_hash($package)] = true; + + $stack[] = $package; + if ($package instanceof AliasPackage) { + $stack[] = $package->getAliasOf(); + } else { + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + $stack[] = $require; + } + } + } + } elseif (!isset($processed[spl_object_hash($package)])) { + $processed[spl_object_hash($package)] = true; + + if ($package instanceof AliasPackage) { + $aliasKey = $package->getName().'::'.$package->getVersion(); + if (isset($presentAliasMap[$aliasKey])) { + unset($removeAliasMap[$aliasKey]); + } else { + $operations[] = new Operation\MarkAliasInstalledOperation($package); + } + } else { + if (isset($presentPackageMap[$package->getName()])) { + $source = $presentPackageMap[$package->getName()]; + + // do we need to update? + // TODO different for lock? + if ($package->getVersion() != $presentPackageMap[$package->getName()]->getVersion()) { + $operations[] = new Operation\UpdateOperation($source, $package); + } elseif ($package->isDev() && $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference()) { + $operations[] = new Operation\UpdateOperation($source, $package); + } + unset($removeMap[$package->getName()]); + } else { + $operations[] = new Operation\InstallOperation($package); + unset($removeMap[$package->getName()]); + } + } + } + } + + foreach ($removeMap as $name => $package) { + array_unshift($operations, new Operation\UninstallOperation($package, null)); + } + foreach ($removeAliasMap as $nameVersion => $package) { + $operations[] = new Operation\MarkAliasUninstalledOperation($package, null); + } + + $operations = $this->movePluginsToFront($operations); + // TODO fix this: + // we have to do this again here even though the above stack code 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? + /* + if ('update' === $jobType) { + $targetPackage = $operation->getTargetPackage(); + if ($targetPackage->isDev()) { + $initialPackage = $operation->getInitialPackage(); + if ($targetPackage->getVersion() === $initialPackage->getVersion() + && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference()) + && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference()) + ) { + $this->io->writeError(' - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG); + $this->io->writeError('', true, IOInterface::DEBUG); + + continue; + } + } + }*/ + + return $this->operations = $operations; + } + + /** + * Determine which packages in the result are not required by any other packages in it. + * + * These serve as a starting point to enumerate packages in a topological order despite potential cycles. + * If there are packages with a cycle on the top level the package with the lowest name gets picked + * + * @return array + */ + protected function getRootPackages() + { + $roots = $this->resultPackageMap; + + foreach ($this->resultPackageMap as $packageHash => $package) { + if (!isset($roots[$packageHash])) { + continue; + } + + foreach ($package->getRequires() as $link) { + $possibleRequires = $this->getProvidersInResult($link); + + foreach ($possibleRequires as $require) { + if ($require !== $package) { + unset($roots[spl_object_hash($require)]); + } + } + } + } + + return $roots; + } + + protected function getProvidersInResult(Link $link) + { + if (!isset($this->resultPackagesByName[$link->getTarget()])) { + return array(); + } + return $this->resultPackagesByName[$link->getTarget()]; + } + + /** + * Workaround: if your packages depend on plugins, we must be sure + * that those are installed / updated first; else it would lead to packages + * being installed multiple times in different folders, when running Composer + * twice. + * + * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147, + * it at least fixes the symptoms and makes usage of composer possible (again) + * in such scenarios. + * + * @param Operation\OperationInterface[] $operations + * @return Operation\OperationInterface[] reordered operation list + */ + private function movePluginsToFront(array $operations) + { + $pluginsNoDeps = array(); + $pluginsWithDeps = array(); + $pluginRequires = array(); + + foreach (array_reverse($operations, true) as $idx => $op) { + if ($op instanceof Operation\InstallOperation) { + $package = $op->getPackage(); + } elseif ($op instanceof Operation\UpdateOperation) { + $package = $op->getTargetPackage(); + } else { + continue; + } + + // is this package a plugin? + $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer'; + + // is this a plugin or a dependency of a plugin? + if ($isPlugin || count(array_intersect($package->getNames(), $pluginRequires))) { + // get the package's requires, but filter out any platform requirements or 'composer-plugin-api' + $requires = array_filter(array_keys($package->getRequires()), function ($req) { + return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req); + }); + + // is this a plugin with no meaningful dependencies? + if ($isPlugin && !count($requires)) { + // plugins with no dependencies go to the very front + array_unshift($pluginsNoDeps, $op); + } else { + // capture the requirements for this package so those packages will be moved up as well + $pluginRequires = array_merge($pluginRequires, $requires); + // move the operation to the front + array_unshift($pluginsWithDeps, $op); + } + + unset($operations[$idx]); + } + } + + return array_merge($pluginsNoDeps, $pluginsWithDeps, $operations); + } + + /** + * Removals of packages should be executed before installations in + * case two packages resolve to the same path (due to custom installers) + * + * @param Operation\OperationInterface[] $operations + * @return Operation\OperationInterface[] reordered operation list + */ + private function moveUninstallsToFront(array $operations) + { + $uninstOps = array(); + foreach ($operations as $idx => $op) { + if ($op instanceof UninstallOperation) { + $uninstOps[] = $op; + unset($operations[$idx]); + } + } + + return array_merge($uninstOps, $operations); + } +} diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 8b5c85057..941717912 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -486,23 +486,6 @@ class Installer if (false === strpos($operation->getJobType(), 'Alias') || $this->io->isDebug()) { $this->io->writeError(' - ' . $operation); } - - // output reasons why the operation was run, only for install/update operations - if ($this->verbose && $this->io->isVeryVerbose() && in_array($jobType, array('install', 'update'))) { - $reason = $operation->getReason(); - if ($reason instanceof Rule) { - switch ($reason->getReason()) { - case Rule::RULE_JOB_INSTALL: - $this->io->writeError(' REASON: Required by the root package: '.$reason->getPrettyString($pool)); - $this->io->writeError(''); - break; - case Rule::RULE_PACKAGE_REQUIRES: - $this->io->writeError(' REASON: '.$reason->getPrettyString($pool)); - $this->io->writeError(''); - break; - } - } - } } $updatedLock = $this->locker->setLockData( diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 8f12f3122..4c733a4fb 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -168,9 +168,9 @@ class SolverTest extends TestCase $this->request->install('C'); $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageA), array('job' => 'install', 'package' => $packageC), array('job' => 'install', 'package' => $packageB), - array('job' => 'install', 'package' => $packageA), )); } @@ -338,15 +338,15 @@ class SolverTest extends TestCase $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0')); $this->checkSolverResult(array( + array( + 'job' => 'remove', + 'package' => $packageB, + ), array( 'job' => 'update', 'from' => $packageA, 'to' => $newPackageA, ), - array( - 'job' => 'remove', - 'package' => $packageB, - ), )); } @@ -369,10 +369,10 @@ class SolverTest extends TestCase $this->request->remove('D'); $this->checkSolverResult(array( - array('job' => 'install', 'package' => $packageB), - array('job' => 'update', 'from' => $oldPackageC, 'to' => $packageC), - array('job' => 'install', 'package' => $packageA), array('job' => 'remove', 'package' => $packageD), + array('job' => 'install', 'package' => $packageB), + array('job' => 'install', 'package' => $packageA), + array('job' => 'update', 'from' => $oldPackageC, 'to' => $packageC), )); } @@ -406,7 +406,8 @@ class SolverTest extends TestCase $this->request->install('B'); $this->checkSolverResult(array( - array('job' => 'update', 'from' => $packageA, 'to' => $packageB), + array('job' => 'remove', 'package' => $packageA), + array('job' => 'install', 'package' => $packageB), )); } @@ -526,8 +527,8 @@ class SolverTest extends TestCase $this->request->install('X'); $this->checkSolverResult(array( - array('job' => 'install', 'package' => $packageA), array('job' => 'install', 'package' => $newPackageB), + array('job' => 'install', 'package' => $packageA), array('job' => 'install', 'package' => $packageX), )); } @@ -571,8 +572,8 @@ class SolverTest extends TestCase $this->checkSolverResult(array( array('job' => 'install', 'package' => $packageB), - array('job' => 'install', 'package' => $packageC), array('job' => 'install', 'package' => $packageA), + array('job' => 'install', 'package' => $packageC), )); } @@ -829,9 +830,9 @@ class SolverTest extends TestCase $this->request->install('B'); $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageA), array('job' => 'install', 'package' => $packageAAlias), array('job' => 'install', 'package' => $packageB), - array('job' => 'install', 'package' => $packageA), )); } @@ -894,12 +895,12 @@ class SolverTest extends TestCase $this->assertFalse($this->solver->testFlagLearnedPositiveLiteral); $this->checkSolverResult(array( - array('job' => 'install', 'package' => $packageC2), - array('job' => 'install', 'package' => $packageG2), array('job' => 'install', 'package' => $packageF1), + array('job' => 'install', 'package' => $packageD), + array('job' => 'install', 'package' => $packageG2), + array('job' => 'install', 'package' => $packageC2), array('job' => 'install', 'package' => $packageE), array('job' => 'install', 'package' => $packageB), - array('job' => 'install', 'package' => $packageD), array('job' => 'install', 'package' => $packageA), )); diff --git a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test index cdf648c0d..7c671565d 100644 --- a/tests/Composer/Test/Fixtures/installer/abandoned-listed.test +++ b/tests/Composer/Test/Fixtures/installer/abandoned-listed.test @@ -27,8 +27,8 @@ update Loading composer repositories with package information Updating dependencies Lock file operations: 2 installs, 0 updates, 0 removals - - Installing c/c (1.0.0) - Installing a/a (1.0.0) + - Installing c/c (1.0.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 2 installs, 0 updates, 0 removals diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test index 878e9429a..e7730cefc 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test @@ -55,8 +55,8 @@ update a/a b/b --with-dependencies Loading composer repositories with package information Updating dependencies Lock file operations: 0 installs, 2 updates, 0 removals - - Updating b/b (1.0.0) to b/b (1.1.0) - Updating a/a (1.0.0) to a/a (1.1.0) + - Updating b/b (1.0.0) to b/b (1.1.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 0 installs, 2 updates, 0 removals diff --git a/tests/Composer/Test/Fixtures/installer/suggest-installed.test b/tests/Composer/Test/Fixtures/installer/suggest-installed.test index 6995f78e7..e53ab0065 100644 --- a/tests/Composer/Test/Fixtures/installer/suggest-installed.test +++ b/tests/Composer/Test/Fixtures/installer/suggest-installed.test @@ -22,8 +22,8 @@ update Loading composer repositories with package information Updating dependencies Lock file operations: 2 installs, 0 updates, 0 removals - - Installing b/b (1.0.0) - Installing a/a (1.0.0) + - Installing b/b (1.0.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 2 installs, 0 updates, 0 removals diff --git a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test index 14d8e13d8..8a72cec66 100644 --- a/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test +++ b/tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test @@ -50,8 +50,8 @@ update b/b --with-all-dependencies Loading composer repositories with package information Updating dependencies Lock file operations: 0 installs, 2 updates, 0 removals - - Updating b/b (1.0.0) to b/b (1.1.0) - Updating a/a (1.0.0) to a/a (1.1.0) + - Updating b/b (1.0.0) to b/b (1.1.0) Writing lock file Installing dependencies from lock file (including require-dev) Package operations: 0 installs, 2 updates, 0 removals