Merge pull request #8558 from Seldaek/error-reporting

Improve error reporting of solver issues
main
Nils Adermann 4 years ago committed by GitHub
commit 65dfb26c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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));
}

@ -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)

@ -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, 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?
$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 = $pool->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, $pool, $packageName, $constraint));
}
}
$messages = array();
foreach ($reasons as $rule) {
$messages[] = $rule->getPrettyString($this->pool, $installedMap, $learnedPool);
$messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
}
return "\n - ".implode("\n - ", $messages);
@ -193,7 +122,150 @@ class Problem
$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();
foreach ($packages as $package) {
@ -207,13 +279,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() : '';
}

@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositorySet;
/**
* @author Nils Adermann <naderman@naderman.de>
@ -122,7 +123,7 @@ 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, Pool $pool, array $installedMap = array(), array $learnedPool = array())
{
$literals = $this->getLiterals();
@ -161,7 +162,7 @@ abstract class Rule
$package1 = $pool->literalToPackage($literals[0]);
$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:
$sourceLiteral = array_shift($literals);
@ -178,85 +179,103 @@ 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, $pool, $targetName, $this->reasonData->getConstraint());
$packages = $pool->whatProvides($targetName);
$package = count($packages) ? current($packages) : phpversion();
return $text . ' -> ' . $reason[1];
}
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.';
}
}
return $text;
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 (!($package instanceof CompletePackage)) {
return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
if ($reason) {
if (isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
// swap vars so the if below passes
$tmp = $package2;
$package2 = $package1;
$package1 = $tmp;
}
$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.';
if (!isset($installedMap[$package1->id]) && isset($installedMap[$package2->id])) {
return $package1->getPrettyString().' can not be installed as that would require removing '.$package2->getPrettyString().'. '.$reason;
}
return $text;
if (!isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'. '.$reason;
}
}
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 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'.';
}
return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.';
return $ruleText;
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
return $ruleText;
case self::RULE_PACKAGE_SAME_NAME:
$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 (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.';
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];
// 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.';
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 $text;
case self::RULE_PACKAGE_OBSOLETES:
return $ruleText;
case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
return $ruleText;
case self::RULE_PACKAGE_SAME_NAME:
return 'Same name, can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
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:
return $ruleText;
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])) {
$learnedString = ', learned rules:'."\n - ";
$reasons = array();
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 {
$learnedString .= 'reasoning unavailable';
$learnedString = ' (reasoning unavailable)';
}
$learnedString .= ')';
return 'Conclusion: '.$ruleText.' '.$learnedString;
return 'Conclusion: '.$ruleText.$learnedString;
case self::RULE_PACKAGE_ALIAS:
return $ruleText;
default:
@ -272,20 +291,23 @@ 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 Problem::getPackageList($packages);
}
private function getReplacedNames(PackageInterface $package)
{
$names = array();
foreach ($package->getReplaces() as $link) {
$names[] = $link->getTarget();
}
return implode(', ', $prepared);
return $names;
}
}

@ -12,6 +12,8 @@
namespace Composer\DependencyResolver;
use Composer\Repository\RepositorySet;
/**
* @author Nils Adermann <naderman@naderman.de>
*/
@ -155,13 +157,13 @@ class RuleSet implements \IteratorAggregate, \Countable
return array_keys($types);
}
public function getPrettyString(Pool $pool = null)
public function getPrettyString(RepositorySet $repositorySet = null, Request $request = null, Pool $pool = 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 && $pool ? $rule->getPrettyString($repositorySet, $request, $pool) : $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, null);
}
}

@ -14,9 +14,7 @@ namespace Composer\DependencyResolver;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositorySet;
/**
* @author Nils Adermann <naderman@naderman.de>
@ -29,7 +27,7 @@ class Solver
/** @var PolicyInterface */
protected $policy;
/** @var Pool */
protected $pool = null;
protected $pool;
/** @var RuleSet */
protected $rules;
@ -120,7 +118,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 +128,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 +175,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 +212,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->learnedPool);
}
return new LockTransaction($this->pool, $request->getPresentMap(), $request->getUnlockableMap(), $this->decisions);
@ -513,7 +511,7 @@ class Solver
*/
private function analyzeUnsolvable(Rule $conflictRule)
{
$problem = new Problem($this->pool);
$problem = new Problem();
$problem->addRule($conflictRule);
$this->analyzeUnsolvableRule($problem, $conflictRule);

@ -13,6 +13,7 @@
namespace Composer\DependencyResolver;
use Composer\Util\IniHelper;
use Composer\Repository\RepositorySet;
/**
* @author Nils Adermann <naderman@naderman.de>
@ -20,24 +21,23 @@ use Composer\Util\IniHelper;
class SolverProblemsException extends \RuntimeException
{
protected $problems;
protected $installedMap;
protected $learnedPool;
public function __construct(array $problems, array $installedMap, array $learnedPool)
public function __construct(array $problems, array $learnedPool)
{
$this->problems = $problems;
$this->installedMap = $installedMap;
$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";
$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, $pool, $installedMap, $this->learnedPool)."\n";
if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
$hasExtensionProblems = true;

@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\Util\Url;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Cache;
@ -434,7 +435,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$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)

@ -389,14 +389,14 @@ 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();
$solver = null;
} 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($e->getMessage());
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
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);
}
@ -529,14 +529,14 @@ 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);
$solver = null;
} 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($e->getMessage());
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
return max(1, $e->getCode());
}
@ -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;
@ -602,7 +602,7 @@ class Installer
}
} 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($e->getMessage());
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
return max(1, $e->getCode());
}
@ -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()])) {

@ -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;
}

@ -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())])) {

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

@ -34,6 +34,7 @@ use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Util\Http\Response;
use Composer\Util\MetadataMinifier;
use Composer\Util\Url;
use React\Promise\Util as PromiseUtil;
/**
@ -52,6 +53,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
protected $cache;
protected $notifyUrl;
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 $providersUrl;
protected $availablePackages;
@ -125,6 +128,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->loop = new Loop($this->httpDownloader);
}
public function getRepoName()
{
return 'composer repo ('.Url::sanitize($this->url).')';
}
public function getRepoConfig()
{
return $this->repoConfig;
@ -411,6 +419,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
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()
{
$this->loadRootServerFile();
@ -805,6 +824,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->hasProviders = true;
}
if (!empty($data['providers-api'])) {
$this->providersApiUrl = $data['providers-api'];
}
return $this->rootData = $data;
}

@ -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
*

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

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

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

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

@ -20,6 +20,7 @@ use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Url;
/**
* 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();
}
public function getRepoName()
{
return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
}
public function getRepoConfig()
{
return $this->repoConfig;

@ -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;

@ -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;
}

@ -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();
}

@ -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,10 +52,10 @@ class RepositorySet
private $acceptableStabilities;
private $stabilityFlags;
protected $rootRequires;
private $rootRequires;
/** @var Pool */
private $pool;
/** @var bool */
private $locked = false;
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
*
@ -74,7 +92,7 @@ class RepositorySet
*/
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.");
}
@ -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();
@ -123,6 +158,19 @@ class RepositorySet
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)
{
return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability);
@ -135,7 +183,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) {
@ -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"?
@ -162,13 +212,4 @@ class RepositorySet
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;
}
}

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

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

@ -255,15 +255,4 @@ class AuthHelper
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);
}
}

@ -362,27 +362,16 @@ class Git
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)
{
// git might delete a directory when it fails and php will not know
clearstatcache();
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));
}
/**

@ -72,23 +72,12 @@ class Hg
$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)
{
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));
}
}

@ -195,7 +195,7 @@ class CurlDownloader
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
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));
@ -254,12 +254,12 @@ class CurlDownloader
$contents = stream_get_contents($job['bodyHandle']);
}
$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 {
rewind($job['bodyHandle']);
$contents = stream_get_contents($job['bodyHandle']);
$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']);
@ -362,7 +362,7 @@ class CurlDownloader
}
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;
}

@ -246,7 +246,7 @@ class RemoteFilesystem
$actualContextOptions = stream_context_get_options($ctx);
$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);
// 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->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;

@ -102,4 +102,21 @@ class Url
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;
}
}

@ -143,12 +143,15 @@ class RuleSetTest extends TestCase
$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;
$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, $pool));
}
}

@ -99,8 +99,11 @@ class RuleTest extends TestCase
$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'));
$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));
}
}

@ -34,6 +34,7 @@ class SolverTest extends TestCase
protected $request;
protected $policy;
protected $solver;
protected $pool;
public function setUp()
{
@ -82,7 +83,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, $this->pool));
}
}
@ -651,9 +652,9 @@ class SolverTest extends TestCase
$msg = "\n";
$msg .= " Problem 1\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";
$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 .= " Problem 1\n";
$msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n";
$msg .= " - A 1.0 requires b >= 2.0 -> no matching package found.\n\n";
$msg .= "Potential causes:\n";
$msg .= " - A typo in the package name\n";
$msg .= " - The package is not available in a stable-enough version according to your minimum-stability setting\n";
$msg .= " see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n";
$msg .= " - It's a private package and you forgot to add a custom repository to find it\n\n";
$msg .= "Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
$this->assertEquals($msg, $e->getMessage());
$msg .= " - A 1.0 requires b >= 2.0 -> found B[1.0] but it does not match your constraint.\n";
$this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool));
}
}
@ -731,10 +726,10 @@ class SolverTest extends TestCase
$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 .= " - 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 .= " - 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()
{
$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)

@ -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].

@ -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--

@ -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--

@ -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--

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

@ -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--

@ -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--

@ -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--

@ -41,7 +41,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1
- 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:
- A typo in the package name

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

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

@ -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));
}
}

@ -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'),
);
}
/**
* @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'),
);
}
}

Loading…
Cancel
Save