From a498cf21f3b4d5e5dc306e34d4fb9c921d38cdeb Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Tue, 28 Jun 2022 21:47:14 +0800 Subject: [PATCH] feature: Improve dependency-cycle error to show the cycle (#11519) * Improve dependency-cycle error to show the cycle Original message isn't help when diagnosing problems with a recipe... Now it will print out all the X->Y dependencies that remain after the irrelevant components have been eliminated. Within that list will be the problematic cycle. For example, as a test I introduced a cycle into my prototype VTK recipe, I added: self.cpp_info.components["vtksys"].requires.append("CommonDataModel") The cycle would be: vtksys -> CommonDataModel -> CommonCore -> vtksys The message was a bit more verbose than that, but enough to be helpful: ERROR: There is a dependency loop in 'self.cpp_info.components' requires: CommonTransforms requires CommonMath CommonDataModel requires CommonCore CommonMath requires CommonCore CommonCore requires vtksys CommonTransforms requires CommonCore CommonCore requires kwiml CommonDataModel requires CommonMath vtksys requires CommonDataModel CommonDataModel requires CommonTransforms CommonMath requires kissfft * add test Co-authored-by: memsharded --- conans/model/build_info.py | 9 +++++++- conans/test/integration/test_components.py | 25 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 conans/test/integration/test_components.py diff --git a/conans/model/build_info.py b/conans/model/build_info.py index a29b09bda1f..ee5478825b8 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -608,8 +608,15 @@ def _get_sorted_components(self): del components[comp_name] break else: + dset = set() + for comp_name, comp in components.items(): + for dep_name, dep in components.items(): + for require in self._filter_component_requires(dep.requires): + if require == comp_name: + dset.add(" {} requires {}".format(dep_name, comp_name)) + dep_mesg = "\n".join(dset) raise ConanException("There is a dependency loop in " - "'self.cpp_info.components' requires") + "'self.cpp_info.components' requires:\n{}".format(dep_mesg)) self._sorted_components = ordered else: # If components do not have requirements, keep them in the same order self._sorted_components = self._cpp_info.components diff --git a/conans/test/integration/test_components.py b/conans/test/integration/test_components.py new file mode 100644 index 00000000000..202dae8d338 --- /dev/null +++ b/conans/test/integration/test_components.py @@ -0,0 +1,25 @@ +import textwrap + +from conans.test.utils.tools import TestClient + + +def test_components_cycles(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + + class TestcycleConan(ConanFile): + name = "testcycle" + version = "1.0" + + def package_info(self): + self.cpp_info.components["c"].requires = ["b"] + self.cpp_info.components["b"].requires = ["a"] + self.cpp_info.components["a"].requires = ["c"] # cycle! + """) + c.save({"conanfile.py": conanfile}) + c.run("create .", assert_error=True) + assert "ERROR: There is a dependency loop in 'self.cpp_info.components' requires:" in c.out + assert "a requires c" + assert "b requires a" + assert "c rquires b"