Browse Source

Improve error reporting of solver issues, refs #7779

Fixes #8525
Fixes #6513
Jordi Boggiano 5 years ago
parent
commit
3fc7e10c5c
34 changed files with 482 additions and 223 deletions
  1. 1 1
      src/Composer/Command/BaseDependencyCommand.php
  2. 1 3
      src/Composer/DependencyResolver/PoolBuilder.php
  3. 159 82
      src/Composer/DependencyResolver/Problem.php
  4. 9 65
      src/Composer/DependencyResolver/Rule.php
  5. 5 3
      src/Composer/DependencyResolver/RuleSet.php
  6. 15 7
      src/Composer/DependencyResolver/Solver.php
  7. 6 5
      src/Composer/DependencyResolver/SolverProblemsException.php
  8. 6 6
      src/Composer/Installer.php
  9. 1 1
      src/Composer/Plugin/PluginManager.php
  10. 8 0
      src/Composer/Repository/ArrayRepository.php
  11. 5 0
      src/Composer/Repository/ArtifactRepository.php
  12. 5 0
      src/Composer/Repository/ComposerRepository.php
  13. 5 0
      src/Composer/Repository/CompositeRepository.php
  14. 4 0
      src/Composer/Repository/InstalledArrayRepository.php
  15. 4 0
      src/Composer/Repository/InstalledFilesystemRepository.php
  16. 4 0
      src/Composer/Repository/LockArrayRepository.php
  17. 5 0
      src/Composer/Repository/PackageRepository.php
  18. 5 0
      src/Composer/Repository/PathRepository.php
  19. 5 0
      src/Composer/Repository/PearRepository.php
  20. 7 2
      src/Composer/Repository/PlatformRepository.php
  21. 9 0
      src/Composer/Repository/RepositoryInterface.php
  22. 42 7
      src/Composer/Repository/RepositorySet.php
  23. 4 0
      src/Composer/Repository/RootPackageRepository.php
  24. 11 0
      src/Composer/Repository/VcsRepository.php
  25. 7 1
      tests/Composer/Test/DependencyResolver/RuleSetTest.php
  26. 7 1
      tests/Composer/Test/DependencyResolver/RuleTest.php
  27. 3 9
      tests/Composer/Test/DependencyResolver/SolverTest.php
  28. 1 1
      tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test
  29. 38 0
      tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test
  30. 1 1
      tests/Composer/Test/Fixtures/installer/github-issues-4319.test
  31. 2 10
      tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test
  32. 1 8
      tests/Composer/Test/Fixtures/installer/repositories-priorities.test
  33. 93 10
      tests/Composer/Test/Fixtures/installer/solver-problems.test
  34. 3 0
      tests/Composer/Test/InstallerTest.php

+ 1 - 1
src/Composer/Command/BaseDependencyCommand.php

@@ -89,7 +89,7 @@ class BaseDependencyCommand extends BaseCommand
         );
 
         // Find packages that are or provide the requested package first
-        $packages = $repositorySet->findPackages(strtolower($needle), null, false);
+        $packages = $repositorySet->findPackages(strtolower($needle), null, RepositorySet::ALLOW_PROVIDERS_REPLACERS);
         if (empty($packages)) {
             throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
         }

+ 1 - 3
src/Composer/DependencyResolver/PoolBuilder.php

@@ -31,7 +31,6 @@ class PoolBuilder
     private $stabilityFlags;
     private $rootAliases;
     private $rootReferences;
-    private $rootRequires;
 
     private $aliasMap = array();
     private $nameConstraints = array();
@@ -39,13 +38,12 @@ class PoolBuilder
     private $packages = array();
     private $unacceptableFixedPackages = array();
 
-    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $rootRequires = array())
+    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences)
     {
         $this->acceptableStabilities = $acceptableStabilities;
         $this->stabilityFlags = $stabilityFlags;
         $this->rootAliases = $rootAliases;
         $this->rootReferences = $rootReferences;
-        $this->rootRequires = $rootRequires;
     }
 
     public function buildPool(array $repositories, Request $request)

+ 159 - 82
src/Composer/DependencyResolver/Problem.php

@@ -13,6 +13,8 @@
 namespace Composer\DependencyResolver;
 
 use Composer\Package\CompletePackageInterface;
+use Composer\Repository\RepositorySet;
+use Composer\Semver\Constraint\Constraint;
 
 /**
  * Represents a problem detected while solving dependencies
@@ -35,13 +37,6 @@ class Problem
 
     protected $section = 0;
 
-    protected $pool;
-
-    public function __construct(Pool $pool)
-    {
-        $this->pool = $pool;
-    }
-
     /**
      * Add a rule as a reason
      *
@@ -68,7 +63,7 @@ class Problem
      * @param  array  $installedMap A map of all present packages
      * @return string
      */
-    public function getPrettyString(array $installedMap = array(), array $learnedPool = array())
+    public function getPrettyString(RepositorySet $repositorySet, Request $request, array $installedMap = array(), array $learnedPool = array())
     {
         // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections?
         $reasons = call_user_func_array('array_merge', array_reverse($this->reasons));
@@ -81,91 +76,25 @@ class Problem
                 throw new \LogicException("Single reason problems must contain a request rule.");
             }
 
-            $request = $rule->getReasonData();
-            $packageName = $request['packageName'];
-            $constraint = $request['constraint'];
+            $reasonData = $rule->getReasonData();
+            $packageName = $reasonData['packageName'];
+            $constraint = $reasonData['constraint'];
 
             if (isset($constraint)) {
-                $packages = $this->pool->whatProvides($packageName, $constraint);
+                $packages = $repositorySet->getPool()->whatProvides($packageName, $constraint);
             } else {
                 $packages = array();
             }
 
             if (empty($packages)) {
-                // handle php/hhvm
-                if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
-                    $version = phpversion();
-                    $available = $this->pool->whatProvides($packageName);
-
-                    if (count($available)) {
-                        $firstAvailable = reset($available);
-                        $version = $firstAvailable->getPrettyVersion();
-                        $extra = $firstAvailable->getExtra();
-                        if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
-                            $version .= '; ' . $firstAvailable->getDescription();
-                        }
-                    }
-
-                    $msg = "\n    - This package requires ".$packageName.$this->constraintToText($constraint).' but ';
-
-                    if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
-                        return $msg . 'your HHVM version does not satisfy that requirement.';
-                    }
-
-                    if ($packageName === 'hhvm') {
-                        return $msg . 'you are running this with PHP and not HHVM.';
-                    }
-
-                    return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.';
-                }
-
-                // handle php extensions
-                if (0 === stripos($packageName, 'ext-')) {
-                    if (false !== strpos($packageName, ' ')) {
-                        return "\n    - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
-                    }
-
-                    $ext = substr($packageName, 4);
-                    $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
-
-                    return "\n    - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
-                }
-
-                // handle linked libs
-                if (0 === stripos($packageName, 'lib-')) {
-                    if (strtolower($packageName) === 'lib-icu') {
-                        $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';
-
-                        return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
-                    }
-
-                    return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
-                }
-
-                if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
-                    $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
-
-                    return "\n    - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
-                }
-
-                // TODO: The pool doesn't know about these anymore, it has to ask the RepositorySet
-                /*if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) {
-                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
-                }*/
-
-                // TODO: The pool doesn't know about these anymore, it has to ask the RepositorySet
-                /*if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
-                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
-                }*/
-
-                return "\n    - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
+                return "\n    ".implode(self::getMissingPackageReason($repositorySet, $request, $packageName, $constraint));
             }
         }
 
         $messages = array();
 
         foreach ($reasons as $rule) {
-            $messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool);
+            $messages[] = $rule->getPrettyString($repositorySet, $request, $installedMap, $learnedPool);
         }
 
         return "\n    - ".implode("\n    - ", $messages);
@@ -193,7 +122,141 @@ class Problem
         $this->section++;
     }
 
-    protected function getPackageList($packages)
+    /**
+     * @internal
+     */
+    public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, $packageName, $constraint = null)
+    {
+        $pool = $repositorySet->getPool();
+
+        // handle php/hhvm
+        if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
+            $version = phpversion();
+            $available = $pool->whatProvides($packageName);
+
+            if (count($available)) {
+                $firstAvailable = reset($available);
+                $version = $firstAvailable->getPrettyVersion();
+                $extra = $firstAvailable->getExtra();
+                if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
+                    $version .= '; ' . str_replace('Package ', '', $firstAvailable->getDescription());
+                }
+            }
+
+            $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';
+
+            if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
+                return array($msg, 'your HHVM version does not satisfy that requirement.');
+            }
+
+            if ($packageName === 'hhvm') {
+                return array($msg, 'you are running this with PHP and not HHVM.');
+            }
+
+            return array($msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.');
+        }
+
+        // handle php extensions
+        if (0 === stripos($packageName, 'ext-')) {
+            if (false !== strpos($packageName, ' ')) {
+                return array('- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.');
+            }
+
+            $ext = substr($packageName, 4);
+            $error = extension_loaded($ext) ? 'it has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'it is missing from your system';
+
+            return array("- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ', $error.'. Install or enable PHP\'s '.$ext.' extension.');
+        }
+
+        // handle linked libs
+        if (0 === stripos($packageName, 'lib-')) {
+            if (strtolower($packageName) === 'lib-icu') {
+                $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';
+
+                return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error);
+            }
+
+            return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.');
+        }
+
+        $fixedPackage = null;
+        foreach ($request->getFixedPackages() as $package) {
+            if ($package->getName() === $packageName) {
+                $fixedPackage = $package;
+                if ($pool->isUnacceptableFixedPackage($package)) {
+                    return array("- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you whitelist it for update.');
+                }
+                break;
+            }
+        }
+
+        // first check if the actual requested package is found in normal conditions
+        // if so it must mean it is rejected by another constraint than the one given here
+        if ($packages = $repositorySet->findPackages($packageName, $constraint)) {
+            $rootReqs = $repositorySet->getRootRequires();
+            if (isset($rootReqs[$packageName])) {
+                $filtered = array_filter($packages, function ($p) use ($rootReqs, $packageName) {
+                    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).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').');
+                }
+            }
+
+            if ($fixedPackage) {
+                $fixedConstraint = new Constraint('==', $fixedPackage->getVersion());
+                $filtered = array_filter($packages, function ($p) use ($fixedConstraint) {
+                    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).' but the package is fixed to '.$fixedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you whitelist it for update.');
+                }
+            }
+
+            return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these 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)) {
+            return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.');
+        }
+
+        // check if the package is found when bypassing the constraint check
+        if ($packages = $repositorySet->findPackages($packageName, null)) {
+            // 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)) {
+                $higherRepoPackages = $repositorySet->findPackages($packageName, null);
+                $nextRepoPackages = array();
+                $nextRepo = null;
+
+                foreach ($allReposPackages as $package) {
+                    if ($nextRepo === null || $nextRepo === $package->getRepository()) {
+                        $nextRepoPackages[] = $package;
+                        $nextRepo = $package->getRepository();
+                    } else {
+                        break;
+                    }
+                }
+
+                return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable.');
+            }
+
+            return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your constraint.');
+        }
+
+        if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
+            $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);
+
+            return array("- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.');
+        }
+
+        return array("- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name.");
+    }
+
+    /**
+     * @internal
+     */
+    public static function getPackageList(array $packages)
     {
         $prepared = array();
         foreach ($packages as $package) {
@@ -207,13 +270,27 @@ class Problem
         return implode(', ', $prepared);
     }
 
+    private static function hasMultipleNames(array $packages)
+    {
+        $name = null;
+        foreach ($packages as $package) {
+            if ($name === null || $name === $package->getName()) {
+                $name = $package->getName();
+            } else {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Turns a constraint into text usable in a sentence describing a request
      *
      * @param  \Composer\Semver\Constraint\ConstraintInterface $constraint
      * @return string
      */
-    protected function constraintToText($constraint)
+    protected static function constraintToText($constraint)
     {
         return $constraint ? ' '.$constraint->getPrettyString() : '';
     }

+ 9 - 65
src/Composer/DependencyResolver/Rule.php

@@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
 use Composer\Package\CompletePackage;
 use Composer\Package\Link;
 use Composer\Package\PackageInterface;
+use Composer\Repository\RepositorySet;
 
 /**
  * @author Nils Adermann <naderman@naderman.de>
@@ -122,8 +123,9 @@ abstract class Rule
 
     abstract public function isAssertion();
 
-    public function getPrettyString(Pool $pool, array $installedMap = array(), array $learnedPool = array())
+    public function getPrettyString(RepositorySet $repositorySet, Request $request, array $installedMap = array(), array $learnedPool = array())
     {
+        $pool = $repositorySet->getPool();
         $literals = $this->getLiterals();
 
         $ruleText = '';
@@ -178,60 +180,9 @@ abstract class Rule
                 } else {
                     $targetName = $this->reasonData->getTarget();
 
-                    if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') {
-                        // handle php/hhvm
-                        if (defined('HHVM_VERSION')) {
-                            return $text . ' -> your HHVM version does not satisfy that requirement.';
-                        }
+                    $reason = Problem::getMissingPackageReason($repositorySet, $request, $targetName, $this->reasonData->getConstraint());
 
-                        $packages = $pool->whatProvides($targetName);
-                        $package = count($packages) ? current($packages) : phpversion();
-
-                        if ($targetName === 'hhvm') {
-                            if ($package instanceof CompletePackage) {
-                                return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
-                            } else {
-                                return $text . ' -> you are running this with PHP and not HHVM.';
-                            }
-                        }
-
-
-                        if (!($package instanceof CompletePackage)) {
-                            return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
-                        }
-
-                        $extra = $package->getExtra();
-
-                        if (!empty($extra['config.platform'])) {
-                            $text .= ' -> your PHP version ('.phpversion().') overridden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
-                        } else {
-                            $text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
-                        }
-
-                        return $text;
-                    }
-
-                    if (0 === strpos($targetName, 'ext-')) {
-                        // handle php extensions
-                        $ext = substr($targetName, 4);
-                        $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';
-
-                        return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.';
-                    }
-
-                    if (0 === strpos($targetName, 'lib-')) {
-                        // handle linked libs
-                        $lib = substr($targetName, 4);
-
-                        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.';
-                    }
-
-                    // TODO: The pool doesn't know about these anymore, it has to ask the RepositorySet
-                    /*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.';
+                    return $text . ' -> ' . $reason[1];
                 }
 
                 return $text;
@@ -249,7 +200,7 @@ abstract class Rule
                 $learnedString = '(learned rule, ';
                 if (isset($learnedPool[$this->reasonData])) {
                     foreach ($learnedPool[$this->reasonData] as $learnedRule) {
-                        $learnedString .= $learnedRule->getPrettyString($pool, $installedMap, $learnedPool);
+                        $learnedString .= $learnedRule->getPrettyString($repositorySet, $request, $installedMap, $learnedPool);
                     }
                 } else {
                     $learnedString .= 'reasoning unavailable';
@@ -272,20 +223,13 @@ abstract class Rule
      */
     protected function formatPackagesUnique($pool, array $packages)
     {
-        // TODO this is essentially a duplicate of Problem: getPackageList, maintain in one place only?
-
         $prepared = array();
-        foreach ($packages as $package) {
+        foreach ($packages as $index => $package) {
             if (!is_object($package)) {
-                $package = $pool->literalToPackage($package);
+                $packages[$index] = $pool->literalToPackage($package);
             }
-            $prepared[$package->getName()]['name'] = $package->getPrettyName();
-            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
-        }
-        foreach ($prepared as $name => $package) {
-            $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
         }
 
-        return implode(', ', $prepared);
+        return Problem::getPackageList($packages);
     }
 }

+ 5 - 3
src/Composer/DependencyResolver/RuleSet.php

@@ -12,6 +12,8 @@
 
 namespace Composer\DependencyResolver;
 
+use Composer\Repository\RepositorySet;
+
 /**
  * @author Nils Adermann <naderman@naderman.de>
  */
@@ -155,13 +157,13 @@ class RuleSet implements \IteratorAggregate, \Countable
         return array_keys($types);
     }
 
-    public function getPrettyString(Pool $pool = null)
+    public function getPrettyString(RepositorySet $repositorySet = null, Request $request = null)
     {
         $string = "\n";
         foreach ($this->rules as $type => $rules) {
             $string .= str_pad(self::$types[$type], 8, ' ') . ": ";
             foreach ($rules as $rule) {
-                $string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
+                $string .= ($repositorySet && $request ? $rule->getPrettyString($repositorySet, $request) : $rule)."\n";
             }
             $string .= "\n\n";
         }
@@ -171,6 +173,6 @@ class RuleSet implements \IteratorAggregate, \Countable
 
     public function __toString()
     {
-        return $this->getPrettyString(null);
+        return $this->getPrettyString(null, null);
     }
 }

+ 15 - 7
src/Composer/DependencyResolver/Solver.php

@@ -29,7 +29,9 @@ class Solver
     /** @var PolicyInterface */
     protected $policy;
     /** @var Pool */
-    protected $pool = null;
+    protected $pool;
+    /** @var RepositorySet */
+    protected $repositorySet;
 
     /** @var RuleSet */
     protected $rules;
@@ -65,11 +67,12 @@ class Solver
      * @param Pool                $pool
      * @param IOInterface         $io
      */
-    public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io)
+    public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io, RepositorySet $repositorySet)
     {
         $this->io = $io;
         $this->policy = $policy;
         $this->pool = $pool;
+        $this->repositorySet = $repositorySet;
     }
 
     /**
@@ -85,6 +88,11 @@ class Solver
         return $this->pool;
     }
 
+    public function getRepositorySet()
+    {
+        return $this->repositorySet;
+    }
+
     // aka solver_makeruledecisions
 
     private function makeAssertionRuleDecisions()
@@ -120,7 +128,7 @@ class Solver
             $conflict = $this->decisions->decisionRule($literal);
 
             if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
-                $problem = new Problem($this->pool);
+                $problem = new Problem();
 
                 $problem->addRule($rule);
                 $problem->addRule($conflict);
@@ -130,7 +138,7 @@ class Solver
             }
 
             // conflict with another root require/fixed package
-            $problem = new Problem($this->pool);
+            $problem = new Problem();
             $problem->addRule($rule);
             $problem->addRule($conflict);
 
@@ -177,7 +185,7 @@ class Solver
             }
 
             if (!$this->pool->whatProvides($packageName, $constraint)) {
-                $problem = new Problem($this->pool);
+                $problem = new Problem();
                 $problem->addRule(new GenericRule(array(), Rule::RULE_ROOT_REQUIRE, array('packageName' => $packageName, 'constraint' => $constraint)));
                 $this->problems[] = $problem;
             }
@@ -214,7 +222,7 @@ class Solver
         $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
 
         if ($this->problems) {
-            throw new SolverProblemsException($this->problems, $request->getPresentMap(true), $this->learnedPool);
+            throw new SolverProblemsException($this->problems, $this->repositorySet, $request, $this->learnedPool);
         }
 
         return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
@@ -513,7 +521,7 @@ class Solver
      */
     private function analyzeUnsolvable(Rule $conflictRule)
     {
-        $problem = new Problem($this->pool);
+        $problem = new Problem();
         $problem->addRule($conflictRule);
 
         $this->analyzeUnsolvableRule($problem, $conflictRule);

+ 6 - 5
src/Composer/DependencyResolver/SolverProblemsException.php

@@ -13,6 +13,7 @@
 namespace Composer\DependencyResolver;
 
 use Composer\Util\IniHelper;
+use Composer\Repository\RepositorySet;
 
 /**
  * @author Nils Adermann <naderman@naderman.de>
@@ -23,21 +24,21 @@ class SolverProblemsException extends \RuntimeException
     protected $installedMap;
     protected $learnedPool;
 
-    public function __construct(array $problems, array $installedMap, array $learnedPool)
+    public function __construct(array $problems, RepositorySet $repositorySet, Request $request, array $learnedPool)
     {
         $this->problems = $problems;
-        $this->installedMap = $installedMap;
+        $this->installedMap = $request->getPresentMap(true);
         $this->learnedPool = $learnedPool;
 
-        parent::__construct($this->createMessage(), 2);
+        parent::__construct($this->createMessage($repositorySet, $request), 2);
     }
 
-    protected function createMessage()
+    protected function createMessage(RepositorySet $repositorySet, Request $request)
     {
         $text = "\n";
         $hasExtensionProblems = false;
         foreach ($this->problems as $i => $problem) {
-            $text .= "  Problem ".($i + 1).$problem->getPrettyString($this->installedMap, $this->learnedPool)."\n";
+            $text .= "  Problem ".($i + 1).$problem->getPrettyString($repositorySet, $request, $this->installedMap, $this->learnedPool)."\n";
 
             if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
                 $hasExtensionProblems = true;

+ 6 - 6
src/Composer/Installer.php

@@ -389,7 +389,7 @@ class Installer
         $pool = $repositorySet->createPool($request);
 
         // solve dependencies
-        $solver = new Solver($policy, $pool, $this->io);
+        $solver = new Solver($policy, $pool, $this->io, $repositorySet);
         try {
             $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
             $ruleSetSize = $solver->getRuleSetSize();
@@ -529,7 +529,7 @@ class Installer
         $pool = $repositorySet->createPool($request);
 
         //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request);
-        $solver = new Solver($policy, $pool, $this->io);
+        $solver = new Solver($policy, $pool, $this->io, $repositorySet);
         try {
             $nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
             //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);
@@ -589,7 +589,7 @@ class Installer
             $pool = $repositorySet->createPool($request);
 
             // solve dependencies
-            $solver = new Solver($policy, $pool, $this->io);
+            $solver = new Solver($policy, $pool, $this->io, $repositorySet);
             try {
                 $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
                 $solver = null;
@@ -884,7 +884,7 @@ class Installer
             $packageQueue = new \SplQueue;
             $nameMatchesRequiredPackage = false;
 
-            $depPackages = $repositorySet->findPackages($packageName, null, false);
+            $depPackages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_PROVIDERS_REPLACERS);
             $matchesByPattern = array();
 
             // check if the name is a glob pattern that did not match directly
@@ -892,7 +892,7 @@ class Installer
                 // add any installed package matching the whitelisted name/pattern
                 $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$');
                 foreach ($lockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) {
-                    $matchesByPattern[] = $repositorySet->findPackages($installedPackage['name'], null, false);
+                    $matchesByPattern[] = $repositorySet->findPackages($installedPackage['name'], null, RepositorySet::ALLOW_PROVIDERS_REPLACERS);
                 }
 
                 // add root requirements which match the whitelisted name/pattern
@@ -933,7 +933,7 @@ class Installer
                 $requires = $package->getRequires();
 
                 foreach ($requires as $require) {
-                    $requirePackages = $repositorySet->findPackages($require->getTarget(), null, false);
+                    $requirePackages = $repositorySet->findPackages($require->getTarget(), null, RepositorySet::ALLOW_PROVIDERS_REPLACERS);
 
                     foreach ($requirePackages as $requirePackage) {
                         if (isset($this->updateWhitelist[$requirePackage->getName()])) {

+ 1 - 1
src/Composer/Plugin/PluginManager.php

@@ -412,7 +412,7 @@ class PluginManager
      */
     private function lookupInstalledPackage(RepositorySet $repositorySet, Link $link)
     {
-        $packages = $repositorySet->findPackages($link->getTarget(), $link->getConstraint(), false);
+        $packages = $repositorySet->findPackages($link->getTarget(), $link->getConstraint(), RepositorySet::ALLOW_PROVIDERS_REPLACERS | RepositorySet::ALLOW_SHADOWED_REPOSITORIES);
 
         return !empty($packages) ? $packages[0] : null;
     }

+ 8 - 0
src/Composer/Repository/ArrayRepository.php

@@ -42,6 +42,11 @@ class ArrayRepository extends BaseRepository
         }
     }
 
+    public function getRepoName()
+    {
+        return 'array repo (defining '.count($this->packages).' package'.(count($this->packages) > 1 ? 's' : '').')';
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -57,7 +62,9 @@ class ArrayRepository extends BaseRepository
                     (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion())))
                     && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability())
                 ) {
+                    // add selected packages which match stability requirements
                     $result[spl_object_hash($package)] = $package;
+                    // add the aliased package for packages where the alias matches
                     if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
                         $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                     }
@@ -67,6 +74,7 @@ class ArrayRepository extends BaseRepository
             }
         }
 
+        // add aliases of packages that were selected, even if the aliases did not match
         foreach ($packages as $package) {
             if ($package instanceof AliasPackage) {
                 if (isset($result[spl_object_hash($package->getAliasOf())])) {

+ 5 - 0
src/Composer/Repository/ArtifactRepository.php

@@ -43,6 +43,11 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
         $this->repoConfig = $repoConfig;
     }
 
+    public function getRepoName()
+    {
+        return 'platform repo ('.$this->lookup.')';
+    }
+
     public function getRepoConfig()
     {
         return $this->repoConfig;

+ 5 - 0
src/Composer/Repository/ComposerRepository.php

@@ -125,6 +125,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
         $this->loop = new Loop($this->httpDownloader);
     }
 
+    public function getRepoName()
+    {
+        return 'composer repo ('.$this->url.')';
+    }
+
     public function getRepoConfig()
     {
         return $this->repoConfig;

+ 5 - 0
src/Composer/Repository/CompositeRepository.php

@@ -39,6 +39,11 @@ class CompositeRepository extends BaseRepository
         }
     }
 
+    public function getRepoName()
+    {
+        return 'composite repo ('.count($this->repositories).' repos)';
+    }
+
     /**
      * Returns all the wrapped repositories
      *

+ 4 - 0
src/Composer/Repository/InstalledArrayRepository.php

@@ -21,4 +21,8 @@ namespace Composer\Repository;
  */
 class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
 {
+    public function getRepoName()
+    {
+        return 'installed '.parent::getRepoName();
+    }
 }

+ 4 - 0
src/Composer/Repository/InstalledFilesystemRepository.php

@@ -19,4 +19,8 @@ namespace Composer\Repository;
  */
 class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
 {
+    public function getRepoName()
+    {
+        return 'installed '.parent::getRepoName();
+    }
 }

+ 4 - 0
src/Composer/Repository/LockArrayRepository.php

@@ -21,5 +21,9 @@ namespace Composer\Repository;
  */
 class LockArrayRepository extends ArrayRepository implements RepositoryInterface
 {
+    public function getRepoName()
+    {
+        return 'lock '.parent::getRepoName();
+    }
 }
 

+ 5 - 0
src/Composer/Repository/PackageRepository.php

@@ -58,4 +58,9 @@ class PackageRepository extends ArrayRepository
             $this->addPackage($package);
         }
     }
+
+    public function getRepoName()
+    {
+        return preg_replace('{^array }', 'package ', parent::getRepoName());
+    }
 }

+ 5 - 0
src/Composer/Repository/PathRepository.php

@@ -111,6 +111,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
         parent::__construct();
     }
 
+    public function getRepoName()
+    {
+        return 'path repo ('.$this->repoConfig['url'].')';
+    }
+
     public function getRepoConfig()
     {
         return $this->repoConfig;

+ 5 - 0
src/Composer/Repository/PearRepository.php

@@ -67,6 +67,11 @@ class PearRepository extends ArrayRepository implements ConfigurableRepositoryIn
         $this->repoConfig = $repoConfig;
     }
 
+    public function getRepoName()
+    {
+        return 'pear repo ('.$this->url.')';
+    }
+
     public function getRepoConfig()
     {
         return $this->repoConfig;

+ 7 - 2
src/Composer/Repository/PlatformRepository.php

@@ -51,6 +51,11 @@ class PlatformRepository extends ArrayRepository
         parent::__construct($packages);
     }
 
+    public function getRepoName()
+    {
+        return 'platform repo';
+    }
+
     protected function initialize()
     {
         parent::initialize();
@@ -275,7 +280,7 @@ class PlatformRepository extends ArrayRepository
             } else {
                 $actualText = 'actual: '.$package->getPrettyVersion();
             }
-            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');
+            $overrider->setDescription($overrider->getDescription().', '.$actualText);
 
             return;
         }
@@ -288,7 +293,7 @@ class PlatformRepository extends ArrayRepository
             } else {
                 $actualText = 'actual: '.$package->getPrettyVersion();
             }
-            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');
+            $overrider->setDescription($overrider->getDescription().', '.$actualText);
 
             return;
         }

+ 9 - 0
src/Composer/Repository/RepositoryInterface.php

@@ -83,4 +83,13 @@ interface RepositoryInterface extends \Countable
      * @return array[] an array of array('name' => '...', 'description' => '...')
      */
     public function search($query, $mode = 0, $type = null);
+
+    /**
+     * Returns a name representing this repository to the user
+     *
+     * This is best effort and definitely can not always be very precise
+     *
+     * @return string
+     */
+    public function getRepoName();
 }

+ 42 - 7
src/Composer/Repository/RepositorySet.php

@@ -29,6 +29,19 @@ use Composer\Package\Version\StabilityFilter;
  */
 class RepositorySet
 {
+    /**
+     * Packages which replace/provide the given name might be returned as well even if they do not match the name exactly
+     */
+    const ALLOW_PROVIDERS_REPLACERS = 1;
+    /**
+     * Packages are returned even though their stability does not match the required stability
+     */
+    const ALLOW_UNACCEPTABLE_STABILITIES = 2;
+    /**
+     * Packages will be looked up in all repositories, even after they have been found in a higher prio one
+     */
+    const ALLOW_SHADOWED_REPOSITORIES = 4;
+
     /** @var array */
     private $rootAliases;
     /** @var array */
@@ -39,7 +52,7 @@ class RepositorySet
 
     private $acceptableStabilities;
     private $stabilityFlags;
-    protected $rootRequires;
+    private $rootRequires;
 
     /** @var Pool */
     private $pool;
@@ -64,6 +77,11 @@ class RepositorySet
         }
     }
 
+    public function getRootRequires()
+    {
+        return $this->rootRequires;
+    }
+
     /**
      * Adds a repository to this repository set
      *
@@ -96,15 +114,32 @@ class RepositorySet
      *
      * @param string $name
      * @param ConstraintInterface|null $constraint
-     * @param bool $exactMatch if set to false, packages which replace/provide the given name might be returned as well even if they do not match the name exactly
-     * @param bool $ignoreStability if set to true, packages are returned even though their stability does not match the required stability
+     * @param int $flags any of the ALLOW_* constants from this class to tweak what is returned
      * @return array
      */
-    public function findPackages($name, ConstraintInterface $constraint = null, $exactMatch = true, $ignoreStability = false)
+    public function findPackages($name, ConstraintInterface $constraint = null, $flags = 0)
     {
+        $exactMatch = ($flags & self::ALLOW_PROVIDERS_REPLACERS) === 0;
+        $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0;
+        $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0;
+
         $packages = array();
-        foreach ($this->repositories as $repository) {
-            $packages[] = $repository->findPackages($name, $constraint) ?: array();
+        if ($loadFromAllRepos) {
+            foreach ($this->repositories as $repository) {
+                $packages[] = $repository->findPackages($name, $constraint) ?: array();
+            }
+        } else {
+            foreach ($this->repositories as $repository) {
+                $result = $repository->loadPackages(array($name => $constraint), $ignoreStability ? BasePackage::$stabilities : $this->acceptableStabilities, $ignoreStability ? array() : $this->stabilityFlags);
+
+                $packages[] = $result['packages'];
+                foreach ($result['namesFound'] as $nameFound) {
+                    // avoid loading the same package again from other repositories once it has been found
+                    if ($name === $nameFound) {
+                        break 2;
+                    }
+                }
+            }
         }
 
         $candidates = $packages ? call_user_func_array('array_merge', $packages) : array();
@@ -135,7 +170,7 @@ class RepositorySet
      */
     public function createPool(Request $request)
     {
-        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $this->rootRequires);
+        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences);
 
         foreach ($this->repositories as $repo) {
             if ($repo instanceof InstalledRepositoryInterface) {

+ 4 - 0
src/Composer/Repository/RootPackageRepository.php

@@ -21,4 +21,8 @@ namespace Composer\Repository;
  */
 class RootPackageRepository extends ArrayRepository
 {
+    public function getRepoName()
+    {
+        return 'root package repo';
+    }
 }

+ 11 - 0
src/Composer/Repository/VcsRepository.php

@@ -79,6 +79,17 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
         $this->processExecutor = new ProcessExecutor($io);
     }
 
+    public function getRepoName()
+    {
+        $driverClass = get_class($this->getDriver());
+        $driverType = array_search($driverClass, $this->drivers);
+        if (!$driverType) {
+            $driverType = $driverClass;
+        }
+
+        return 'vcs repo ('.$driverType.' '.$this->url.')';
+    }
+
     public function getRepoConfig()
     {
         return $this->repoConfig;

+ 7 - 1
tests/Composer/Test/DependencyResolver/RuleSetTest.php

@@ -143,12 +143,18 @@ class RuleSetTest extends TestCase
             $p = $this->getPackage('foo', '2.1'),
         ));
 
+        $repositorySetMock = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock();
+        $repositorySetMock->expects($this->any())
+            ->method('getPool')
+            ->willReturn($pool);
+        $requestMock = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock();
+
         $ruleSet = new RuleSet;
         $literal = $p->getId();
         $rule = new GenericRule(array($literal), Rule::RULE_ROOT_REQUIRE, array('packageName' => 'foo/bar', 'constraint' => null));
 
         $ruleSet->add($rule, RuleSet::TYPE_REQUEST);
 
-        $this->assertContains('REQUEST : No package found to satisfy root composer.json require foo/bar', $ruleSet->getPrettyString($pool));
+        $this->assertContains('REQUEST : No package found to satisfy root composer.json require foo/bar', $ruleSet->getPrettyString($repositorySetMock, $requestMock));
     }
 }

+ 7 - 1
tests/Composer/Test/DependencyResolver/RuleTest.php

@@ -99,8 +99,14 @@ class RuleTest extends TestCase
             $p2 = $this->getPackage('baz', '1.1'),
         ));
 
+        $repositorySetMock = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock();
+        $repositorySetMock->expects($this->any())
+            ->method('getPool')
+            ->willReturn($pool);
+        $requestMock = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock();
+
         $rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_PACKAGE_REQUIRES, new Link('baz', 'foo'));
 
-        $this->assertEquals('baz 1.1 relates to foo -> satisfiable by foo[2.1].', $rule->getPrettyString($pool));
+        $this->assertEquals('baz 1.1 relates to foo -> satisfiable by foo[2.1].', $rule->getPrettyString($repositorySetMock, $requestMock));
     }
 }

+ 3 - 9
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -82,7 +82,7 @@ class SolverTest extends TestCase
             $problems = $e->getProblems();
             $this->assertCount(1, $problems);
             $this->assertEquals(2, $e->getCode());
-            $this->assertEquals("\n    - The requested package b could not be found in any version, there may be a typo in the package name.", $problems[0]->getPrettyString());
+            $this->assertEquals("\n    - Root composer.json requires b, it could not be found in any version, there may be a typo in the package name.", $problems[0]->getPrettyString($this->repoSet, $this->request));
         }
     }
 
@@ -682,13 +682,7 @@ class SolverTest extends TestCase
             $msg = "\n";
             $msg .= "  Problem 1\n";
             $msg .= "    - Root composer.json requires a -> satisfiable by A[1.0].\n";
-            $msg .= "    - A 1.0 requires b >= 2.0 -> no matching package found.\n\n";
-            $msg .= "Potential causes:\n";
-            $msg .= " - A typo in the package name\n";
-            $msg .= " - The package is not available in a stable-enough version according to your minimum-stability setting\n";
-            $msg .= "   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n";
-            $msg .= " - It's a private package and you forgot to add a custom repository to find it\n\n";
-            $msg .= "Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
+            $msg .= "    - A 1.0 requires b >= 2.0 -> found B[1.0] but it does not match your constraint.\n";
             $this->assertEquals($msg, $e->getMessage());
         }
     }
@@ -895,7 +889,7 @@ class SolverTest extends TestCase
 
     protected function createSolver()
     {
-        $this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), new NullIO());
+        $this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), new NullIO(), $this->repoSet);
     }
 
     protected function checkSolverResult(array $expected)

+ 1 - 1
tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test

@@ -26,7 +26,7 @@ Updating dependencies
 Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
-    - c/c 1.0.0 requires x/x 1.0 -> no matching package found.
+    - c/c 1.0.0 requires x/x 1.0 -> could not be found in any version, there may be a typo in the package name.
     - b/b 1.0.0 requires c/c 1.* -> satisfiable by c/c[1.0.0].
     - Root composer.json requires b/b 1.* -> satisfiable by b/b[1.0.0].
 

+ 38 - 0
tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test

@@ -0,0 +1,38 @@
+--TEST--
+Test the error output of solver problems for conflicts between two dependents
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "conflicter/pkg", "version": "1.0.0", "conflict": { "victim/pkg": "1.0.0"} },
+                { "name": "victim/pkg", "version": "1.0.0" }
+            ]
+        }
+    ],
+    "require": {
+        "conflicter/pkg": "1.0.0",
+        "victim/pkg": "1.0.0"
+    }
+}
+
+
+--RUN--
+update
+
+--EXPECT-EXIT-CODE--
+2
+
+--EXPECT-OUTPUT--
+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 conflicter/pkg 1.0.0 -> satisfiable by conflicter/pkg[1.0.0].
+    - victim/pkg 1.0.0 conflicts with conflicter/pkg[1.0.0].
+    - Root composer.json requires victim/pkg 1.0.0 -> satisfiable by victim/pkg[1.0.0].
+
+--EXPECT--
+

+ 1 - 1
tests/Composer/Test/Fixtures/installer/github-issues-4319.test

@@ -37,7 +37,7 @@ Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
     - Root composer.json requires a/a ~1.0 -> satisfiable by a/a[1.0.0].
-    - a/a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement.
+    - a/a 1.0.0 requires php 5.5 -> your php version (5.3; overridden via config.platform, actual: %s) does not satisfy that requirement.
 
 --EXPECT--
 

+ 2 - 10
tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test

@@ -1,5 +1,5 @@
 --TEST--
-Partial update from lock file should apply lock file and downgrade unstable packages even if not whitelisted
+Partial update from lock file should apply lock file and if an unstable package is not allowed anymore by latest composer.json it should fail
 --COMPOSER--
 {
     "repositories": [
@@ -59,12 +59,4 @@ Updating dependencies
 Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
-    - The requested package b/unstable could not be found in any version, there may be a typo in the package name.
-
-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.
- - It's a private package and you forgot to add a custom repository to find it
-
-Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
+    - b/unstable is fixed to 1.1.0-alpha (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you whitelist it for update.

+ 1 - 8
tests/Composer/Test/Fixtures/installer/repositories-priorities.test

@@ -28,15 +28,8 @@ Updating dependencies
 Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
-    - The requested package foo/a could not be found in any version, there may be a typo in the package name.
+    - Root composer.json requires foo/a 2.*, it is satisfiable by foo/a[2.0.0] from package repo (defining 1 package) but foo/a[1.0.0] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable.
 
-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.
- - It's a private package and you forgot to add a custom repository to find it
-
-Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
 --EXPECT--
 --EXPECT-EXIT-CODE--
 2

+ 93 - 10
tests/Composer/Test/Fixtures/installer/solver-problems.test

@@ -6,34 +6,84 @@ Test the error output of solver problems.
         {
             "type": "package",
             "package": [
+                { "name": "package/found", "version": "2.0.0", "require": {
+                    "unstable/package2": "2.*"
+                } },
+                { "name": "package/found2", "version": "2.0.0", "require": {
+                    "invalid/💩package": "*"
+                } },
+                { "name": "package/found3", "version": "2.0.0", "require": {
+                    "unstable/package2": "2.*"
+                } },
+                { "name": "package/found4", "version": "2.0.0", "require": {
+                    "non-existent/pkg2": "1.*"
+                } },
+                { "name": "package/found5", "version": "2.0.0", "require": {
+                    "requirer/pkg": "1.*"
+                } },
+                { "name": "package/found6", "version": "2.0.0", "require": {
+                    "stable-requiree-excluded/pkg2": "1.0.1"
+                } },
+                { "name": "package/found7", "version": "2.0.0", "require": {
+                    "php-64bit": "1.0.1"
+                } },
+                { "name": "conflict/requirer", "version": "2.0.0", "require": {
+                    "conflict/dep": "1.0.0"
+                } },
+                { "name": "conflict/requirer2", "version": "2.0.0", "require": {
+                    "conflict/dep": "2.0.0"
+                } },
+                { "name": "conflict/dep", "version": "1.0.0" },
+                { "name": "conflict/dep", "version": "2.0.0" },
                 { "name": "unstable/package", "version": "2.0.0-alpha" },
                 { "name": "unstable/package", "version": "1.0.0" },
-                { "name": "requirer/pkg", "version": "1.0.0", "require": {"dependency/pkg": "1.0.0" } },
+                { "name": "unstable/package2", "version": "2.0.0-alpha" },
+                { "name": "unstable/package2", "version": "1.0.0" },
+                { "name": "requirer/pkg", "version": "1.0.0", "require": {
+                    "dependency/pkg": "1.0.0",
+                    "dependency/unstable-pkg": "1.0.0-dev"
+                } },
                 { "name": "dependency/pkg", "version": "2.0.0" },
                 { "name": "dependency/pkg", "version": "1.0.0" },
+                { "name": "dependency/unstable-pkg", "version": "1.0.0-dev" },
                 { "name": "stable-requiree-excluded/pkg", "version": "1.0.1" },
                 { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
             ]
         }
     ],
     "require": {
+        "package/found": "2.*",
+        "package/found2": "2.*",
+        "package/found3": "2.*",
+        "package/found4": "2.*",
+        "package/found5": "2.*",
+        "package/found6": "2.*",
+        "package/found7": "2.*",
+        "conflict/requirer": "2.*",
+        "conflict/requirer2": "2.*",
         "unstable/package": "2.*",
-        "bogus/pkg": "1.*",
+        "non-existent/pkg": "1.*",
         "requirer/pkg": "1.*",
         "dependency/pkg": "2.*",
-        "stable-requiree-excluded/pkg": "1.0.1"
+        "stable-requiree-excluded/pkg": "1.0.1",
+        "lib-xml": "1002.*",
+        "lib-icu": "1001.*",
+        "ext-xml": "1002.*",
+        "php": "1"
     }
 }
 
 --INSTALLED--
 [
-    { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
+    { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" },
+    { "name": "stable-requiree-excluded/pkg2", "version": "1.0.0" }
 ]
 
 --LOCK--
 {
     "packages": [
-        { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
+        { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" },
+        { "name": "stable-requiree-excluded/pkg2", "version": "1.0.0" }
     ],
     "packages-dev": [],
     "aliases": [],
@@ -46,7 +96,7 @@ Test the error output of solver problems.
 }
 
 --RUN--
-update unstable/package requirer/pkg dependency/pkg
+update unstable/package requirer/pkg dependency/pkg conflict/requirer
 
 --EXPECT-EXIT-CODE--
 2
@@ -57,14 +107,44 @@ Updating dependencies
 Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
-    - The requested package unstable/package could not be found in any version, there may be a typo in the package name.
+    - Root composer.json requires unstable/package 2.*, found unstable/package[2.0.0-alpha] but it does not match your minimum-stability.
   Problem 2
-    - The requested package bogus/pkg could not be found in any version, there may be a typo in the package name.
+    - Root composer.json requires non-existent/pkg, it could not be found in any version, there may be a typo in the package name.
   Problem 3
-    - The requested package stable-requiree-excluded/pkg could not be found in any version, there may be a typo in the package name.
+    - Root composer.json requires stable-requiree-excluded/pkg 1.0.1, found stable-requiree-excluded/pkg[1.0.1] but the package is fixed to 1.0.0 (lock file version) by a partial update and that version does not match. Make sure you whitelist it for update.
   Problem 4
+    - Root composer.json requires linked library lib-xml 1002.* but it has the wrong version installed or is missing from your system, make sure to load the extension providing it.
+  Problem 5
+    - Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension.
+  Problem 6
+    - Root composer.json requires PHP extension ext-xml 1002.* but it has the wrong version (%s) installed. Install or enable PHP's xml extension.
+  Problem 7
+    - Root composer.json requires php 1 but your php version (%s) does not satisfy that requirement.
+  Problem 8
+    - Root composer.json requires package/found 2.* -> satisfiable by package/found[2.0.0].
+    - package/found 2.0.0 requires unstable/package2 2.* -> found unstable/package2[2.0.0-alpha] but it does not match your minimum-stability.
+  Problem 9
+    - Root composer.json requires package/found2 2.* -> satisfiable by package/found2[2.0.0].
+    - package/found2 2.0.0 requires invalid/💩package * -> could not be found, it looks like its name is invalid, "💩" is not allowed in package names.
+  Problem 10
+    - Root composer.json requires package/found3 2.* -> satisfiable by package/found3[2.0.0].
+    - package/found3 2.0.0 requires unstable/package2 2.* -> found unstable/package2[2.0.0-alpha] but it does not match your minimum-stability.
+  Problem 11
+    - Root composer.json requires package/found4 2.* -> satisfiable by package/found4[2.0.0].
+    - package/found4 2.0.0 requires non-existent/pkg2 1.* -> could not be found in any version, there may be a typo in the package name.
+  Problem 12
+    - Root composer.json requires package/found6 2.* -> satisfiable by package/found6[2.0.0].
+    - package/found6 2.0.0 requires stable-requiree-excluded/pkg2 1.0.1 -> found stable-requiree-excluded/pkg2[1.0.0] but it does not match your constraint.
+  Problem 13
+    - Root composer.json requires package/found7 2.* -> satisfiable by package/found7[2.0.0].
+    - package/found7 2.0.0 requires php-64bit 1.0.1 -> your php-64bit version (%s) does not satisfy that requirement.
+  Problem 14
     - Root composer.json requires requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0].
-    - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> no matching package found.
+    - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> found dependency/pkg[1.0.0] but it conflicts with your root composer.json require (2.*).
+  Problem 15
+    - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> found dependency/pkg[1.0.0] but it conflicts with your root composer.json require (2.*).
+    - package/found5 2.0.0 requires requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0].
+    - Root composer.json requires package/found5 2.* -> satisfiable by package/found5[2.0.0].
 
 Potential causes:
  - A typo in the package name
@@ -73,6 +153,9 @@ Potential causes:
  - It's a private package and you forgot to add a custom repository to find it
 
 Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
+  To enable extensions, verify that they are enabled in your .ini files:
+__inilist__
+  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.
 
 --EXPECT--
 

+ 3 - 0
tests/Composer/Test/InstallerTest.php

@@ -323,6 +323,9 @@ class InstallerTest extends TestCase
         $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace()));
 
         if ($expectOutput) {
+            $output = preg_replace('{^    - .*?\.ini$}m', '__inilist__', $output);
+            $output = preg_replace('{(__inilist__\r?\n)+}', "__inilist__\n", $output);
+
             $this->assertStringMatchesFormat(rtrim($expectOutput), rtrim($output));
         }
     }