diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 13d0ffd4f..f0dba7567 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -13,7 +13,9 @@ namespace Composer\DependencyResolver; use Composer\Package\CompletePackageInterface; +use Composer\Package\AliasPackage; use Composer\Repository\RepositorySet; +use Composer\Repository\LockArrayRepository; use Composer\Semver\Constraint\Constraint; /** @@ -96,7 +98,7 @@ class Problem $messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool); } - return "\n - ".implode("\n - ", $messages); + return "\n - ".implode("\n - ", array_unique($messages)); } public function isCausedByLock() @@ -221,6 +223,14 @@ class Problem } } + $nonLockedPackages = array_filter($packages, function ($p) { + return !$p->getRepository() instanceof LockArrayRepository; + }); + + if (!$nonLockedPackages) { + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages).' in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file.'); + } + 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.'); } @@ -281,7 +291,7 @@ class Problem $prepared = array(); foreach ($packages as $package) { $prepared[$package->getName()]['name'] = $package->getPrettyName(); - $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion(); + $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : ''); } foreach ($prepared as $name => $package) { // remove the implicit dev-master alias to avoid cruft in the display diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index 7885efda3..7514cecce 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -15,6 +15,7 @@ namespace Composer\DependencyResolver; use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\PackageInterface; +use Composer\Package\AliasPackage; use Composer\Repository\RepositorySet; /** @@ -153,7 +154,7 @@ abstract class Rule return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages).'.'; case self::RULE_FIXED: - $package = $this->reasonData['package']; + $package = $this->deduplicateMasterAlias($this->reasonData['package']); if ($this->reasonData['lockable']) { return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.'; } @@ -161,14 +162,14 @@ abstract class Rule return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer'; case self::RULE_PACKAGE_CONFLICT: - $package1 = $pool->literalToPackage($literals[0]); - $package2 = $pool->literalToPackage($literals[1]); + $package1 = $this->deduplicateMasterAlias($pool->literalToPackage($literals[0])); + $package2 = $this->deduplicateMasterAlias($pool->literalToPackage($literals[1])); return $package2->getPrettyString().' conflicts with '.$package1->getPrettyString().'.'; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = array_shift($literals); - $sourcePackage = $pool->literalToPackage($sourceLiteral); + $sourcePackage = $this->deduplicateMasterAlias($pool->literalToPackage($sourceLiteral)); $requires = array(); foreach ($literals as $literal) { @@ -279,4 +280,13 @@ abstract class Rule return $names; } + + private function deduplicateMasterAlias(PackageInterface $package) + { + if ($package instanceof AliasPackage && $package->getPrettyVersion() === '9999999-dev') { + $package = $package->getAliasOf(); + } + + return $package; + } } diff --git a/src/Composer/DependencyResolver/SolverProblemsException.php b/src/Composer/DependencyResolver/SolverProblemsException.php index 1dc5be5b8..1821bde91 100644 --- a/src/Composer/DependencyResolver/SolverProblemsException.php +++ b/src/Composer/DependencyResolver/SolverProblemsException.php @@ -34,11 +34,12 @@ class SolverProblemsException extends \RuntimeException public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, $isDevExtraction = false) { $installedMap = $request->getPresentMap(true); - $text = "\n"; $hasExtensionProblems = false; $isCausedByLock = false; - foreach ($this->problems as $i => $problem) { - $text .= " Problem ".($i + 1).$problem->getPrettyString($repositorySet, $request, $pool, $installedMap, $this->learnedPool)."\n"; + + $problems = array(); + foreach ($this->problems as $problem) { + $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $installedMap, $this->learnedPool)."\n"; if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) { $hasExtensionProblems = true; @@ -47,6 +48,12 @@ class SolverProblemsException extends \RuntimeException $isCausedByLock |= $problem->isCausedByLock(); } + $i = 1; + $text = "\n"; + foreach (array_unique($problems) as $problem) { + $text .= " Problem ".($i++).$problem; + } + if (!$isDevExtraction && (strpos($text, 'could not be found') || strpos($text, 'no matching package found'))) { $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n see for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead for further common problems."; } diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 1debf3e30..3e117f7d9 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -173,19 +173,27 @@ class AliasPackage extends BasePackage implements CompletePackageInterface */ protected function replaceSelfVersionDependencies(array $links, $linkType) { + // for self.version requirements, we use the original package's branch name instead of 9999999-dev, to avoid leaking 9999999-dev to users + $prettyVersion = $this->prettyVersion; + if ($prettyVersion === '9999999-dev') { + $prettyVersion = $this->aliasOf->getPrettyVersion(); + } + if (in_array($linkType, array('conflicts', 'provides', 'replaces'), true)) { $newLinks = array(); foreach ($links as $link) { // link is self.version, but must be replacing also the replaced version if ('self.version' === $link->getPrettyConstraint()) { - $newLinks[] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); + $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); } } $links = array_merge($links, $newLinks); } else { foreach ($links as $index => $link) { if ('self.version' === $link->getPrettyConstraint()) { - $links[$index] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion); + $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion); + $constraint->setPrettyString($prettyVersion); } } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index a9ddabf7d..2b31db793 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -247,11 +247,13 @@ class ArrayLoader implements LoaderInterface } if ($aliasNormalized = $this->getBranchAlias($config)) { + $prettyAlias = preg_replace('{(\.9{7})+}', '.x', $aliasNormalized); + if ($package instanceof RootPackageInterface) { - return new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); + return new RootAliasPackage($package, $aliasNormalized, $prettyAlias); } - return new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized)); + return new AliasPackage($package, $aliasNormalized, $prettyAlias); } return $package; diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test new file mode 100644 index 000000000..ce1aea7bb --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems.test @@ -0,0 +1,57 @@ +--TEST-- +Test the error output of solver problems with dev-master aliases. +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + {"name": "a/a", "version": "dev-master", "require": {"d/d": "1.0.0"}}, + {"name": "b/b", "version": "dev-master", "require": {"d/d": "2.0.0"}}, + {"name": "d/d", "version": "1.0.0"}, + {"name": "d/d", "version": "2.0.0"} + ] + } + ], + "require": { + "a/a": "*@dev", + "b/b": "*@dev" + } +} + +--LOCK-- +{ + "packages": [ + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} + +--RUN-- +update a/a b/b + +--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 b/b *@dev -> satisfiable by b/b[dev-master]. + - a/a dev-master requires d/d 1.0.0 -> satisfiable by d/d[1.0.0]. + - You can only install one version of a package, so only one of these can be installed: d/d[2.0.0, 1.0.0]. + - Conclusion: install d/d 2.0.0, learned rules: + - Root composer.json requires b/b *@dev -> satisfiable by b/b[dev-master]. + - b/b dev-master requires d/d 2.0.0 -> satisfiable by d/d[2.0.0]. + - Root composer.json requires a/a *@dev -> satisfiable by a/a[dev-master]. + +--EXPECT-- + diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test new file mode 100644 index 000000000..344c4ce67 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test @@ -0,0 +1,54 @@ +--TEST-- +Test the error output of solver problems with dev-master aliases. +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "locked/pkg", "version": "dev-master", "require": {"locked/dependency": "1.0.0"} } + ] + } + ], + "require": { + "locked/pkg": "*@dev" + } +} + +--LOCK-- +{ + "packages": [ + { "name": "locked/pkg", "version": "dev-master", "require": {"locked/dependency": "1.0.0"} }, + { "name": "locked/dependency", "version": "1.0.0" } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} + +--RUN-- +update locked/dependency + +--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 + - locked/pkg is locked to version dev-master and an update of this package was not requested. + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file. + Problem 2 + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file. + - Root composer.json requires locked/pkg *@dev -> satisfiable by locked/pkg[dev-master]. + +Use the option --with-all-dependencies to allow updates and removals for packages currently locked to specific versions. +--EXPECT-- +