Преглед изворни кода

Merge pull request #8690 from Seldaek/solver-problems

Error reporting improvements
Nils Adermann пре 5 година
родитељ
комит
cb68fc3134

+ 12 - 2
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

+ 14 - 4
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;
+    }
 }

+ 10 - 3
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 <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
         }

+ 10 - 2
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);
                 }
             }
         }

+ 4 - 2
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;

+ 57 - 0
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--
+

+ 54 - 0
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--
+