From 3fc7e10c5c0871d2fceb0de599d3e43a39f19158 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 29 Jan 2020 22:47:16 +0100 Subject: [PATCH] Improve error reporting of solver issues, refs #7779 Fixes #8525 Fixes #6513 --- .../Command/BaseDependencyCommand.php | 2 +- .../DependencyResolver/PoolBuilder.php | 4 +- src/Composer/DependencyResolver/Problem.php | 241 ++++++++++++------ src/Composer/DependencyResolver/Rule.php | 74 +----- src/Composer/DependencyResolver/RuleSet.php | 8 +- src/Composer/DependencyResolver/Solver.php | 22 +- .../SolverProblemsException.php | 11 +- src/Composer/Installer.php | 12 +- src/Composer/Plugin/PluginManager.php | 2 +- src/Composer/Repository/ArrayRepository.php | 8 + .../Repository/ArtifactRepository.php | 5 + .../Repository/ComposerRepository.php | 5 + .../Repository/CompositeRepository.php | 5 + .../Repository/InstalledArrayRepository.php | 4 + .../InstalledFilesystemRepository.php | 4 + .../Repository/LockArrayRepository.php | 4 + src/Composer/Repository/PackageRepository.php | 5 + src/Composer/Repository/PathRepository.php | 5 + src/Composer/Repository/PearRepository.php | 5 + .../Repository/PlatformRepository.php | 9 +- .../Repository/RepositoryInterface.php | 9 + src/Composer/Repository/RepositorySet.php | 49 +++- .../Repository/RootPackageRepository.php | 4 + src/Composer/Repository/VcsRepository.php | 11 + .../Test/DependencyResolver/RuleSetTest.php | 8 +- .../Test/DependencyResolver/RuleTest.php | 8 +- .../Test/DependencyResolver/SolverTest.php | 12 +- .../installer/broken-deps-do-not-replace.test | 2 +- .../conflict-between-dependents.test | 38 +++ .../installer/github-issues-4319.test | 2 +- ...e-downgrades-non-whitelisted-unstable.test | 12 +- .../installer/repositories-priorities.test | 9 +- .../Fixtures/installer/solver-problems.test | 103 +++++++- tests/Composer/Test/InstallerTest.php | 3 + 34 files changed, 482 insertions(+), 223 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index 00fed9240..be7703f02 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/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)); } diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index bcfc67fea..360924143 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/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) diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 5291bdfaa..d91b48dd3 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/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() : ''; } diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index fa4a0f574..462f3e4df 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/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 @@ -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); } } diff --git a/src/Composer/DependencyResolver/RuleSet.php b/src/Composer/DependencyResolver/RuleSet.php index 3db54d9df..9834e002f 100644 --- a/src/Composer/DependencyResolver/RuleSet.php +++ b/src/Composer/DependencyResolver/RuleSet.php @@ -12,6 +12,8 @@ namespace Composer\DependencyResolver; +use Composer\Repository\RepositorySet; + /** * @author Nils Adermann */ @@ -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); } } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index be9494d91..d670c980a 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/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); diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index f720aba4e..37768f436 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -13,6 +13,7 @@ namespace Composer\DependencyResolver; use Composer\Util\IniHelper; +use Composer\Repository\RepositorySet; /** * @author Nils Adermann @@ -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; diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index bbd81d997..542a9ee1c 100644 --- a/src/Composer/Installer.php +++ b/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()])) { diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 03b872d47..517b27d7b 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/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; } diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 6c7d0e65e..ab67d42d2 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/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())])) { diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index aff80e4cd..d317b1404 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/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; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 3919d2a6b..dd1588a9a 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/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; diff --git a/src/Composer/Repository/CompositeRepository.php b/src/Composer/Repository/CompositeRepository.php index 8ead6693a..9565a9b86 100644 --- a/src/Composer/Repository/CompositeRepository.php +++ b/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 * diff --git a/src/Composer/Repository/InstalledArrayRepository.php b/src/Composer/Repository/InstalledArrayRepository.php index 7ad05d0fa..de1dd67d8 100644 --- a/src/Composer/Repository/InstalledArrayRepository.php +++ b/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(); + } } diff --git a/src/Composer/Repository/InstalledFilesystemRepository.php b/src/Composer/Repository/InstalledFilesystemRepository.php index 1ff8a0a06..bf81734d4 100644 --- a/src/Composer/Repository/InstalledFilesystemRepository.php +++ b/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(); + } } diff --git a/src/Composer/Repository/LockArrayRepository.php b/src/Composer/Repository/LockArrayRepository.php index 0ccc998d3..8da3d5915 100644 --- a/src/Composer/Repository/LockArrayRepository.php +++ b/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(); + } } diff --git a/src/Composer/Repository/PackageRepository.php b/src/Composer/Repository/PackageRepository.php index 52b9a0f6b..de6d31d4d 100644 --- a/src/Composer/Repository/PackageRepository.php +++ b/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()); + } } diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 4db774579..4ae6d4b1c 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/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; diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index 5cffb6233..97e131afb 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/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; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 897700707..360671c7e 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/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; } diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index 8b1bb3cff..9992778fb 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/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(); } diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 10f3b7dd6..21c7efe0e 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/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) { diff --git a/src/Composer/Repository/RootPackageRepository.php b/src/Composer/Repository/RootPackageRepository.php index 8b5892717..721737fdc 100644 --- a/src/Composer/Repository/RootPackageRepository.php +++ b/src/Composer/Repository/RootPackageRepository.php @@ -21,4 +21,8 @@ namespace Composer\Repository; */ class RootPackageRepository extends ArrayRepository { + public function getRepoName() + { + return 'root package repo'; + } } diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 25b1b21b0..585b0e582 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/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; diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index 2215c019b..3617e08fd 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/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)); } } diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index 6a01ff2f2..3ff6b765d 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/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)); } } diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index bc9b39204..bcbc47c97 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/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 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 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) diff --git a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test b/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test index d970d5c4c..c9a9dba6e 100644 --- a/tests/Composer/Test/Fixtures/installer/broken-deps-do-not-replace.test +++ b/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]. diff --git a/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test b/tests/Composer/Test/Fixtures/installer/conflict-between-dependents.test new file mode 100644 index 000000000..40c19f3eb --- /dev/null +++ b/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-- + diff --git a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test b/tests/Composer/Test/Fixtures/installer/github-issues-4319.test index 7d012cf7c..dcc94dff2 100644 --- a/tests/Composer/Test/Fixtures/installer/github-issues-4319.test +++ b/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-- diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test b/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test index 9735d0a2c..8e0b18e05 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test +++ b/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 for more details. - - It's a private package and you forgot to add a custom repository to find it - -Read 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. diff --git a/tests/Composer/Test/Fixtures/installer/repositories-priorities.test b/tests/Composer/Test/Fixtures/installer/repositories-priorities.test index 42f7a1a6e..bc06179e0 100644 --- a/tests/Composer/Test/Fixtures/installer/repositories-priorities.test +++ b/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 for more details. - - It's a private package and you forgot to add a custom repository to find it - -Read for further common problems. --EXPECT-- --EXPECT-EXIT-CODE-- 2 diff --git a/tests/Composer/Test/Fixtures/installer/solver-problems.test b/tests/Composer/Test/Fixtures/installer/solver-problems.test index a0264257f..f3905b6fa 100644 --- a/tests/Composer/Test/Fixtures/installer/solver-problems.test +++ b/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 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-- diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index bb0643cf1..bb1473e71 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/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)); } }