1
0
Fork 0

List identical/compatible removed versions in problem output as if they had not been removed

pull/9261/head
Jordi Boggiano 2021-11-09 17:45:12 +01:00
parent 34183f49f9
commit ce2a40b259
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
8 changed files with 204 additions and 77 deletions

View File

@ -36,16 +36,57 @@ class Pool implements \Countable
protected $providerCache = array(); protected $providerCache = array();
/** @var BasePackage[] */ /** @var BasePackage[] */
protected $unacceptableFixedOrLockedPackages; protected $unacceptableFixedOrLockedPackages;
/** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
protected $removedVersions = array();
/** @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version */
protected $removedVersionsByPackage = array();
/** /**
* @param BasePackage[] $packages * @param BasePackage[] $packages
* @param BasePackage[] $unacceptableFixedOrLockedPackages * @param BasePackage[] $unacceptableFixedOrLockedPackages
* @param array<string, array<string, string>> $removedVersions
* @param array<string, array<string, string>> $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->versionParser = new VersionParser;
$this->setPackages($packages); $this->setPackages($packages);
$this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages; $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages;
$this->removedVersions = $removedVersions;
$this->removedVersionsByPackage = $removedVersionsByPackage;
}
/**
* @param string $name
* @return array<string, string>
*/
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<string, string>
*/
public function getRemovedVersionsByPackage($objectHash)
{
if (!isset($this->removedVersionsByPackage[$objectHash])) {
return array();
}
return $this->removedVersionsByPackage[$objectHash];
} }
/** /**

View File

@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\AliasPackage; use Composer\Package\AliasPackage;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\CompilingMatcher; use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
@ -57,6 +58,11 @@ class PoolOptimizer
*/ */
private $aliasesPerPackage = array(); private $aliasesPerPackage = array();
/**
* @var array<string, array<string, string>>
*/
private $removedVersionsByPackage = array();
public function __construct(PolicyInterface $policy) public function __construct(PolicyInterface $policy)
{ {
$this->policy = $policy; $this->policy = $policy;
@ -81,6 +87,7 @@ class PoolOptimizer
$this->conflictConstraintsPerPackage = array(); $this->conflictConstraintsPerPackage = array();
$this->packagesToRemove = array(); $this->packagesToRemove = array();
$this->aliasesPerPackage = array(); $this->aliasesPerPackage = array();
$this->removedVersionsByPackage = array();
return $optimizedPool; return $optimizedPool;
} }
@ -165,13 +172,16 @@ class PoolOptimizer
private function applyRemovalsToPool(Pool $pool) private function applyRemovalsToPool(Pool $pool)
{ {
$packages = array(); $packages = array();
$removedVersions = array();
foreach ($pool->getPackages() as $package) { foreach ($pool->getPackages() as $package) {
if (!isset($this->packagesToRemove[$package->id])) { if (!isset($this->packagesToRemove[$package->id])) {
$packages[] = $package; $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 // Reset package removals
$this->packagesToRemove = array(); $this->packagesToRemove = array();
@ -184,8 +194,8 @@ class PoolOptimizer
*/ */
private function optimizeByIdenticalDependencies(Pool $pool) private function optimizeByIdenticalDependencies(Pool $pool)
{ {
$identicalDefinitionPerPackage = array(); $identicalDefinitionsPerPackage = array();
$packageIdsToRemove = array(); $packageIdenticalDefinitionLookup = array();
foreach ($pool->getPackages() as $package) { foreach ($pool->getPackages() as $package) {
@ -195,7 +205,7 @@ class PoolOptimizer
continue; continue;
} }
$packageIdsToRemove[$package->id] = true; $this->markPackageForRemoval($package->id);
$dependencyHash = $this->calculateDependencyHash($package); $dependencyHash = $this->calculateDependencyHash($package);
@ -206,7 +216,6 @@ class PoolOptimizer
} }
foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) { foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) {
$groupHashParts = array(); $groupHashParts = array();
if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) { if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) {
@ -234,32 +243,19 @@ class PoolOptimizer
continue; 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) { foreach ($identicalDefinitionsPerPackage as $constraintGroups) {
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 ($constraintGroups as $constraintGroup) { 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 // Only one package in this constraint group has the same requirements, we're not allowed to remove that package
if (1 === \count($packages)) { if (1 === \count($packages)) {
$keepPackage($packages[0], $this->aliasesPerPackage); $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
continue; continue;
} }
@ -272,17 +268,12 @@ class PoolOptimizer
} }
foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) { 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); return $this->applyRemovalsToPool($pool);
} }
@ -342,4 +333,54 @@ class PoolOptimizer
$this->packagesToRemove[$id] = true; $this->packagesToRemove[$id] = true;
} }
/**
* @param array<string, array<string, array<string, list<BasePackage>>>> $identicalDefinitionsPerPackage
* @param array<int, array<string, array{groupHash: string, dependencyHash: string}>> $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();
}
}
}
}
}
}
} }

View File

@ -126,6 +126,10 @@ class Problem
$template = preg_replace('{^\S+ \S+ }', '%s%s ', $message); $template = preg_replace('{^\S+ \S+ }', '%s%s ', $message);
$messages[] = $template; $messages[] = $template;
$templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2]; $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 !== '') { } elseif ($message !== '') {
$messages[] = $message; $messages[] = $message;
} }
@ -267,7 +271,7 @@ class Problem
return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion())); return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion()));
}); });
if (0 === count($filtered)) { 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())); return $fixedConstraint->matches(new Constraint('==', $p->getVersion()));
}); });
if (0 === count($filtered)) { 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) { 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 // check if the package is found when bypassing stability checks
if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { 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 // 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)) { 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 // check if the package is found when bypassing the constraint and stability checks
if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { 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 // 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)) { 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 = ''; $suffix = '';
@ -326,7 +330,7 @@ class Problem
$suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.'; $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)) { if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
@ -356,15 +360,26 @@ class Problem
* @internal * @internal
* @param PackageInterface[] $packages * @param PackageInterface[] $packages
* @param bool $isVerbose * @param bool $isVerbose
* @param bool $useRemovedVersionGroup
* @return string * @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(); $prepared = array();
$hasDefaultBranch = array(); $hasDefaultBranch = array();
foreach ($packages as $package) { foreach ($packages as $package) {
$prepared[$package->getName()]['name'] = $package->getPrettyName(); $prepared[$package->getName()]['name'] = $package->getPrettyName();
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); $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()) { if ($package->isDefaultBranch()) {
$hasDefaultBranch[$package->getName()] = true; $hasDefaultBranch[$package->getName()] = true;
} }
@ -469,7 +484,7 @@ class Problem
* @param string $reason * @param string $reason
* @return array{0: string, 1: string} * @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(); $nextRepoPackages = array();
$nextRepo = null; $nextRepo = null;
@ -488,7 +503,7 @@ class Problem
if ($topPackage instanceof RootPackageInterface) { if ($topPackage instanceof RootPackageInterface) {
return array( return array(
"- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ', "- 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; $singular = count($higherRepoPackages) === 1;
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 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.');
} }
/** /**

View File

@ -228,6 +228,41 @@ abstract class Rule
return false; 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 bool $isVerbose
* @param BasePackage[] $installedMap * @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: case self::RULE_FIXED:
$package = $this->deduplicateDefaultBranchAlias($this->reasonData['package']); $package = $this->deduplicateDefaultBranchAlias($this->reasonData['package']);
@ -320,7 +355,7 @@ abstract class Rule
$text = $reasonData->getPrettyString($sourcePackage); $text = $reasonData->getPrettyString($sourcePackage);
if ($requires) { if ($requires) {
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose) . '.'; $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $this->reasonData->getConstraint()) . '.';
} else { } else {
$targetName = $reasonData->getTarget(); $targetName = $reasonData->getTarget();
@ -368,13 +403,13 @@ abstract class Rule
} }
if ($installedPackages && $removablePackages) { 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: 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 */ /** @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])) { // if (isset($learnedPool[$this->reasonData])) {
@ -445,9 +480,10 @@ abstract class Rule
/** /**
* @param array<int|BasePackage> $packages An array containing packages or literals * @param array<int|BasePackage> $packages An array containing packages or literals
* @param bool $isVerbose * @param bool $isVerbose
* @param bool $useRemovedVersionGroup
* @return string * @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) { foreach ($packages as $index => $package) {
if (!\is_object($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);
} }
/** /**

View File

@ -460,6 +460,8 @@ class Installer
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); $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); $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
$pool = null;
if (!$lockTransaction->getOperations()) { if (!$lockTransaction->getOperations()) {
$this->io->writeError('Nothing to modify in lock file'); $this->io->writeError('Nothing to modify in lock file');
} }

View File

@ -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.2", "require": { "missing/dep": "^1.0" } },
{ "name": "package/a", "version": "2.0.3", "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.1.0", "require": { "missing/dep": "^1.0" } },
{ "name": "package/a", "version": "2.2.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.0" } }, { "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.1" } },
{ "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.0" } }, { "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.1" } },
{ "name": "missing/dep", "version": "2.0.0" } { "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. Your requirements could not be resolved to an installable set of packages.
Problem 1 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]. - 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-- --EXPECT--

View File

@ -137,11 +137,11 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1 Problem 1
- Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0]. - 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/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.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.8]. - 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.8, v3.1.10, v3.4.29]. - 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.5 requires symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[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.5]. - Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5].
--EXPECT-- --EXPECT--

View File

@ -50,8 +50,8 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages. Your requirements could not be resolved to an installable set of packages.
Problem 1 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. - 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.3]. - 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]. - Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
--EXPECT-- --EXPECT--