Browse Source

Merge pull request #8859 from Seldaek/deduplicate-problems

Deduplicate require/conflict rules which are the same but for different versions of the same package
Nils Adermann 4 years ago
parent
commit
ff0717ad66

+ 65 - 20
src/Composer/DependencyResolver/Problem.php

@@ -95,11 +95,42 @@ class Problem
         }
 
         $messages = array();
+        $templates = array();
+        $parser = new VersionParser;
+        $deduplicatableRuleTypes = array(Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT);
         foreach ($reasons as $rule) {
-            $messages[] = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
+            $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
+            if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && preg_match('{^(?P<package>\S+) (?P<version>\S+) (?P<type>requires|conflicts)}', $message, $m)) {
+                $template = preg_replace('{^\S+ \S+ }', '%s%s ', $message);
+                $messages[] = $template;
+                $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2];
+            } else {
+                $messages[] = $message;
+            }
+        }
+
+        $result = array();
+        foreach (array_unique($messages) as $message) {
+            if (isset($templates[$message])) {
+                foreach ($templates[$message] as $package => $versions) {
+                    uksort($versions, 'version_compare');
+                    if (!$isVerbose) {
+                        $versions = self::condenseVersionList($versions, 1);
+                    }
+                    if (count($versions) > 1) {
+                        // remove the s from requires/conflicts to correct grammar
+                        $message = preg_replace('{^(%s%s (?:require|conflict))s}', '$1', $message);
+                        $result[] = sprintf($message, $package, '['.implode(', ', $versions).']');
+                    } else {
+                        $result[] = sprintf($message, $package, ' '.reset($versions));
+                    }
+                }
+            } else {
+                $result[] = $message;
+            }
         }
 
-        return "\n    - ".implode("\n    - ", array_unique($messages));
+        return "\n    - ".implode("\n    - ", $result);
     }
 
     public function isCausedByLock()
@@ -299,32 +330,46 @@ class Problem
             if (isset($package['versions'][VersionParser::DEV_MASTER_ALIAS]) && isset($package['versions']['dev-master'])) {
                 unset($package['versions'][VersionParser::DEV_MASTER_ALIAS]);
             }
-            if (!$isVerbose && count($package['versions']) > 4) {
-                uksort($package['versions'], 'version_compare');
-                $filtered = array();
-                $byMajor = array();
-                foreach ($package['versions'] as $version => $pretty) {
-                    $byMajor[preg_replace('{^(\d+)\..*}', '$1', $version)][] = $pretty;
-                }
-                foreach ($byMajor as $versions) {
-                    if (count($versions) > 4) {
-                        $filtered[] = $versions[0];
-                        $filtered[] = '...';
-                        $filtered[] = $versions[count($versions) - 1];
-                    } else {
-                        $filtered = array_merge($filtered, $versions);
-                    }
-                }
 
-                $package['versions'] = $filtered;
-            }
+            uksort($package['versions'], 'version_compare');
 
+            if (!$isVerbose) {
+                $package['versions'] = self::condenseVersionList($package['versions'], 4);
+            }
             $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
         }
 
         return implode(', ', $prepared);
     }
 
+    /**
+     * @param string[] $versions an array of pretty versions, with normalized versions as keys
+     * @return list<string> a list of pretty versions and '...' where versions were removed
+     */
+    private static function condenseVersionList(array $versions, $max)
+    {
+        if (count($versions) <= $max) {
+            return $versions;
+        }
+
+        $filtered = array();
+        $byMajor = array();
+        foreach ($versions as $version => $pretty) {
+            $byMajor[preg_replace('{^(\d+)\..*}', '$1', $version)][] = $pretty;
+        }
+        foreach ($byMajor as $versionsForMajor) {
+            if (count($versionsForMajor) > $max) {
+                $filtered[] = $versionsForMajor[0];
+                $filtered[] = '...';
+                $filtered[] = $versionsForMajor[count($versionsForMajor) - 1];
+            } else {
+                $filtered = array_merge($filtered, $versionsForMajor);
+            }
+        }
+
+        return $filtered;
+    }
+
     private static function hasMultipleNames(array $packages)
     {
         $name = null;

+ 1 - 1
tests/Composer/Test/Fixtures/installer/alias-solver-problems.test

@@ -47,7 +47,7 @@ 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].
+    - You can only install one version of a package, so only one of these can be installed: d/d[1.0.0, 2.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].

+ 48 - 0
tests/Composer/Test/Fixtures/installer/deduplicate-solver-problems.test

@@ -0,0 +1,48 @@
+--TEST--
+Test the error output of solver problems is deduplicated.
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "package/a", "version": "2.0.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.0.1", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.0.2", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.0.3", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.1.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.2.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.0" } },
+                { "name": "missing/dep", "version": "2.0.0" }
+            ]
+        }
+    ],
+    "require": {
+        "package/a": "*"
+    }
+}
+
+--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
+    - package/a[2.0.0, ..., 2.6.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint.
+    - Root composer.json requires package/a * -> satisfiable by package/a[2.0.0, ..., 2.6.0].
+
+--EXPECT--
+

+ 4 - 4
tests/Composer/Test/Fixtures/installer/provider-conflicts3.test

@@ -39,14 +39,14 @@ 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 thus cannot coexist with it.
+        - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot 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 thus cannot coexist with it.
+        - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot 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 thus cannot 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 thus cannot coexist with it.
+        - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it.
+    - Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot 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].