diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index f92a38da3..849268fed 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/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: diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index bcbc47c97..74f0a480f 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/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()); diff --git a/tests/Composer/Test/Fixtures/installer/provider-conflicts.test b/tests/Composer/Test/Fixtures/installer/provider-conflicts.test new file mode 100644 index 000000000..4e9ce54fa --- /dev/null +++ b/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-- + diff --git a/tests/Composer/Test/Fixtures/installer/provider-conflicts2.test b/tests/Composer/Test/Fixtures/installer/provider-conflicts2.test new file mode 100644 index 000000000..18f8f2db6 --- /dev/null +++ b/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-- +