Эх сурвалжийг харах

Merge pull request #5120 from Seldaek/improve-errors

Improve solver error reporting
Jordi Boggiano 9 жил өмнө
parent
commit
870dcece1f

+ 13 - 8
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;

+ 7 - 3
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.';
             }
         }
 

+ 4 - 0
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.';
                     }
                 }

+ 20 - 4
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;
     }
 
     /**

+ 52 - 0
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 <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.
+
+Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
+
+--EXPECT--
+