From ce2a40b2598a3381e5050fd8d7b5bbf14619775d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Nov 2021 17:45:12 +0100 Subject: [PATCH] List identical/compatible removed versions in problem output as if they had not been removed --- src/Composer/DependencyResolver/Pool.php | 43 +++++++- .../DependencyResolver/PoolOptimizer.php | 101 ++++++++++++------ src/Composer/DependencyResolver/Problem.php | 41 ++++--- src/Composer/DependencyResolver/Rule.php | 50 +++++++-- src/Composer/Installer.php | 2 + .../deduplicate-solver-problems.test | 30 ++---- .../installer/github-issues-7051.test | 10 +- .../installer/provider-conflicts3.test | 4 +- 8 files changed, 204 insertions(+), 77 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index aa9648129..857f62df8 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -36,16 +36,57 @@ class Pool implements \Countable protected $providerCache = array(); /** @var BasePackage[] */ protected $unacceptableFixedOrLockedPackages; + /** @var array> Map of package name => normalized version => pretty version */ + protected $removedVersions = array(); + /** @var array> Map of package object hash => removed normalized versions => removed pretty version */ + protected $removedVersionsByPackage = array(); /** * @param BasePackage[] $packages * @param BasePackage[] $unacceptableFixedOrLockedPackages + * @param array> $removedVersions + * @param array> $removedVersionsByPackage */ - public function __construct(array $packages = array(), array $unacceptableFixedOrLockedPackages = array()) + public function __construct(array $packages = array(), array $unacceptableFixedOrLockedPackages = array(), array $removedVersions = array(), array $removedVersionsByPackage = array()) { $this->versionParser = new VersionParser; $this->setPackages($packages); $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; + $this->removedVersions = $removedVersions; + $this->removedVersionsByPackage = $removedVersionsByPackage; + } + + /** + * @param string $name + * @return array + */ + public function getRemovedVersions($name, ConstraintInterface $constraint) + { + if (!isset($this->removedVersions[$name])) { + return array(); + } + + $result = array(); + foreach ($this->removedVersions[$name] as $version => $prettyVersion) { + if ($constraint->matches(new Constraint('==', $version))) { + $result[$version] = $prettyVersion; + } + } + + return $result; + } + + /** + * @param string $objectHash + * @return array + */ + public function getRemovedVersionsByPackage($objectHash) + { + if (!isset($this->removedVersionsByPackage[$objectHash])) { + return array(); + } + + return $this->removedVersionsByPackage[$objectHash]; } /** diff --git a/src/Composer/DependencyResolver/PoolOptimizer.php b/src/Composer/DependencyResolver/PoolOptimizer.php index 3b08da0e0..c05045a2b 100644 --- a/src/Composer/DependencyResolver/PoolOptimizer.php +++ b/src/Composer/DependencyResolver/PoolOptimizer.php @@ -14,6 +14,7 @@ namespace Composer\DependencyResolver; use Composer\Package\AliasPackage; use Composer\Package\BasePackage; +use Composer\Package\Version\VersionParser; use Composer\Semver\CompilingMatcher; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\Constraint; @@ -57,6 +58,11 @@ class PoolOptimizer */ private $aliasesPerPackage = array(); + /** + * @var array> + */ + private $removedVersionsByPackage = array(); + public function __construct(PolicyInterface $policy) { $this->policy = $policy; @@ -81,6 +87,7 @@ class PoolOptimizer $this->conflictConstraintsPerPackage = array(); $this->packagesToRemove = array(); $this->aliasesPerPackage = array(); + $this->removedVersionsByPackage = array(); return $optimizedPool; } @@ -165,13 +172,16 @@ class PoolOptimizer private function applyRemovalsToPool(Pool $pool) { $packages = array(); + $removedVersions = array(); foreach ($pool->getPackages() as $package) { if (!isset($this->packagesToRemove[$package->id])) { $packages[] = $package; + } else { + $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion(); } } - $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages()); + $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage); // Reset package removals $this->packagesToRemove = array(); @@ -184,8 +194,8 @@ class PoolOptimizer */ private function optimizeByIdenticalDependencies(Pool $pool) { - $identicalDefinitionPerPackage = array(); - $packageIdsToRemove = array(); + $identicalDefinitionsPerPackage = array(); + $packageIdenticalDefinitionLookup = array(); foreach ($pool->getPackages() as $package) { @@ -195,7 +205,7 @@ class PoolOptimizer continue; } - $packageIdsToRemove[$package->id] = true; + $this->markPackageForRemoval($package->id); $dependencyHash = $this->calculateDependencyHash($package); @@ -206,7 +216,6 @@ class PoolOptimizer } foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { - $groupHashParts = array(); if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { @@ -234,32 +243,19 @@ class PoolOptimizer continue; } - $identicalDefinitionPerPackage[$packageName][implode('', $groupHashParts)][$dependencyHash][] = $package; + $groupHash = implode('', $groupHashParts); + $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package; + $packageIdenticalDefinitionLookup[$package->id][$packageName] = array('groupHash' => $groupHash, 'dependencyHash' => $dependencyHash); } } } - $keepPackage = function (BasePackage $package, $aliasesPerPackage) use (&$packageIdsToRemove, &$keepPackage) { - unset($packageIdsToRemove[$package->id]); - if ($package instanceof AliasPackage) { - // recursing here so aliasesPerPackage for the aliasOf can be checked - // and all its aliases marked to be kept as well - $keepPackage($package->getAliasOf(), $aliasesPerPackage); - } - if (isset($aliasesPerPackage[$package->id])) { - foreach ($aliasesPerPackage[$package->id] as $aliasPackage) { - unset($packageIdsToRemove[$aliasPackage->id]); - } - } - }; - - foreach ($identicalDefinitionPerPackage as $package => $constraintGroups) { + foreach ($identicalDefinitionsPerPackage as $constraintGroups) { foreach ($constraintGroups as $constraintGroup) { - foreach ($constraintGroup as $hash => $packages) { - + foreach ($constraintGroup as $packages) { // Only one package in this constraint group has the same requirements, we're not allowed to remove that package if (1 === \count($packages)) { - $keepPackage($packages[0], $this->aliasesPerPackage); + $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); continue; } @@ -272,17 +268,12 @@ class PoolOptimizer } foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) { - $keepPackage($pool->literalToPackage($preferredLiteral), $this->aliasesPerPackage); + $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); } } } } - foreach ($packageIdsToRemove as $id => $dummy) { - $this->markPackageForRemoval($id); - } - - // Apply removals return $this->applyRemovalsToPool($pool); } @@ -342,4 +333,54 @@ class PoolOptimizer $this->packagesToRemove[$id] = true; } + + /** + * @param array>>> $identicalDefinitionsPerPackage + * @param array> $packageIdenticalDefinitionLookup + * @return void + */ + private function keepPackage(BasePackage $package, $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup) + { + unset($this->packagesToRemove[$package->id]); + + if ($package instanceof AliasPackage) { + // recursing here so aliasesPerPackage for the aliasOf can be checked + // and all its aliases marked to be kept as well + $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup); + } + + // record all the versions of the package group so we can list them later in Problem output + foreach ($package->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + + if (isset($this->aliasesPerPackage[$package->id])) { + foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) { + unset($this->packagesToRemove[$aliasPackage->id]); + + // record all the versions of the package group so we can list them later in Problem output + foreach ($aliasPackage->getNames(false) as $name) { + if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) { + $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name]; + $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']]; + foreach ($packageGroup as $pkg) { + if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { + $pkg = $pkg->getAliasOf(); + } + $this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion(); + } + } + } + } + } + } } diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 507e45d17..aec0a33d1 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -126,6 +126,10 @@ class Problem $template = preg_replace('{^\S+ \S+ }', '%s%s ', $message); $messages[] = $template; $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; + $sourcePackage = $rule->getSourcePackage($pool); + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) { + $templates[$template][$m[1]][$version] = $prettyVersion; + } } elseif ($message !== '') { $messages[] = $message; } @@ -267,7 +271,7 @@ class Problem return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); }); if (0 === count($filtered)) { - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').'); } } @@ -277,7 +281,7 @@ class Problem return $fixedConstraint->matches(new Constraint('==', $p->getVersion())); }); if (0 === count($filtered)) { - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.'); } } @@ -286,27 +290,27 @@ class Problem }); if (!$nonLockedPackages) { - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.'); } - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.'); } // check if the package is found when bypassing stability checks if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { - return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint); } - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'); } // check if the package is found when bypassing the constraint and stability checks if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { - return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); + return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint); } $suffix = ''; @@ -326,7 +330,7 @@ class Problem $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; } - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix); } if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) { @@ -356,15 +360,26 @@ class Problem * @internal * @param PackageInterface[] $packages * @param bool $isVerbose + * @param bool $useRemovedVersionGroup * @return string */ - public static function getPackageList(array $packages, $isVerbose) + public static function getPackageList(array $packages, $isVerbose, Pool $pool = null, ConstraintInterface $constraint = null, $useRemovedVersionGroup = false) { $prepared = array(); $hasDefaultBranch = array(); foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); + if ($pool && $constraint) { + foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } + if ($pool && $useRemovedVersionGroup) { + foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) { + $prepared[$package->getName()]['versions'][$version] = $prettyVersion; + } + } if ($package->isDefaultBranch()) { $hasDefaultBranch[$package->getName()] = true; } @@ -469,7 +484,7 @@ class Problem * @param string $reason * @return array{0: string, 1: string} */ - private static function computeCheckForLowerPrioRepo($isVerbose, $packageName, array $higherRepoPackages, array $allReposPackages, $reason, ConstraintInterface $constraint = null) + private static function computeCheckForLowerPrioRepo(Pool $pool, $isVerbose, $packageName, array $higherRepoPackages, array $allReposPackages, $reason, ConstraintInterface $constraint = null) { $nextRepoPackages = array(); $nextRepo = null; @@ -488,7 +503,7 @@ class Problem if ($topPackage instanceof RootPackageInterface) { return array( "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', - 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', + 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.', ); } } @@ -497,10 +512,10 @@ class Problem $singular = count($higherRepoPackages) === 1; return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', - 'found '.self::getPackageList($nextRepoPackages, $isVerbose).' in the lock file and '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.', ); + 'found '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' in the lock file and '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.', ); } - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.'); } /** diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index bafd57994..8f3cf4abc 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -228,6 +228,41 @@ abstract class Rule return false; } + /** + * @internal + * @return BasePackage + */ + public function getSourcePackage(Pool $pool) + { + $literals = $this->getLiterals(); + + switch ($this->getReason()) { + case self::RULE_PACKAGE_CONFLICT: + $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); + + $conflictTarget = $package1->getPrettyString(); + if ($reasonData = $this->getReasonData()) { + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + list($package2, $package1) = array($package1, $package2); + } + } + + return $package2; + + case self::RULE_PACKAGE_REQUIRES: + $sourceLiteral = array_shift($literals); + $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral)); + + return $sourcePackage; + + default: + throw new \LogicException('Not implemented'); + } + } + + /** * @param bool $isVerbose * @param BasePackage[] $installedMap @@ -258,7 +293,7 @@ abstract class Rule } } - return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose).'.'; + return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.'; case self::RULE_FIXED: $package = $this->deduplicateDefaultBranchAlias($this->reasonData['package']); @@ -320,7 +355,7 @@ abstract class Rule $text = $reasonData->getPrettyString($sourcePackage); if ($requires) { - $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose) . '.'; + $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $this->reasonData->getConstraint()) . '.'; } else { $targetName = $reasonData->getTarget(); @@ -368,13 +403,13 @@ abstract class Rule } if ($installedPackages && $removablePackages) { - return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose).'. '.$reason; + return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason; } - return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose).'. '.$reason; + return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason; } - return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose) . '.'; + return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.'; case self::RULE_LEARNED: /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */ // if (isset($learnedPool[$this->reasonData])) { @@ -445,9 +480,10 @@ abstract class Rule /** * @param array $packages An array containing packages or literals * @param bool $isVerbose + * @param bool $useRemovedVersionGroup * @return string */ - protected function formatPackagesUnique(Pool $pool, array $packages, $isVerbose) + protected function formatPackagesUnique(Pool $pool, array $packages, $isVerbose, ConstraintInterface $constraint = null, $useRemovedVersionGroup = false) { foreach ($packages as $index => $package) { if (!\is_object($package)) { @@ -455,7 +491,7 @@ abstract class Rule } } - return Problem::getPackageList($packages, $isVerbose); + return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup); } /** diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index a2f976ec6..ebdc30caa 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -460,6 +460,8 @@ class Installer $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); + $pool = null; + if (!$lockTransaction->getOperations()) { $this->io->writeError('Nothing to modify in lock file'); } diff --git a/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test b/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test index 778abe1f6..02155ef52 100644 --- a/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test +++ b/tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test @@ -11,15 +11,15 @@ Test the error output of solver problems is deduplicated. { "name": "package/a", "version": "2.0.2", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.0.3", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.1.0", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.2.0", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.0" } }, - { "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.0" } }, + { "name": "package/a", "version": "2.2.0", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.1" } }, + { "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.1" } }, { "name": "missing/dep", "version": "2.0.0" } ] } @@ -41,17 +41,9 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - package/a[2.0.0, ..., 2.6.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint. + - package/a[2.2.0, ..., 2.6.0] require missing/dep ^1.1 -> found missing/dep[2.0.0] but it does not match the constraint. + - package/a[2.0.0, ..., 2.1.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint. - Root composer.json requires package/a * -> satisfiable by package/a[2.0.0, ..., 2.6.0]. ---EXPECT-OUTPUT-OPTIMIZED-- -Loading composer repositories with package information -Updating dependencies -Your requirements could not be resolved to an installable set of packages. - - Problem 1 - - Root composer.json requires package/a * -> satisfiable by package/a[2.6.0]. - - package/a 2.6.0 requires missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint. - --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-7051.test b/tests/Composer/Test/Fixtures/installer/github-issues-7051.test index 51cd96f40..bfa7c9440 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-7051.test +++ b/tests/Composer/Test/Fixtures/installer/github-issues-7051.test @@ -137,11 +137,11 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0]. - illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26]. - - illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.10]. - - illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.8]. - - You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.8, v3.1.10, v3.4.29]. - - friendsofphp/php-cs-fixer v2.10.5 requires symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.4.29]. - - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.5]. + - illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10]. + - illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.7, v2.8.8]. + - You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29]. + - friendsofphp/php-cs-fixer[v2.10.4, ..., v2.10.5] require symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29]. + - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5]. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test b/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test index 25fab5ee3..29f0ebe86 100644 --- a/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test +++ b/tests/Composer/Test/Fixtures/installer/provider-conflicts3.test @@ -50,8 +50,8 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - Only one of these can be installed: regular/pkg[1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. - - Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.3]. + - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it. + - Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3]. - Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. --EXPECT--