Skip to content
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
4 changes: 4 additions & 0 deletions conans/cli/formatters/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def _format_resolved(title, reqs_to_print):
reason = f": {reason}" if reason else ""
output.info(" {}{}".format(d, reason), Color.BRIGHT_CYAN)

if graph.error:
output.info("Graph error", Color.BRIGHT_RED)
output.info(" {}".format(graph.error), Color.BRIGHT_RED)


def print_graph_packages(graph):
# I am excluding the "download"-"cache" or remote information, that is not
Expand Down
29 changes: 4 additions & 25 deletions conans/client/graph/graph_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


class GraphError(ConanException):
# TODO: refactor into multiple classes, do not do type by attribute "kind"
LOOP = "graph loop"
VERSION_CONFLICT = "version conflict"
PROVIDE_CONFLICT = "provide conflict"
Expand All @@ -16,6 +17,9 @@ def __str__(self):
# TODO: Nicer error reporting
if self.kind == GraphError.MISSING_RECIPE:
return f"Package '{self.require.ref}' not resolved: {self.missing_error}"
elif self.kind == GraphError.VERSION_CONFLICT:
return f"Version conflict: {self.node.ref}->{self.require.ref}, "\
f"{self.base_previous.ref}->{self.prev_require.ref}."
return self.kind

@staticmethod
Expand Down Expand Up @@ -81,28 +85,3 @@ def conflict_config(node, require, prev_node, prev_require, base_previous,
if prev_node:
prev_node.error = result
return result

def report_graph_error(self):
# FIXME: THis is completely broken and useless
# print("REPORTING GRAPH ERRORS")
conflict_nodes = [n for n in self.nodes if n.conflict]
# print("PROBLEMATIC NODES ", conflict_nodes)
for node in conflict_nodes: # At the moment there should be only 1 conflict at most
conflict = node.conflict
# print("CONFLICT ", conflict)
if conflict[0] == GraphError.LOOP:
loop_ref = node.ref
parent = node.dependants[0]
parent_ref = parent.src.ref
msg = "Loop detected in context host: '{}' requires '{}' which "\
"is an ancestor too"
msg = msg.format(parent_ref, loop_ref)
raise ConanException(msg)
elif conflict[0] == GraphError.VERSION_CONFLICT:
raise ConanException(
"There was a version conflict building the dependency graph")
elif conflict[0] == GraphError.PROVIDE_CONFLICT:
raise ConanException(
"There was a provides conflict building the dependency graph")

raise ConanException("Thre was an error in the graph: {}".format(self.error))
89 changes: 0 additions & 89 deletions conans/client/graph/printer.py

This file was deleted.

2 changes: 2 additions & 0 deletions conans/model/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ def aggregate(self, other):
self.libs |= other.libs
self.run = self.run or other.run
self.visible |= other.visible
# The force trait is also defined from an override
self.force |= other.force or other.override
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core fix of this PR, the rests are tests, output and removing dead code

# TODO: self.package_id_mode => Choose more restrictive?

def transform_downstream(self, pkg_type, require, dep_pkg_type):
Expand Down
2 changes: 1 addition & 1 deletion conans/test/functional/revisions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_diamond_revisions_conflict(self):
self.c_v2.create(project,
conanfile=GenConanfile().with_requirement(lib2).with_requirement(lib3),
assert_error=True)
self.assertIn("ERROR: version conflict", self.c_v2.out)
self.assertIn("ERROR: Version conflict", self.c_v2.out)
# self.assertIn("Different revisions of {} has been requested".format(lib1), self.c_v2.out)

def test_alias_to_a_rrev(self):
Expand Down
129 changes: 42 additions & 87 deletions conans/test/integration/graph/conflict_diamond_test.py
Original file line number Diff line number Diff line change
@@ -1,91 +1,46 @@
import os
import textwrap
import unittest
from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient

import pytest

from conans.util.env import environment_update
from conans.paths import CONANFILE
from conans.test.utils.tools import TestClient, load
import json


@pytest.mark.xfail(reason="Conflict Output have changed")
class ConflictDiamondTest(unittest.TestCase):
conanfile = textwrap.dedent("""
from conan import ConanFile

class HelloReuseConan(ConanFile):
name = "%s"
version = "%s"
requires = %s
""")

def _export(self, name, version, deps=None, export=True):
deps = ", ".join(['"%s"' % d for d in deps or []]) or '""'
conanfile = self.conanfile % (name, version, deps)
files = {CONANFILE: conanfile}
self.client.save(files, clean_first=True)
if export:
self.client.run("export . --user=lasote --channel=stable")

def setUp(self):
self.client = TestClient()
self._export("hello0", "0.1")
self._export("hello0", "0.2")
self._export("hello1", "0.1", ["hello0/0.1@lasote/stable"])
self._export("Hello2", "0.1", ["hello0/0.2@lasote/stable"])

def test_conflict(self):
""" There is a conflict in the graph: branches with requirement in different
version, Conan will raise
class TestConflictDiamondTest:
def test_version_diamond_conflict(self):
"""
self._export("Hello3", "0.1", ["hello1/0.1@lasote/stable", "hello2/0.1@lasote/stable"],
export=False)
self.client.run("install . --build missing", assert_error=True)
self.assertIn("Conflict in hello2/0.1@lasote/stable:\n"
" 'hello2/0.1@lasote/stable' requires 'hello0/0.2@lasote/stable' "
"while 'hello1/0.1@lasote/stable' requires 'hello0/0.1@lasote/stable'.\n"
" To fix this conflict you need to override the package 'hello0' in "
"your root package.", self.client.out)
self.assertNotIn("Generated conaninfo.txt", self.client.out)

def test_override_silent(self):
""" There is a conflict in the graph, but the consumer project depends on the conflicting
library, so all the graph will use the version from the consumer project
test that we obtain a version conflict with a diamond, and that we can fix it by
defining an override in the "game" consumer
"""
self._export("Hello3", "0.1",
["hello1/0.1@lasote/stable", "hello2/0.1@lasote/stable",
"hello0/0.1@lasote/stable"], export=False)
self.client.run("install . --build missing", assert_error=False)
self.assertIn("hello2/0.1@lasote/stable: requirement hello0/0.2@lasote/stable overridden"
" by Hello3/0.1 to hello0/0.1@lasote/stable",
self.client.out)


@pytest.mark.xfail(reason="UX conflict error to be completed")
def test_create_werror():
client = TestClient()
client.save({"conanfile.py": """from conan import ConanFile
class Pkg(ConanFile):
pass
"""})
client.run("export . --name=LibA --version=0.1 --user=user --channel=channel")
client.run("export conanfile.py --name=LibA --version=0.2 --user=user --channel=channel")
client.save({"conanfile.py": """from conan import ConanFile
class Pkg(ConanFile):
requires = "LibA/0.1@user/channel"
"""})
client.run("export ./ --name=LibB --version=0.1 --user=user --channel=channel")
client.save({"conanfile.py": """from conan import ConanFile
class Pkg(ConanFile):
requires = "LibA/0.2@user/channel"
"""})
client.run("export . --name=LibC --version=0.1 --user=user --channel=channel")
client.save({"conanfile.py": """from conan import ConanFile
class Pkg(ConanFile):
requires = "LibB/0.1@user/channel", "LibC/0.1@user/channel"
"""})
client.run("create ./conanfile.py consumer/0.1@lasote/testing", assert_error=True)
self.assertIn("ERROR: Conflict in LibC/0.1@user/channel",
client.out)
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("install game", assert_error=True)
assert "Version conflict: ai/1.0->math/1.0.1, game/1.0->math/1.0" in c.out

def _game_conanfile(version, reverse=False):
if reverse:
return GenConanfile("game", "1.0")\
.with_requirement(f"math/{version}", override=True)\
.with_requirement("engine/1.0")\
.with_requirement("ai/1.0")
else:
return GenConanfile("game", "1.0").with_requirement("engine/1.0") \
.with_requirement("ai/1.0") \
.with_requirement(f"math/{version}", override=True)

for v in ("1.0", "1.0.1", "1.0.2"):
c.save({"game/conanfile.py": _game_conanfile(v)})
c.run("install game")
c.assert_listed_require({f"math/{v}": "Cache"})

# Check that order of requirements doesn't affect
for v in ("1.0", "1.0.1", "1.0.2"):
c.save({"game/conanfile.py": _game_conanfile(v, reverse=True)})
c.run("install game")
c.assert_listed_require({f"math/{v}": "Cache"})
13 changes: 0 additions & 13 deletions conans/test/integration/graph/core/graph_manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,19 +884,6 @@ def test_diamond(self):
self._check_node(libb, "libb/0.1#123", deps=[liba], dependents=[app])
self._check_node(liba, "liba/0.2#123", dependents=[libb, app])

# FIXME: Remove
def test_a_ver_si_me_entero(self):
# consumer -> dep1 -> dep2
# \-> dep3
self.recipe_cache("dep2/0")
self.recipe_cache("dep1/0", ["dep2/0"])
self.recipe_cache("dep3/0")
consumer = self.consumer_conanfile(GenConanfile("consumer", "0").with_require("dep1/0")
.with_require("dep3/0"))
deps_graph = self.build_consumer(consumer)

self.assertEqual(4, len(deps_graph.nodes))

def test_diamond_conflict(self):
# app -> libb0.1 -> liba0.2 (overriden to lib0.2)
# \-> --------- ->/
Expand Down
2 changes: 1 addition & 1 deletion conans/test/integration/remote/download_retries_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ def get(self, *args, **kwargs):
requester_class=BuggyRequester)
client.run("install --reference=pkg/0.1@lasote/stable", assert_error=True)
self.assertEqual(str(client.out).count("Waiting 0 seconds to retry..."), 2)
self.assertEqual(str(client.out).count("Error 200 downloading"), 3)
self.assertEqual(str(client.out).count("Error 200 downloading"), 4)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This increments by 1 because the detailed information about the Graph error includes the error downloading message.