From 623c0dcda7a29a06d21e9736527b05ca436d0f4b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 27 Mar 2016 18:39:36 +0100 Subject: [PATCH 1/2] 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; } /** From 23af589076fcdf7ebc35526b1920f26312552b1c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 1 Apr 2016 00:37:06 +0100 Subject: [PATCH 2/2] Add test case for solver problem improvements --- .../Fixtures/installer/solver-problems.test | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/Composer/Test/Fixtures/installer/solver-problems.test diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test new file mode 100644 index 000000000..87c4fa7ee --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/solver-problems.test @@ -0,0 +1,52 @@ +--TEST-- +Test the error output of solver problems. +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "unstable/package", "version": "2.0.0-alpha" }, + { "name": "unstable/package", "version": "1.0.0" }, + { "name": "requirer", "version": "1.0.0", "require": {"dependency": "1.0.0" } }, + { "name": "dependency", "version": "2.0.0" }, + { "name": "dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "unstable/package": "2.*", + "bogus": "1.*", + "requirer": "1.*", + "dependency": "2.*" + } +} + +--RUN-- +install + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies (including require-dev) +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - The requested package unstable/package 2.* exists as unstable/package[1.0.0] but those are rejected by your constraint. + Problem 2 + - The requested package bogus could not be found in any version, there may be a typo in the package name. + Problem 3 + - Installation request for requirer 1.* -> satisfiable by requirer[1.0.0]. + - requirer 1.0.0 requires dependency 1.0.0 -> satisfiable by dependency[1.0.0] but these conflict with your requirements or minimum-stability + +Potential causes: + - A typo in the package name + - The package is not available in a stable-enough version according to your minimum-stability setting + see for more details. + +Read for further common problems. + +--EXPECT-- +