From 1f37d1c1d57fb709833fa80d6aa0c514f638bf8a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 2 Jun 2021 10:02:34 +0200 Subject: [PATCH] Add better error reporting for cases where a package conflicts with a replace and not directly a package, fixes #9834 --- src/Composer/DependencyResolver/Rule.php | 35 +++++++++++++- ...against-provided-by-dep-package-works.test | 39 +++++++++++++++ ...nflict-against-provided-package-works.test | 40 ++++++++++++++++ ...ainst-replaced-by-dep-package-problem.test | 48 +++++++++++++++++++ ...lict-against-replaced-package-problem.test | 45 +++++++++++++++++ 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/conflict-against-provided-by-dep-package-works.test create mode 100644 tests/Composer/Test/Fixtures/installer/conflict-against-provided-package-works.test create mode 100644 tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test create mode 100644 tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index d29e4c57d..86abe994f 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -211,7 +211,40 @@ abstract class Rule $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0])); $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1])); - return $package2->getPrettyString().' conflicts with '.$package1->getPrettyString().'.'; + $conflictTarget = $package1->getPrettyString(); + if ($reasonData = $this->getReasonData()) { + // swap literals if they are not in the right order with package2 being the conflicter + if ($reasonData->getSource() === $package1->getName()) { + list($package2, $package1) = array($package1, $package2); + } + + // if the conflict is not directly against the package but something it provides/replaces, + // we try to find that link to display a better message + if ($reasonData->getTarget() !== $package1->getName()) { + $provideType = null; + $provided = null; + foreach ($package1->getProvides() as $provide) { + if ($provide->getTarget() === $reasonData->getTarget()) { + $provideType = 'provides'; + $provided = $provide->getPrettyConstraint(); + break; + } + } + foreach ($package1->getReplaces() as $replace) { + if ($replace->getTarget() === $reasonData->getTarget()) { + $provideType = 'replaces'; + $provided = $replace->getPrettyConstraint(); + break; + } + } + if (null !== $provideType) { + $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')'; + } + } + } + + + return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.'; case self::RULE_PACKAGE_REQUIRES: $sourceLiteral = array_shift($literals); diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-provided-by-dep-package-works.test b/tests/Composer/Test/Fixtures/installer/conflict-against-provided-by-dep-package-works.test new file mode 100644 index 000000000..76e60d23e --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-provided-by-dep-package-works.test @@ -0,0 +1,39 @@ +--TEST-- +Test that a conflict against a name that is provided by a dependency does not error +--COMPOSER-- +{ + "version": "1.2.3", + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "conflicting/pkg", + "version": "1.0.0", + "conflict": { + "provided/pkg2": "2.*" + } + }, + { + "name": "provider/pkg", + "version": "1.0.0", + "provide": { "provided/pkg2": "2.*" } + } + ] + } + ], + "require": { + "conflicting/pkg": "*", + "provider/pkg": "*" + } +} + +--RUN-- +update + +--EXPECT-EXIT-CODE-- +0 + +--EXPECT-- +Installing conflicting/pkg (1.0.0) +Installing provider/pkg (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-provided-package-works.test b/tests/Composer/Test/Fixtures/installer/conflict-against-provided-package-works.test new file mode 100644 index 000000000..e5a3ca48f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-provided-package-works.test @@ -0,0 +1,40 @@ +--TEST-- +Test that a conflict against a name that provided by the root does not error +--COMPOSER-- +{ + "version": "1.2.3", + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "conflicting/pkg", + "version": "1.0.0", + "conflict": { + "provided/pkg": "2.*" + } + }, + { + "name": "provider/pkg", + "version": "1.0.0", + "provide": { "provided/pkg2": "2.0.5" } + } + ] + } + ], + "require": { + "conflicting/pkg": "*" + }, + "provide": { + "provided/pkg": "2.*" + } +} + +--RUN-- +update + +--EXPECT-EXIT-CODE-- +0 + +--EXPECT-- +Installing conflicting/pkg (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test new file mode 100644 index 000000000..81035601a --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-by-dep-package-problem.test @@ -0,0 +1,48 @@ +--TEST-- +Test that a conflict against a name that is only replaced by a dependency correctly highlights the issue +--COMPOSER-- +{ + "version": "1.2.3", + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "conflicting/pkg", + "version": "1.0.0", + "conflict": { + "replaced/pkg2": ">=2.1" + } + }, + { + "name": "provider/pkg", + "version": "1.0.0", + "replace": { "replaced/pkg2": "2.5" } + } + ] + } + ], + "require": { + "conflicting/pkg": "*", + "provider/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 conflicting/pkg * -> satisfiable by conflicting/pkg[1.0.0]. + - conflicting/pkg 1.0.0 conflicts with replaced/pkg2 >=2.1 (provider/pkg 1.0.0 replaces replaced/pkg2 2.5). + - Root composer.json requires provider/pkg * -> satisfiable by provider/pkg[1.0.0]. + +--EXPECT-- + diff --git a/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test new file mode 100644 index 000000000..b6cca7270 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/conflict-against-replaced-package-problem.test @@ -0,0 +1,45 @@ +--TEST-- +Test that a conflict against a name that is only replaced by root correctly highlights the issue +--COMPOSER-- +{ + "version": "1.2.3", + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "conflicting/pkg", + "version": "1.0.0", + "conflict": { + "replaced/pkg": "2.*" + } + } + ] + } + ], + "require": { + "conflicting/pkg": "*" + }, + "replace": { + "replaced/pkg": "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 + - conflicting/pkg 1.0.0 conflicts with replaced/pkg 2.* (__root__ 1.2.3 replaces replaced/pkg 2.*). + - Root composer.json requires conflicting/pkg * -> satisfiable by conflicting/pkg[1.0.0]. + +--EXPECT-- +