From 623c0dcda7a29a06d21e9736527b05ca436d0f4b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 27 Mar 2016 18:39:36 +0100 Subject: [PATCH] Improve solver error reporting, fixes #5086, fixes #2575, fixes #2661 --- src/Composer/DependencyResolver/Pool.php | 21 +++++++++------- src/Composer/DependencyResolver/Problem.php | 10 +++++--- src/Composer/DependencyResolver/Rule.php | 4 ++++ .../Repository/ComposerRepository.php | 24 +++++++++++++++---- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index a185de66d..3dc6d90be 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -176,27 +176,32 @@ class Pool implements \Countable * packages must match or null to return all * @param bool $mustMatchName Whether the name of returned packages * must match the given name + * @param bool $bypassFilters If enabled, filterRequires and stability matching is ignored * @return PackageInterface[] A set of packages */ - public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false) + public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false, $bypassFilters = false) { + if ($bypassFilters) { + return $this->computeWhatProvides($name, $constraint, $mustMatchName, true); + } + $key = ((int) $mustMatchName).$constraint; if (isset($this->providerCache[$name][$key])) { return $this->providerCache[$name][$key]; } - return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName); + return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName, $bypassFilters); } /** * @see whatProvides */ - private function computeWhatProvides($name, $constraint, $mustMatchName = false) + private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false) { $candidates = array(); foreach ($this->providerRepos as $repo) { - foreach ($repo->whatProvides($this, $name) as $candidate) { + foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) { $candidates[] = $candidate; if ($candidate->id < 1) { $candidate->setId($this->id++); @@ -228,13 +233,13 @@ class Pool implements \Countable $aliasOfCandidate = $candidate->getAliasOf(); } - if ($this->whitelist !== null && ( + if ($this->whitelist !== null && !$bypassFilters && ( (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) || ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id])) )) { continue; } - switch ($this->match($candidate, $name, $constraint)) { + switch ($this->match($candidate, $name, $constraint, $bypassFilters)) { case self::MATCH_NONE: break; @@ -317,14 +322,14 @@ class Pool implements \Countable * @param ConstraintInterface $constraint The constraint to verify * @return int One of the MATCH* constants of this class or 0 if there is no match */ - private function match($candidate, $name, ConstraintInterface $constraint = null) + private function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters) { $candidateName = $candidate->getName(); $candidateVersion = $candidate->getVersion(); $isDev = $candidate->getStability() === 'dev'; $isAlias = $candidate instanceof AliasPackage; - if (!$isDev && !$isAlias && isset($this->filterRequires[$name])) { + if (!$bypassFilters && !$isDev && !$isAlias && isset($this->filterRequires[$name])) { $requireFilter = $this->filterRequires[$name]; } else { $requireFilter = new EmptyConstraint; diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index c333b0bfe..474c07b43 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -129,11 +129,15 @@ class Problem return "\n - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; } - if (!$this->pool->whatProvides($job['packageName'])) { - return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; + if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) { + return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but those are rejected by your minimum-stability.'; } - return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' could not be found.'; + if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) { + return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but those are rejected by your constraint.'; + } + + return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; } } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index fbf2b92f8..1476ac5dd 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -230,6 +230,10 @@ class Rule return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.'; } else { + if ($providers = $pool->whatProvides($targetName, $this->reasonData->getConstraint(), true, true)) { + return $text . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $providers) .' but these conflict with your requirements or minimum-stability'; + } + return $text . ' -> no matching package found.'; } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index e71030738..035c2b0d7 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -268,9 +268,14 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - public function whatProvides(Pool $pool, $name) + /** + * @param Pool $pool + * @param string $name package name + * @param bool $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache + */ + public function whatProvides(Pool $pool, $name, $bypassFilters = false) { - if (isset($this->providers[$name])) { + if (isset($this->providers[$name]) && !$bypassFilters) { return $this->providers[$name]; } @@ -354,7 +359,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } } else { - if (!$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) { + if (!$bypassFilters && !$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) { continue; } @@ -396,7 +401,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - return $this->providers[$name]; + $result = $this->providers[$name]; + + // clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache + // which is likely not an issue but might cause hard to track behaviors depending on how the repo is used + if ($bypassFilters) { + foreach ($this->providers[$name] as $uid => $provider) { + unset($this->providersByUid[$uid]); + } + unset($this->providers[$name]); + } + + return $result; } /**