Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix --format=html output for graphs with conflict errors #14190

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions conan/cli/formatters/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 37 additions & 16 deletions conan/cli/formatters/graph/info_graph_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<h3>{{ error["label"] }}</h3><p>This node creates a conflict in the dependency graph with {{ error["prev_node"]["ref"] }}</p>',
fulllabel: '<h3>{{ error["context"].require.ref }}</h3><p>This node creates a conflict in the dependency graph</p>',
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);
Expand Down
36 changes: 27 additions & 9 deletions conans/test/integration/command/info/test_graph_info_graphical.py
Original file line number Diff line number Diff line change
Expand Up @@ -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