1
0
Fork 0

Merge pull request #8558 from Seldaek/error-reporting

Improve error reporting of solver issues
pull/8566/head
Nils Adermann 2020-01-30 17:19:56 +01:00 committed by GitHub
commit 65dfb26c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 849 additions and 298 deletions

View File

@ -89,7 +89,7 @@ class BaseDependencyCommand extends BaseCommand
); );
// Find packages that are or provide the requested package first // 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)) { if (empty($packages)) {
throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle)); throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
} }

View File

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

View File

@ -13,6 +13,8 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Package\CompletePackageInterface; use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\Constraint;
/** /**
* Represents a problem detected while solving dependencies * Represents a problem detected while solving dependencies
@ -35,13 +37,6 @@ class Problem
protected $section = 0; protected $section = 0;
protected $pool;
public function __construct(Pool $pool)
{
$this->pool = $pool;
}
/** /**
* Add a rule as a reason * Add a rule as a reason
* *
@ -68,7 +63,7 @@ class Problem
* @param array $installedMap A map of all present packages * @param array $installedMap A map of all present packages
* @return string * @return string
*/ */
public function getPrettyString(array $installedMap = array(), array $learnedPool = array()) public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
{ {
// TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? // 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)); $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."); throw new \LogicException("Single reason problems must contain a request rule.");
} }
$request = $rule->getReasonData(); $reasonData = $rule->getReasonData();
$packageName = $request['packageName']; $packageName = $reasonData['packageName'];
$constraint = $request['constraint']; $constraint = $reasonData['constraint'];
if (isset($constraint)) { if (isset($constraint)) {
$packages = $this->pool->whatProvides($packageName, $constraint); $packages = $pool->whatProvides($packageName, $constraint);
} else { } else {
$packages = array(); $packages = array();
} }
if (empty($packages)) { if (empty($packages)) {
// handle php/hhvm return "\n ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $packageName, $constraint));
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.';
} }
} }
$messages = array(); $messages = array();
foreach ($reasons as $rule) { foreach ($reasons as $rule) {
$messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool); $messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
} }
return "\n - ".implode("\n - ", $messages); return "\n - ".implode("\n - ", $messages);
@ -193,7 +122,150 @@ class Problem
$this->section++; $this->section++;
} }
protected function getPackageList($packages) /**
* @internal
*/
public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, $packageName, $constraint = null)
{
// 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.');
}
if ($providers = $repositorySet->getProviders($packageName)) {
$maxProviders = 20;
$providersStr = implode(array_map(function ($p) {
return " - ${p['name']} ".substr($p['description'], 0, 100)."\n";
}, count($providers) > $maxProviders+1 ? array_slice($providers, 0, $maxProviders) : $providers));
if (count($providers) > $maxProviders+1) {
$providersStr .= ' ... and '.(count($providers)-$maxProviders).' more.'."\n";
}
return array("- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it: \n".$providersStr." Consider requiring one of these to satisfy the $packageName requirement.");
}
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(); $prepared = array();
foreach ($packages as $package) { foreach ($packages as $package) {
@ -207,13 +279,27 @@ class Problem
return implode(', ', $prepared); 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 * Turns a constraint into text usable in a sentence describing a request
* *
* @param \Composer\Semver\Constraint\ConstraintInterface $constraint * @param \Composer\Semver\Constraint\ConstraintInterface $constraint
* @return string * @return string
*/ */
protected function constraintToText($constraint) protected static function constraintToText($constraint)
{ {
return $constraint ? ' '.$constraint->getPrettyString() : ''; return $constraint ? ' '.$constraint->getPrettyString() : '';
} }

View File

@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\CompletePackage; use Composer\Package\CompletePackage;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Repository\RepositorySet;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -122,7 +123,7 @@ abstract class Rule
abstract public function isAssertion(); abstract public function isAssertion();
public function getPrettyString(Pool $pool, array $installedMap = array(), array $learnedPool = array()) public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
{ {
$literals = $this->getLiterals(); $literals = $this->getLiterals();
@ -161,7 +162,7 @@ abstract class Rule
$package1 = $pool->literalToPackage($literals[0]); $package1 = $pool->literalToPackage($literals[0]);
$package2 = $pool->literalToPackage($literals[1]); $package2 = $pool->literalToPackage($literals[1]);
return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.'; return $package2->getPrettyString().' conflicts with '.$package1->getPrettyString().'.';
case self::RULE_PACKAGE_REQUIRES: case self::RULE_PACKAGE_REQUIRES:
$sourceLiteral = array_shift($literals); $sourceLiteral = array_shift($literals);
@ -178,85 +179,103 @@ abstract class Rule
} else { } else {
$targetName = $this->reasonData->getTarget(); $targetName = $this->reasonData->getTarget();
if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') { $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $targetName, $this->reasonData->getConstraint());
// handle php/hhvm
if (defined('HHVM_VERSION')) {
return $text . ' -> your HHVM version does not satisfy that requirement.';
}
$packages = $pool->whatProvides($targetName); return $text . ' -> ' . $reason[1];
$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; return $text;
case self::RULE_PACKAGE_OBSOLETES: case self::RULE_PACKAGE_OBSOLETES:
if (count($literals) === 2 && $literals[0] < 0 && $literals[1] < 0) {
$package1 = $pool->literalToPackage($literals[0]);
$package2 = $pool->literalToPackage($literals[1]);
$replaces1 = $this->getReplacedNames($package1);
$replaces2 = $this->getReplacedNames($package2);
$reason = null;
if ($conflictingNames = array_values(array_intersect($replaces1, $replaces2))) {
$reason = 'They both replace '.(count($conflictingNames) > 1 ? '['.implode(', ', $conflictingNames).']' : $conflictingNames[0]).' and can thus not coexist.';
} elseif (in_array($package1->getName(), $replaces2, true)) {
$reason = $package2->getName().' replaces '.$package1->getName().' and can thus not coexist with it.';
} elseif (in_array($package2->getName(), $replaces1, true)) {
$reason = $package1->getName().' replaces '.$package2->getName().' and can thus not coexist with it.';
}
if ($reason) {
if (isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
// swap vars so the if below passes
$tmp = $package2;
$package2 = $package1;
$package1 = $tmp;
}
if (!isset($installedMap[$package1->id]) && isset($installedMap[$package2->id])) {
return $package1->getPrettyString().' can not be installed as that would require removing '.$package2->getPrettyString().'. '.$reason;
}
if (!isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'. '.$reason;
}
}
return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'.';
}
return $ruleText; return $ruleText;
case self::RULE_INSTALLED_PACKAGE_OBSOLETES: case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
return $ruleText; return $ruleText;
case self::RULE_PACKAGE_SAME_NAME: case self::RULE_PACKAGE_SAME_NAME:
return 'Same name, can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.'; $replacedNames = null;
$packageNames = array();
foreach ($literals as $literal) {
$package = $pool->literalToPackage($literal);
$pkgReplaces = $this->getReplacedNames($package);
if ($pkgReplaces) {
if ($replacedNames === null) {
$replacedNames = $this->getReplacedNames($package);
} else {
$replacedNames = array_intersect($replacedNames, $this->getReplacedNames($package));
}
}
$packageNames[$package->getName()] = true;
}
if ($replacedNames) {
$replacedNames = array_values(array_intersect(array_keys($packageNames), $replacedNames));
}
if ($replacedNames && count($packageNames) > 1) {
$replacer = null;
foreach ($literals as $literal) {
$package = $pool->literalToPackage($literal);
if (array_intersect($replacedNames, $this->getReplacedNames($package))) {
$replacer = $package;
break;
}
}
$replacedNames = count($replacedNames) > 1 ? '['.implode(', ', $replacedNames).']' : $replacedNames[0];
if ($replacer) {
return 'Only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '. '.$replacer->getName().' replaces '.$replacedNames.' and can thus not coexist with it.';
}
}
return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '.';
case self::RULE_PACKAGE_IMPLICIT_OBSOLETES: case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
return $ruleText; return $ruleText;
case self::RULE_LEARNED: case self::RULE_LEARNED:
// TODO not sure this is a good idea, most of these rules should be listed in the problem anyway
$learnedString = '(learned rule, ';
if (isset($learnedPool[$this->reasonData])) { if (isset($learnedPool[$this->reasonData])) {
$learnedString = ', learned rules:'."\n - ";
$reasons = array();
foreach ($learnedPool[$this->reasonData] as $learnedRule) { foreach ($learnedPool[$this->reasonData] as $learnedRule) {
$learnedString .= $learnedRule->getPrettyString($pool, $installedMap, $learnedPool); $reasons[] = $learnedRule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
} }
$learnedString .= implode("\n - ", array_unique($reasons));
} else { } else {
$learnedString .= 'reasoning unavailable'; $learnedString = ' (reasoning unavailable)';
} }
$learnedString .= ')';
return 'Conclusion: '.$ruleText.' '.$learnedString; return 'Conclusion: '.$ruleText.$learnedString;
case self::RULE_PACKAGE_ALIAS: case self::RULE_PACKAGE_ALIAS:
return $ruleText; return $ruleText;
default: default:
@ -272,20 +291,23 @@ abstract class Rule
*/ */
protected function formatPackagesUnique($pool, array $packages) protected function formatPackagesUnique($pool, array $packages)
{ {
// TODO this is essentially a duplicate of Problem: getPackageList, maintain in one place only?
$prepared = array(); $prepared = array();
foreach ($packages as $package) { foreach ($packages as $index => $package) {
if (!is_object($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);
}
private function getReplacedNames(PackageInterface $package)
{
$names = array();
foreach ($package->getReplaces() as $link) {
$names[] = $link->getTarget();
}
return $names;
} }
} }

View File

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

View File

@ -14,9 +14,7 @@ namespace Composer\DependencyResolver;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositorySet;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -29,7 +27,7 @@ class Solver
/** @var PolicyInterface */ /** @var PolicyInterface */
protected $policy; protected $policy;
/** @var Pool */ /** @var Pool */
protected $pool = null; protected $pool;
/** @var RuleSet */ /** @var RuleSet */
protected $rules; protected $rules;
@ -120,7 +118,7 @@ class Solver
$conflict = $this->decisions->decisionRule($literal); $conflict = $this->decisions->decisionRule($literal);
if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) { if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
$problem = new Problem($this->pool); $problem = new Problem();
$problem->addRule($rule); $problem->addRule($rule);
$problem->addRule($conflict); $problem->addRule($conflict);
@ -130,7 +128,7 @@ class Solver
} }
// conflict with another root require/fixed package // conflict with another root require/fixed package
$problem = new Problem($this->pool); $problem = new Problem();
$problem->addRule($rule); $problem->addRule($rule);
$problem->addRule($conflict); $problem->addRule($conflict);
@ -177,7 +175,7 @@ class Solver
} }
if (!$this->pool->whatProvides($packageName, $constraint)) { 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))); $problem->addRule(new GenericRule(array(), Rule::RULE_ROOT_REQUIRE, array('packageName' => $packageName, 'constraint' => $constraint)));
$this->problems[] = $problem; $this->problems[] = $problem;
} }
@ -214,7 +212,7 @@ class Solver
$this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
if ($this->problems) { if ($this->problems) {
throw new SolverProblemsException($this->problems, $request->getPresentMap(true), $this->learnedPool); throw new SolverProblemsException($this->problems, $this->learnedPool);
} }
return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions); return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
@ -513,7 +511,7 @@ class Solver
*/ */
private function analyzeUnsolvable(Rule $conflictRule) private function analyzeUnsolvable(Rule $conflictRule)
{ {
$problem = new Problem($this->pool); $problem = new Problem();
$problem->addRule($conflictRule); $problem->addRule($conflictRule);
$this->analyzeUnsolvableRule($problem, $conflictRule); $this->analyzeUnsolvableRule($problem, $conflictRule);

View File

@ -13,6 +13,7 @@
namespace Composer\DependencyResolver; namespace Composer\DependencyResolver;
use Composer\Util\IniHelper; use Composer\Util\IniHelper;
use Composer\Repository\RepositorySet;
/** /**
* @author Nils Adermann <naderman@naderman.de> * @author Nils Adermann <naderman@naderman.de>
@ -20,24 +21,23 @@ use Composer\Util\IniHelper;
class SolverProblemsException extends \RuntimeException class SolverProblemsException extends \RuntimeException
{ {
protected $problems; protected $problems;
protected $installedMap;
protected $learnedPool; protected $learnedPool;
public function __construct(array $problems, array $installedMap, array $learnedPool) public function __construct(array $problems, array $learnedPool)
{ {
$this->problems = $problems; $this->problems = $problems;
$this->installedMap = $installedMap;
$this->learnedPool = $learnedPool; $this->learnedPool = $learnedPool;
parent::__construct($this->createMessage(), 2); parent::__construct('Failed resolving dependencies with '.count($problems).' problems, call getPrettyString to get formatted details', 2);
} }
protected function createMessage() public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool)
{ {
$installedMap = $request->getPresentMap(true);
$text = "\n"; $text = "\n";
$hasExtensionProblems = false; $hasExtensionProblems = false;
foreach ($this->problems as $i => $problem) { 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, $pool, $installedMap, $this->learnedPool)."\n";
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) { if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
$hasExtensionProblems = true; $hasExtensionProblems = true;

View File

@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil; use Composer\Util\Git as GitUtil;
use Composer\Util\Url;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Cache; use Composer\Cache;
@ -434,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>'); $this->io->writeError(' <warning>'.$reference.' is gone (history was rewritten?)</warning>');
} }
throw new \RuntimeException(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput())); throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
} }
protected function updateOriginUrl($path, $url) protected function updateOriginUrl($path, $url)

View File

@ -389,14 +389,14 @@ class Installer
$pool = $repositorySet->createPool($request); $pool = $repositorySet->createPool($request);
// solve dependencies // solve dependencies
$solver = new Solver($policy, $pool, $this->io); $solver = new Solver($policy, $pool, $this->io, $repositorySet);
try { try {
$lockTransaction = $solver->solve($request, $this->ignorePlatformReqs); $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
$ruleSetSize = $solver->getRuleSetSize(); $ruleSetSize = $solver->getRuleSetSize();
$solver = null; $solver = null;
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
$this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET); $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
$this->io->writeError($e->getMessage()); $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
if (!$this->devMode) { if (!$this->devMode) {
$this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET); $this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
} }
@ -529,14 +529,14 @@ class Installer
$pool = $repositorySet->createPool($request); $pool = $repositorySet->createPool($request);
//$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $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 { try {
$nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs); $nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
//$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops); //$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);
$solver = null; $solver = null;
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
$this->io->writeError('<error>Unable to find a compatible set of packages based on your non-dev requirements alone.</error>', true, IOInterface::QUIET); $this->io->writeError('<error>Unable to find a compatible set of packages based on your non-dev requirements alone.</error>', true, IOInterface::QUIET);
$this->io->writeError($e->getMessage()); $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
return max(1, $e->getCode()); return max(1, $e->getCode());
} }
@ -589,7 +589,7 @@ class Installer
$pool = $repositorySet->createPool($request); $pool = $repositorySet->createPool($request);
// solve dependencies // solve dependencies
$solver = new Solver($policy, $pool, $this->io); $solver = new Solver($policy, $pool, $this->io, $repositorySet);
try { try {
$lockTransaction = $solver->solve($request, $this->ignorePlatformReqs); $lockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
$solver = null; $solver = null;
@ -602,7 +602,7 @@ class Installer
} }
} catch (SolverProblemsException $e) { } catch (SolverProblemsException $e) {
$this->io->writeError('<error>Your lock file does not contain a compatible set of packages. Please run composer update.</error>', true, IOInterface::QUIET); $this->io->writeError('<error>Your lock file does not contain a compatible set of packages. Please run composer update.</error>', true, IOInterface::QUIET);
$this->io->writeError($e->getMessage()); $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
return max(1, $e->getCode()); return max(1, $e->getCode());
} }
@ -884,7 +884,7 @@ class Installer
$packageQueue = new \SplQueue; $packageQueue = new \SplQueue;
$nameMatchesRequiredPackage = false; $nameMatchesRequiredPackage = false;
$depPackages = $repositorySet->findPackages($packageName, null, false); $depPackages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_PROVIDERS_REPLACERS);
$matchesByPattern = array(); $matchesByPattern = array();
// check if the name is a glob pattern that did not match directly // 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 // add any installed package matching the whitelisted name/pattern
$whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$');
foreach ($lockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { 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 // add root requirements which match the whitelisted name/pattern
@ -933,7 +933,7 @@ class Installer
$requires = $package->getRequires(); $requires = $package->getRequires();
foreach ($requires as $require) { 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) { foreach ($requirePackages as $requirePackage) {
if (isset($this->updateWhitelist[$requirePackage->getName()])) { if (isset($this->updateWhitelist[$requirePackage->getName()])) {

View File

@ -412,7 +412,7 @@ class PluginManager
*/ */
private function lookupInstalledPackage(RepositorySet $repositorySet, Link $link) 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; return !empty($packages) ? $packages[0] : null;
} }

View File

@ -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} * {@inheritDoc}
*/ */
@ -57,7 +62,9 @@ class ArrayRepository extends BaseRepository
(!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion())))
&& StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability())
) { ) {
// add selected packages which match stability requirements
$result[spl_object_hash($package)] = $package; $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())])) { if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
$result[spl_object_hash($package->getAliasOf())] = $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) { foreach ($packages as $package) {
if ($package instanceof AliasPackage) { if ($package instanceof AliasPackage) {
if (isset($result[spl_object_hash($package->getAliasOf())])) { if (isset($result[spl_object_hash($package->getAliasOf())])) {

View File

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

View File

@ -34,6 +34,7 @@ use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\EmptyConstraint; use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Util\Http\Response; use Composer\Util\Http\Response;
use Composer\Util\MetadataMinifier; use Composer\Util\MetadataMinifier;
use Composer\Util\Url;
use React\Promise\Util as PromiseUtil; use React\Promise\Util as PromiseUtil;
/** /**
@ -52,6 +53,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
protected $cache; protected $cache;
protected $notifyUrl; protected $notifyUrl;
protected $searchUrl; protected $searchUrl;
/** @var string|null a URL containing %package% which can be queried to get providers of a given name */
protected $providersApiUrl;
protected $hasProviders = false; protected $hasProviders = false;
protected $providersUrl; protected $providersUrl;
protected $availablePackages; protected $availablePackages;
@ -125,6 +128,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->loop = new Loop($this->httpDownloader); $this->loop = new Loop($this->httpDownloader);
} }
public function getRepoName()
{
return 'composer repo ('.Url::sanitize($this->url).')';
}
public function getRepoConfig() public function getRepoConfig()
{ {
return $this->repoConfig; return $this->repoConfig;
@ -411,6 +419,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return parent::search($query, $mode); return parent::search($query, $mode);
} }
public function getProviders($packageName)
{
if (!$this->providersApiUrl) {
return array();
}
$result = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson();
return $result['providers'];
}
private function getProviderNames() private function getProviderNames()
{ {
$this->loadRootServerFile(); $this->loadRootServerFile();
@ -805,6 +824,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->hasProviders = true; $this->hasProviders = true;
} }
if (!empty($data['providers-api'])) {
$this->providersApiUrl = $data['providers-api'];
}
return $this->rootData = $data; return $this->rootData = $data;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\Url;
/** /**
* This repository allows installing local packages that are not necessarily under their own VCS. * This repository allows installing local packages that are not necessarily under their own VCS.
@ -111,6 +112,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
parent::__construct(); parent::__construct();
} }
public function getRepoName()
{
return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
}
public function getRepoConfig() public function getRepoConfig()
{ {
return $this->repoConfig; return $this->repoConfig;

View File

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

View File

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

View File

@ -83,4 +83,13 @@ interface RepositoryInterface extends \Countable
* @return array[] an array of array('name' => '...', 'description' => '...') * @return array[] an array of array('name' => '...', 'description' => '...')
*/ */
public function search($query, $mode = 0, $type = null); 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();
} }

View File

@ -29,6 +29,19 @@ use Composer\Package\Version\StabilityFilter;
*/ */
class RepositorySet 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 */ /** @var array */
private $rootAliases; private $rootAliases;
/** @var array */ /** @var array */
@ -39,10 +52,10 @@ class RepositorySet
private $acceptableStabilities; private $acceptableStabilities;
private $stabilityFlags; private $stabilityFlags;
protected $rootRequires; private $rootRequires;
/** @var Pool */ /** @var bool */
private $pool; private $locked = false;
public function __construct(array $rootAliases = array(), array $rootReferences = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $rootRequires = array()) public function __construct(array $rootAliases = array(), array $rootReferences = array(), $minimumStability = 'stable', array $stabilityFlags = array(), array $rootRequires = array())
{ {
@ -64,6 +77,11 @@ class RepositorySet
} }
} }
public function getRootRequires()
{
return $this->rootRequires;
}
/** /**
* Adds a repository to this repository set * Adds a repository to this repository set
* *
@ -74,7 +92,7 @@ class RepositorySet
*/ */
public function addRepository(RepositoryInterface $repo) public function addRepository(RepositoryInterface $repo)
{ {
if ($this->pool) { if ($this->locked) {
throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore."); throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore.");
} }
@ -96,15 +114,32 @@ class RepositorySet
* *
* @param string $name * @param string $name
* @param ConstraintInterface|null $constraint * @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 int $flags any of the ALLOW_* constants from this class to tweak what is returned
* @param bool $ignoreStability if set to true, packages are returned even though their stability does not match the required stability
* @return array * @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(); $packages = array();
foreach ($this->repositories as $repository) { if ($loadFromAllRepos) {
$packages[] = $repository->findPackages($name, $constraint) ?: array(); 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(); $candidates = $packages ? call_user_func_array('array_merge', $packages) : array();
@ -123,6 +158,19 @@ class RepositorySet
return $candidates; return $candidates;
} }
public function getProviders($packageName)
{
foreach ($this->repositories as $repository) {
if ($repository instanceof ComposerRepository) {
if ($providers = $repository->getProviders($packageName)) {
return $providers;
}
}
}
return array();
}
public function isPackageAcceptable($names, $stability) public function isPackageAcceptable($names, $stability)
{ {
return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability); return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability);
@ -135,7 +183,7 @@ class RepositorySet
*/ */
public function createPool(Request $request) 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) { foreach ($this->repositories as $repo) {
if ($repo instanceof InstalledRepositoryInterface) { if ($repo instanceof InstalledRepositoryInterface) {
@ -143,7 +191,9 @@ class RepositorySet
} }
} }
return $this->pool = $poolBuilder->buildPool($this->repositories, $request); $this->locked = true;
return $poolBuilder->buildPool($this->repositories, $request);
} }
// TODO unify this with above in some simpler version without "request"? // TODO unify this with above in some simpler version without "request"?
@ -162,13 +212,4 @@ class RepositorySet
return $this->createPool($request); return $this->createPool($request);
} }
/**
* Access the pool object after it has been created, relevant for plugins which need to read info from the pool
* @return Pool
*/
public function getPool()
{
return $this->pool;
}
} }

View File

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

View File

@ -22,6 +22,7 @@ use Composer\Package\Loader\LoaderInterface;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\Url;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Config; use Composer\Config;
@ -79,6 +80,17 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
$this->processExecutor = new ProcessExecutor($io); $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.' '.Url::sanitize($this->url).')';
}
public function getRepoConfig() public function getRepoConfig()
{ {
return $this->repoConfig; return $this->repoConfig;

View File

@ -255,15 +255,4 @@ class AuthHelper
return count($pathParts) >= 4 && $pathParts[3] == 'downloads'; return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
} }
/**
* @param string $url
* @return string
*/
public function stripCredentialsFromUrl($url)
{
// GitHub repository rename result in redirect locations containing the access_token as GET parameter
// e.g. https://api.github.com/repositories/9999999999?access_token=github_token
return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
}
} }

View File

@ -362,27 +362,16 @@ class Git
return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')'; return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
} }
public static function sanitizeUrl($message)
{
return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
return '://***:***@';
}
return '://' . $m[1] . ':***@';
}, $message);
}
private function throwException($message, $url) private function throwException($message, $url)
{ {
// git might delete a directory when it fails and php will not know // git might delete a directory when it fails and php will not know
clearstatcache(); clearstatcache();
if (0 !== $this->process->execute('git --version', $ignoredOutput)) { if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
} }
throw new \RuntimeException(self::sanitizeUrl($message)); throw new \RuntimeException(Url::sanitize($message));
} }
/** /**

View File

@ -72,23 +72,12 @@ class Hg
$this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url); $this->throwException('Failed to clone ' . $url . ', ' . "\n\n" . $error, $url);
} }
public static function sanitizeUrl($message)
{
return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
return '://***:***@';
}
return '://' . $m[1] . ':***@';
}, $message);
}
private function throwException($message, $url) private function throwException($message, $url)
{ {
if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
throw new \RuntimeException(self::sanitizeUrl('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
} }
throw new \RuntimeException(self::sanitizeUrl($message)); throw new \RuntimeException(Url::sanitize($message));
} }
} }

View File

@ -195,7 +195,7 @@ class CurlDownloader
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : ''; $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : ''; $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
if ($attributes['redirects'] === 0) { if ($attributes['redirects'] === 0) {
$this->io->writeError('Downloading ' . $this->authHelper->stripCredentialsFromUrl($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG); $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
} }
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
@ -254,12 +254,12 @@ class CurlDownloader
$contents = stream_get_contents($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']);
} }
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
$this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG);
} else { } else {
rewind($job['bodyHandle']); rewind($job['bodyHandle']);
$contents = stream_get_contents($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']);
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
$this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG);
} }
fclose($job['bodyHandle']); fclose($job['bodyHandle']);
@ -362,7 +362,7 @@ class CurlDownloader
} }
if (!empty($targetUrl)) { if (!empty($targetUrl)) {
$this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);
return $targetUrl; return $targetUrl;
} }

View File

@ -246,7 +246,7 @@ class RemoteFilesystem
$actualContextOptions = stream_context_get_options($ctx); $actualContextOptions = stream_context_get_options($ctx);
$usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : ''; $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
$this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->authHelper->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG); $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
unset($origFileUrl, $actualContextOptions); unset($origFileUrl, $actualContextOptions);
// Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
@ -704,7 +704,7 @@ class RemoteFilesystem
$this->redirects++; $this->redirects++;
$this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError('', true, IOInterface::DEBUG);
$this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);
$additionalOptions['redirects'] = $this->redirects; $additionalOptions['redirects'] = $this->redirects;

View File

@ -102,4 +102,21 @@ class Url
return $origin; return $origin;
} }
public static function sanitize($url)
{
// GitHub repository rename result in redirect locations containing the access_token as GET parameter
// e.g. https://api.github.com/repositories/9999999999?access_token=github_token
$url = preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
$url = preg_replace_callback('{://(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@}i', function ($m) {
if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) {
return '://***:***@';
}
return '://'.$m['user'].':***@';
}, $url);
return $url;
}
} }

View File

@ -143,12 +143,15 @@ class RuleSetTest extends TestCase
$p = $this->getPackage('foo', '2.1'), $p = $this->getPackage('foo', '2.1'),
)); ));
$repositorySetMock = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock();
$requestMock = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock();
$ruleSet = new RuleSet; $ruleSet = new RuleSet;
$literal = $p->getId(); $literal = $p->getId();
$rule = new GenericRule(array($literal), Rule::RULE_ROOT_REQUIRE, array('packageName' => 'foo/bar', 'constraint' => null)); $rule = new GenericRule(array($literal), Rule::RULE_ROOT_REQUIRE, array('packageName' => 'foo/bar', 'constraint' => null));
$ruleSet->add($rule, RuleSet::TYPE_REQUEST); $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, $pool));
} }
} }

View File

@ -99,8 +99,11 @@ class RuleTest extends TestCase
$p2 = $this->getPackage('baz', '1.1'), $p2 = $this->getPackage('baz', '1.1'),
)); ));
$repositorySetMock = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock();
$requestMock = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock();
$rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_PACKAGE_REQUIRES, new Link('baz', 'foo')); $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, $pool));
} }
} }

View File

@ -34,6 +34,7 @@ class SolverTest extends TestCase
protected $request; protected $request;
protected $policy; protected $policy;
protected $solver; protected $solver;
protected $pool;
public function setUp() public function setUp()
{ {
@ -82,7 +83,7 @@ class SolverTest extends TestCase
$problems = $e->getProblems(); $problems = $e->getProblems();
$this->assertCount(1, $problems); $this->assertCount(1, $problems);
$this->assertEquals(2, $e->getCode()); $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, $this->pool));
} }
} }
@ -651,9 +652,9 @@ class SolverTest extends TestCase
$msg = "\n"; $msg = "\n";
$msg .= " Problem 1\n"; $msg .= " Problem 1\n";
$msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n"; $msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n";
$msg .= " - B 1.0 conflicts with A[1.0].\n"; $msg .= " - A 1.0 conflicts with B 1.0.\n";
$msg .= " - Root composer.json requires b -> satisfiable by B[1.0].\n"; $msg .= " - Root composer.json requires b -> satisfiable by B[1.0].\n";
$this->assertEquals($msg, $e->getMessage()); $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool));
} }
} }
@ -682,14 +683,8 @@ class SolverTest extends TestCase
$msg = "\n"; $msg = "\n";
$msg .= " Problem 1\n"; $msg .= " Problem 1\n";
$msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\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 .= " - A 1.0 requires b >= 2.0 -> found B[1.0] but it does not match your constraint.\n";
$msg .= "Potential causes:\n"; $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool));
$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.";
$this->assertEquals($msg, $e->getMessage());
} }
} }
@ -731,10 +726,10 @@ class SolverTest extends TestCase
$msg .= " - C 1.0 requires d >= 1.0 -> satisfiable by D[1.0].\n"; $msg .= " - C 1.0 requires d >= 1.0 -> satisfiable by D[1.0].\n";
$msg .= " - D 1.0 requires b < 1.0 -> satisfiable by B[0.9].\n"; $msg .= " - D 1.0 requires b < 1.0 -> satisfiable by B[0.9].\n";
$msg .= " - B 1.0 requires c >= 1.0 -> satisfiable by C[1.0].\n"; $msg .= " - B 1.0 requires c >= 1.0 -> satisfiable by C[1.0].\n";
$msg .= " - Same name, can only install one of: B[0.9, 1.0].\n"; $msg .= " - You can only install one version of a package, so only one of these can be installed: B[0.9, 1.0].\n";
$msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n"; $msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n";
$msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n"; $msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n";
$this->assertEquals($msg, $e->getMessage()); $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool));
} }
} }
@ -895,7 +890,8 @@ class SolverTest extends TestCase
protected function createSolver() protected function createSolver()
{ {
$this->solver = new Solver($this->policy, $this->repoSet->createPool($this->request), new NullIO()); $this->pool = $this->repoSet->createPool($this->request);
$this->solver = new Solver($this->policy, $this->pool, new NullIO());
} }
protected function checkSolverResult(array $expected) protected function checkSolverResult(array $expected)

View File

@ -26,7 +26,7 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages. Your requirements could not be resolved to an installable set of packages.
Problem 1 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]. - 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]. - Root composer.json requires b/b 1.* -> satisfiable by b/b[1.0.0].

View File

@ -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].
- conflicter/pkg 1.0.0 conflicts with victim/pkg 1.0.0.
- Root composer.json requires victim/pkg 1.0.0 -> satisfiable by victim/pkg[1.0.0].
--EXPECT--

View File

@ -0,0 +1,40 @@
--TEST--
Test conflicts between a dependency's requirements and the root requirements
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "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" }
]
}
],
"require": {
"requirer/pkg": "1.*",
"dependency/pkg": "2.*"
}
}
--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 requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0].
- 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.*).
--EXPECT--

View File

@ -37,7 +37,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1 Problem 1
- Root composer.json requires a/a ~1.0 -> satisfiable by a/a[1.0.0]. - 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-- --EXPECT--

View File

@ -1,5 +1,5 @@
--TEST-- --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-- --COMPOSER--
{ {
"repositories": [ "repositories": [
@ -59,12 +59,4 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages. Your requirements could not be resolved to an installable set of packages.
Problem 1 Problem 1
- The requested package b/unstable could not be found in any version, there may be a typo in the package name. - 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.
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.

View File

@ -0,0 +1,49 @@
--TEST--
Test that names provided by a dependent and root package cause a conflict only for replace
--COMPOSER--
{
"version": "1.2.3",
"repositories": [
{
"type": "package",
"package": [
{
"name": "provider/pkg",
"version": "1.0.0",
"provide": { "root-provided/transitive-provided": "2.*", "root-replaced/transitive-provided": "2.*" },
"replace": { "root-provided/transitive-replaced": "2.*", "root-replaced/transitive-replaced": "2.*" }
}
]
}
],
"require": {
"provider/pkg": "*"
},
"provide": {
"root-provided/transitive-replaced": "2.*",
"root-provided/transitive-provided": "2.*"
},
"replace": {
"root-replaced/transitive-replaced": "2.*",
"root-replaced/transitive-provided": "2.*"
}
}
--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__ is present at version 1.2.3 and cannot be modified by Composer
- provider/pkg 1.0.0 can not be installed as that would require removing __root__ 1.2.3. They both replace root-replaced/transitive-replaced and can thus not coexist.
- Root composer.json requires provider/pkg * -> satisfiable by provider/pkg[1.0.0].
--EXPECT--

View File

@ -0,0 +1,45 @@
--TEST--
Test that names provided by two dependents cause a conflict
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{
"name": "provider/pkg",
"version": "1.0.0",
"provide": { "third/pkg": "2.*" }
},
{
"name": "replacer/pkg",
"version": "1.0.0",
"replace": { "third/pkg": "2.*" }
}
]
}
],
"require": {
"provider/pkg": "*",
"replacer/pkg": "*"
}
}
--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 provider/pkg * -> satisfiable by provider/pkg[1.0.0].
- Only one of these can be installed: replacer/pkg 1.0.0, provider/pkg 1.0.0.
- Root composer.json requires replacer/pkg * -> satisfiable by replacer/pkg[1.0.0].
--EXPECT--

View File

@ -0,0 +1,54 @@
--TEST--
Test that a replacer can not be installed together with another version of the package it replaces
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{"name": "replacer/pkg", "version": "2.0.0", "replace": { "regular/pkg": "self.version" }},
{"name": "replacer/pkg", "version": "2.0.1", "replace": { "regular/pkg": "self.version" }},
{"name": "replacer/pkg", "version": "2.0.2", "replace": { "regular/pkg": "self.version" }},
{"name": "replacer/pkg", "version": "2.0.3", "replace": { "regular/pkg": "self.version" }},
{"name": "regular/pkg", "version": "1.0.0"},
{"name": "regular/pkg", "version": "1.0.1"},
{"name": "regular/pkg", "version": "1.0.2"},
{"name": "regular/pkg", "version": "1.0.3"},
{"name": "regular/pkg", "version": "2.0.0"},
{"name": "regular/pkg", "version": "2.0.1"}
]
}
],
"require": {
"regular/pkg": "1.*",
"replacer/pkg": "2.*"
}
}
--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
- Conclusion: don't install regular/pkg 1.0.3, learned rules:
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
- Only one of these can be installed: regular/pkg[1.0.3, 1.0.2, 1.0.1, 1.0.0], replacer/pkg[2.0.3, 2.0.2, 2.0.1, 2.0.0]. replacer/pkg replaces regular/pkg and can thus not coexist with it.
- Conclusion: don't install regular/pkg 1.0.2, learned rules:
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
- Only one of these can be installed: regular/pkg[1.0.3, 1.0.2, 1.0.1, 1.0.0], replacer/pkg[2.0.3, 2.0.2, 2.0.1, 2.0.0]. replacer/pkg replaces regular/pkg and can thus not coexist with it.
- Conclusion: don't install regular/pkg 1.0.1, learned rules:
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
- Only one of these can be installed: regular/pkg[1.0.3, 1.0.2, 1.0.1, 1.0.0], replacer/pkg[2.0.3, 2.0.2, 2.0.1, 2.0.0]. replacer/pkg replaces regular/pkg and can thus not coexist with it.
- Only one of these can be installed: regular/pkg[1.0.3, 1.0.2, 1.0.1, 1.0.0], replacer/pkg[2.0.3, 2.0.2, 2.0.1, 2.0.0]. replacer/pkg replaces regular/pkg and can thus not coexist with it.
- Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3].
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
--EXPECT--

View File

@ -41,7 +41,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1 Problem 1
- Root composer.json requires foo/standard 1.0.0 -> satisfiable by foo/standard[1.0.0]. - Root composer.json requires foo/standard 1.0.0 -> satisfiable by foo/standard[1.0.0].
- foo/standard 1.0.0 requires foo/does-not-exist 1.0.0 -> no matching package found. - foo/standard 1.0.0 requires foo/does-not-exist 1.0.0 -> could not be found in any version, there may be a typo in the package name.
Potential causes: Potential causes:
- A typo in the package name - A typo in the package name

View File

@ -28,15 +28,8 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages. Your requirements could not be resolved to an installable set of packages.
Problem 1 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--
--EXPECT-EXIT-CODE-- --EXPECT-EXIT-CODE--
2 2

View File

@ -6,34 +6,84 @@ Test the error output of solver problems.
{ {
"type": "package", "type": "package",
"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": "2.0.0-alpha" },
{ "name": "unstable/package", "version": "1.0.0" }, { "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": "2.0.0" },
{ "name": "dependency/pkg", "version": "1.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.1" },
{ "name": "stable-requiree-excluded/pkg", "version": "1.0.0" } { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
] ]
} }
], ],
"require": { "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.*", "unstable/package": "2.*",
"bogus/pkg": "1.*", "non-existent/pkg": "1.*",
"requirer/pkg": "1.*", "requirer/pkg": "1.*",
"dependency/pkg": "2.*", "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-- --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-- --LOCK--
{ {
"packages": [ "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": [], "packages-dev": [],
"aliases": [], "aliases": [],
@ -46,7 +96,7 @@ Test the error output of solver problems.
} }
--RUN-- --RUN--
update unstable/package requirer/pkg dependency/pkg update unstable/package requirer/pkg dependency/pkg conflict/requirer
--EXPECT-EXIT-CODE-- --EXPECT-EXIT-CODE--
2 2
@ -57,14 +107,44 @@ Updating dependencies
Your requirements could not be resolved to an installable set of packages. Your requirements could not be resolved to an installable set of packages.
Problem 1 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 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 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 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]. - 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: Potential causes:
- A typo in the package name - 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 - 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. 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-- --EXPECT--

View File

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

View File

@ -58,4 +58,25 @@ class UrlTest extends TestCase
array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'), array('https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=abcd', 'https://mygitlab.com/api/v3/projects/foo%2Fbar/repository/archive.tar.bz2?sha=65', array('gitlab-domains' => array('mygitlab.com')), '65'),
); );
} }
/**
* @dataProvider sanitizeProvider
*/
public function testSanitize($expected, $url)
{
$this->assertSame($expected, Url::sanitize($url));
}
public static function sanitizeProvider()
{
return array(
array('https://foo:***@example.org/', 'https://foo:bar@example.org/'),
array('https://foo@example.org/', 'https://foo@example.org/'),
array('https://example.org/', 'https://example.org/'),
array('http://***:***@example.org', 'http://10a8f08e8d7b7b9:foo@example.org'),
array('https://foo:***@example.org:123/', 'https://foo:bar@example.org:123/'),
array('https://example.org/foo/bar?access_token=***', 'https://example.org/foo/bar?access_token=abcdef'),
array('https://example.org/foo/bar?foo=bar&access_token=***', 'https://example.org/foo/bar?foo=bar&access_token=abcdef'),
);
}
} }