diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py index 3f4f8ada726..bf591c461ce 100644 --- a/conan/cli/commands/graph.py +++ b/conan/cli/commands/graph.py @@ -14,9 +14,8 @@ from conan.internal.deploy import do_deploys from conans.client.graph.graph import BINARY_MISSING from conans.client.graph.install_graph import InstallGraph -from conans.errors import ConanConnectionError, NotFoundException +from conans.errors import NotFoundException from conans.model.recipe_ref import ref_matches, RecipeReference -from conans.model.version_range import VersionRange def explain_formatter_text(data): @@ -214,10 +213,7 @@ def graph_info(conan_api, parser, subparser, *args): remotes, args.update, check_updates=args.check_updates) print_graph_basic(deps_graph) - if deps_graph.error: - ConanOutput().info("Graph error", Color.BRIGHT_RED) - ConanOutput().info(" {}".format(deps_graph.error), Color.BRIGHT_RED) - else: + if not deps_graph.error: conan_api.graph.analyze_binaries(deps_graph, args.build, remotes=remotes, update=args.update, lockfile=lockfile) print_graph_packages(deps_graph) diff --git a/conans/client/graph/graph_error.py b/conans/client/graph/graph_error.py index b29e84fb24f..cf2497e355d 100644 --- a/conans/client/graph/graph_error.py +++ b/conans/client/graph/graph_error.py @@ -25,19 +25,15 @@ def serialize(self): "require": self.require.serialize()}} def __str__(self): - if self.node.ref is not None and self.base_previous.ref is not None: - return f"Version conflict: {self.node.ref}->{self.require.ref}, " \ - f"{self.base_previous.ref}->{self.prev_require.ref}." - else: - conflicting_node = self.node.ref or self.base_previous.ref - conflicting_node_msg = "" - if conflicting_node is not None: - conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n" - return f"Version conflict: " \ - f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \ - f"{conflicting_node_msg}" \ - f"\nRun conan graph info with your recipe and add --format=html " \ - f"to inspect the graph errors in an easier to visualize way." + conflicting_node = self.node.ref or self.base_previous.ref + conflicting_node_msg = "" + if conflicting_node is not None: + conflicting_node_msg = f"\nConflict originates from {conflicting_node}\n" + return f"Version conflict: " \ + f"Conflict between {self.require.ref} and {self.prev_require.ref} in the graph." \ + f"{conflicting_node_msg}" \ + f"\nRun 'conan graph info ... --format=html > graph.html' " \ + f"and open 'graph.html' to inspect the conflict graphically." class GraphLoopError(GraphError): diff --git a/conans/test/integration/command/info/test_graph_info_graphical.py b/conans/test/integration/command/info/test_graph_info_graphical.py index 29dfe14b494..6637f2299a4 100644 --- a/conans/test/integration/command/info/test_graph_info_graphical.py +++ b/conans/test/integration/command/info/test_graph_info_graphical.py @@ -131,7 +131,7 @@ def test_graph_info_html_error_reporting_output(): tc.run("graph info --requires=math/1.0 --requires=ui/1.0 --format=html", assert_error=True, redirect_stdout="graph.html") - assert "ERROR: Version conflict:" in tc.out # check that it doesn't crash + assert "ERROR: Version conflict:" in tc.out # check that it doesn't crash # change order, just in case tc.run("graph info --requires=ui/1.0 --requires=math/1.0 --format=html", assert_error=True, @@ -144,3 +144,20 @@ def test_graph_info_html_error_reporting_output(): assert "ERROR: Version conflict:" in tc.out # check that it doesn't crash # Check manually # tc.run_command(f"{tc.current_folder}/graph.html") + + +def test_graph_conflict_diamond(): + c = TestClient() + c.save({"math/conanfile.py": GenConanfile("math"), + "engine/conanfile.py": GenConanfile("engine", "1.0").with_requires("math/1.0"), + "ai/conanfile.py": GenConanfile("ai", "1.0").with_requires("math/1.0.1"), + "game/conanfile.py": GenConanfile("game", "1.0").with_requires("engine/1.0", "ai/1.0"), + }) + c.run("create math --version=1.0") + c.run("create math --version=1.0.1") + c.run("create math --version=1.0.2") + c.run("create engine") + c.run("create ai") + c.run("graph info game --format=html", assert_error=True, redirect_stdout="graph.html") + # check that it doesn't crash + assert "ERROR: Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph." in c.out diff --git a/conans/test/integration/graph/conflict_diamond_test.py b/conans/test/integration/graph/conflict_diamond_test.py index f55336fda38..462bfecddcf 100644 --- a/conans/test/integration/graph/conflict_diamond_test.py +++ b/conans/test/integration/graph/conflict_diamond_test.py @@ -23,7 +23,7 @@ def test_version_diamond_conflict(self): c.run("create engine") c.run("create ai") c.run("install game", assert_error=True) - assert "Version conflict: ai/1.0->math/1.0.1, game/1.0->math/1.0" in c.out + assert "Version conflict: Conflict between math/1.0.1 and math/1.0 in the graph" in c.out # This shouldnt error, so we are able to diagnose our dependency graph # The UX still need to be improved, but this is start c.run("graph info game --filter=requires", assert_error=True) diff --git a/conans/test/integration/lockfile/test_graph_overrides.py b/conans/test/integration/lockfile/test_graph_overrides.py index bae9f6817a4..6c64ecb1a88 100644 --- a/conans/test/integration/lockfile/test_graph_overrides.py +++ b/conans/test/integration/lockfile/test_graph_overrides.py @@ -315,7 +315,7 @@ def test_introduced_conflict(override, force): }) c.run("graph info pkgd --lockfile=pkgd/conan.lock --lockfile-partial", assert_error=True) - assert "Version conflict: pkgc/0.1->pkga/[>=0.2 <0.3], pkgd/0.1->pkga/0.1" in c.out + assert "Version conflict: Conflict between pkga/[>=0.2 <0.3] and pkga/0.1 in the graph" in c.out # Resolve the conflict with an override or force c.save({"pkgd/conanfile.py": GenConanfile("pkgd", "0.1").with_requirement("pkgb/0.1") .with_requirement("pkgc/0.1")