浏览代码

Improve error reporting for conflicts/replaces of various kinds, fixes #7254

Jordi Boggiano 5 年之前
父节点
当前提交
1d4cdb60d0

+ 42 - 1
src/Composer/DependencyResolver/Rule.php

@@ -188,11 +188,52 @@ abstract class Rule
                 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]);
+                    $conflictingNames = array_values(array_intersect($package1->getNames(), $package2->getNames()));
+                    $provideClash = count($conflictingNames) > 1 ? '['.implode(', ', $conflictingNames).']' : $conflictingNames[0];
+
+                    if ($conflictingNames && isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
+                        // swap vars so the if below passes
+                        $tmp = $package2;
+                        $package2 = $package1;
+                        $package1 = $tmp;
+                    }
+                    if ($conflictingNames && !isset($installedMap[$package1->id]) && isset($installedMap[$package2->id])) {
+                        return $package1->getPrettyString().' can not be installed as that would require removing '.$package2->getPrettyString().'. They both provide '.$provideClash.' and can thus not coexist.';
+                    }
+                    if (!isset($installedMap[$package1->id]) && !isset($installedMap[$package2->id])) {
+                        if ($conflictingNames) {
+                            return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'. They both provide '.$provideClash.' and can thus not coexist.';
+                        }
+
+                        return 'Only one of these can be installed: '.$package1->getPrettyString().', '.$package2->getPrettyString().'.';
+                    }
+                }
+
                 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) . '.';
+                $conflictingNames = null;
+                $allNames = array();
+                foreach ($literals as $literal) {
+                    $package = $pool->literalToPackage($literal);
+                    if ($conflictingNames === null) {
+                        $conflictingNames = $package->getNames();
+                    } else {
+                        $conflictingNames = array_values(array_intersect($conflictingNames, $package->getNames()));
+                    }
+                    $allNames = array_unique(array_merge($allNames, $package->getNames()));
+                }
+                $provideClash = count($conflictingNames) > 1 ? '['.implode(', ', $conflictingNames).']' : $conflictingNames[0];
+
+                if ($conflictingNames && count($allNames) > 1) {
+                    return 'Only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '. They all provide '.$provideClash.' and can thus not coexist.';
+                }
+
+                return 'Only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '.';
             case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
                 return $ruleText;
             case self::RULE_LEARNED:

+ 1 - 1
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -725,7 +725,7 @@ 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 .= "    - 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());

+ 49 - 0
tests/Composer/Test/Fixtures/installer/provider-conflicts.test

@@ -0,0 +1,49 @@
+--TEST--
+Test that providers provided by a dependent and root package cause a conflict
+--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 provide [root-provided/transitive-provided, root-replaced/transitive-provided, root-provided/transitive-replaced, root-replaced/transitive-replaced] and can thus not coexist.
+    - Root composer.json requires provider/pkg * -> satisfiable by provider/pkg[1.0.0].
+
+--EXPECT--
+

+ 45 - 0
tests/Composer/Test/Fixtures/installer/provider-conflicts2.test

@@ -0,0 +1,45 @@
+--TEST--
+Test that providers 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. They both provide third/pkg and can thus not coexist.
+    - Root composer.json requires replacer/pkg * -> satisfiable by replacer/pkg[1.0.0].
+
+--EXPECT--
+