diff --git a/conan/cli/formatters/graph/graph.py b/conan/cli/formatters/graph/graph.py index 73d9598ec12..ea3d692d172 100644 --- a/conan/cli/formatters/graph/graph.py +++ b/conan/cli/formatters/graph/graph.py @@ -105,20 +105,14 @@ def format_graph_html(result): template_folder = os.path.join(conan_api.cache_folder, "templates") user_template = os.path.join(template_folder, "graph.html") template = load(user_template) if os.path.isfile(user_template) else graph_info_html + error = { + "type": "unknown", + "context": graph.error, + "should_highlight_node": lambda node: False + } if isinstance(graph.error, GraphConflictError): - error = { - "label": graph.error.require.ref, - "type": "conflict", - "from": graph.error.node.id or graph.error.base_previous.id, - "prev_node": graph.error.prev_node, - "should_highlight_node": lambda node: node.id == graph.error.node.id - } - else: - error = { - "type": "unknown", - "error:": graph.error, - "should_highlight_node": lambda node: False - } + error["type"] = "conflict" + error["should_highlight_node"] = lambda node: node.id == graph.error.node.id cli_out_write(_render_graph(graph, error, template, template_folder)) if graph.error: raise graph.error diff --git a/conan/cli/formatters/graph/info_graph_html.py b/conan/cli/formatters/graph/info_graph_html.py index 72955242249..394a7ceca2b 100644 --- a/conan/cli/formatters/graph/info_graph_html.py +++ b/conan/cli/formatters/graph/info_graph_html.py @@ -79,32 +79,53 @@ ] {% if error is not none and error["type"] == "conflict" %} - // Add some helpful visualizations for conflicts + // Add error conflict node nodes.push({ id: "{{ error["type"] }}", - label: "{{ error["label"] }}", + label: "{{ error["context"].require.ref }}", shape: "circle", font: { color: "white" }, color: "red", - fulllabel: '
This node creates a conflict in the dependency graph with {{ error["prev_node"]["ref"] }}
', + fulllabel: 'This node creates a conflict in the dependency graph
', shapeProperties: { borderDashes: [5, 5] } }) - {% if error["from"] is not none %} - edges.push({from: {{ error["from"] }}, to: "{{ error["type"] }}", color: "red", dashes: true, title: "Conflict"}) + {% if error["context"].node.id is not none %} + // Add edge from node that introduces the conflict to the new error node + edges.push({from: {{ error["context"].node.id }}, + to: "{{ error["type"] }}", + color: "red", + dashes: true, + title: "Conflict", + physics: false, + color: "red", + arrows: "to;from"}) + {% endif %} + + {% if error["context"].prev_node is none and error["context"].base_previous.id is not none %} + // Add edge from base node to the new error node + edges.push({from: {{ error["context"].base_previous.id }}, + to: "{{ error["type"] }}", + color: "red", + dashes: true, + title: "Conflict", + physics: false, + color: "red", + arrows: "to;from"}) + {% endif %} + + {% if error["context"].prev_node is not none and error["context"].prev_node.id is not none %} + // Add edge from previous node that already had conflicting dependency + edges.push({from: {{ error["context"].prev_node.id }}, + to: "{{ error["type"] }}", + color: "red", + dashes: true, + title: "Conflict", + physics: false, + color: "red", + arrows: "to;from"}) {% endif %} - edges.push({from: {{ error["from"] }}, - to: "{{ error["type"] }}", - dashes: true, - title: "Conflict", - physics: false, - color: "red", - arrows: "to;from"}) - edges.push({from: {{ error["prev_node"].id }}, - to: "{{ error["type"] }}", - color: "red", - title: "Problematic require"}) {% endif %} var nodes = new vis.DataSet(nodes); 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 6d4d5b0f376..be4e5ff635a 100644 --- a/conans/test/integration/command/info/test_graph_info_graphical.py +++ b/conans/test/integration/command/info/test_graph_info_graphical.py @@ -181,13 +181,31 @@ def test_user_templates(): def test_graph_info_html_output(): tc = TestClient() - tc.run("new cmake_lib -d name=lib -d version=1.0") - tc.run("export .") - tc.run("new cmake_lib -d name=lib -d version=2.0 -f") - tc.run("export .") - tc.run("new cmake_lib -d name=ui -d version=1.0 -d requires=lib/1.0 -f") - tc.run("export .") - tc.run("new cmake_lib -d name=math -d version=1.0 -d requires=lib/2.0 -f") - tc.run("export .") + tc.save({"lib/conanfile.py": GenConanfile("lib"), + "ui/conanfile.py": GenConanfile("ui", "1.0").with_requirement("lib/1.0"), + "math/conanfile.py": GenConanfile("math", "1.0").with_requirement("lib/2.0"), + "libiconv/conanfile.py": GenConanfile("libiconv", "1.0").with_requirement("lib/2.0"), + "boost/conanfile.py": GenConanfile("boost", "1.0").with_requirement("libiconv/1.0"), + "openimageio/conanfile.py": GenConanfile("openimageio", "1.0").with_requirement("boost/1.0").with_requirement("lib/1.0")}) + tc.run("export lib/ --version=1.0") + tc.run("export lib/ --version=2.0") + tc.run("export ui") + tc.run("export math") + tc.run("export libiconv") + tc.run("export boost") + tc.run("export openimageio") + tc.run("graph info --requires=math/1.0 --requires=ui/1.0 --format=html", assert_error=True) - assert "// Add some helpful visualizations for conflicts" in tc.out + assert "// Add error conflict node" in tc.out + assert "// Add edge from node that introduces the conflict to the new error node" in tc.out + assert "// Add edge from base node to the new error node" not in tc.out + assert "// Add edge from previous node that already had conflicting dependency" in tc.out + + tc.run("graph info openimageio/ --format=html", assert_error=True) + assert "// Add error conflict node" in tc.out + assert "// Add edge from node that introduces the conflict to the new error node" in tc.out + assert "// Add edge from base node to the new error node" in tc.out + assert "// Add edge from previous node that already had conflicting dependency" not in tc.out + # There used to be a few bugs with weird graphs, check for regressions + assert "jinja2.exceptions.UndefinedError" not in tc.out + assert "from: ," not in tc.out