Skip to content

Commit

Permalink
Ux/components errors (#17771)
Browse files Browse the repository at this point in the history
* improving components errors messages

* runtime error conflict

* remove legacy and duplicated CMakeDeps tests
  • Loading branch information
memsharded authored Feb 14, 2025
1 parent 75fcab7 commit 5857767
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 88 deletions.
15 changes: 10 additions & 5 deletions conan/internal/model/cpp_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,9 @@ def check_component_requires(self, conanfile):
comps = self.required_components
missing_internal = [c[1] for c in comps if c[0] is None and c[1] not in self.components]
if missing_internal:
raise ConanException(f"{conanfile}: Internal components not found: {missing_internal}")
msg = f"{conanfile}: package_info(): There are '(cpp_info/components).requires' to " \
f"other internal components that are not defined: {missing_internal}"
raise ConanException(msg)
external = [c[0] for c in comps if c[0] is not None]
if not external:
return
Expand All @@ -782,13 +784,16 @@ def check_component_requires(self, conanfile):

for e in external:
if e not in direct_dependencies:
raise ConanException(
f"{conanfile}: required component package '{e}::' not in dependencies")
msg = f"{conanfile}: package_info(): There are '(cpp_info/components).requires' " \
f"that includes package '{e}::', but such package is not a a direct " \
f"requirement of the recipe"
raise ConanException(msg)
# TODO: discuss if there are cases that something is required but not transitive
for e in direct_dependencies:
if e not in external:
raise ConanException(
f"{conanfile}: Required package '{e}' not in component 'requires'")
msg = f"{conanfile}: package_info(): The direct dependency '{e}' is not used by " \
f"any '(cpp_info/components).requires'."
raise ConanException(msg)

@property
def required_components(self):
Expand Down
7 changes: 5 additions & 2 deletions conans/client/graph/graph_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,8 @@ def __init__(self, node, conflicting_node):
self.conflicting_node = conflicting_node

def __str__(self):
return f"Runtime Error: Could not process '{self.node.ref}' with " \
f"'{self.conflicting_node.ref}'."
return f"Runtime Conflict Error: There is a conflict between packages that will happen " \
f"at runtime (but not at compile time), like different versions of the same shared " \
f"library that would end in the path. Please check the 'application' and " \
f"'shared-library' package types and requirements with 'run=True' trait in your " \
f"graph: '{self.node.ref}' with '{self.conflicting_node.ref}'."
Original file line number Diff line number Diff line change
Expand Up @@ -116,85 +116,6 @@ def package_info(self):
assert "Component 'top::not-existing' not found in 'top' package requirement" in t.out


# TODO: This is CMakeDeps Independent, move it out of here
def test_unused_requirement(top_conanfile):
""" Requires should include all listed requirements
This error is known when creating the package if the requirement is consumed.
"""
consumer = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
requires = "top/version", "top2/version"
def package_info(self):
self.cpp_info.requires = ["top::other"]
""")
t = TestClient()
t.save({'top.py': top_conanfile, 'consumer.py': consumer})
t.run('create top.py --name=top --version=version')
t.run('create top.py --name=top2 --version=version')
t.run('create consumer.py --name=wrong --version=version', assert_error=True)
assert "ERROR: wrong/version: Required package 'top2' not in component 'requires" in t.out



# TODO: This is CMakeDeps Independent, move it out of here
def test_unused_tool_requirement(top_conanfile):
""" Requires should include all listed requirements
This error is known when creating the package if the requirement is consumed.
"""
consumer = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
requires = "top/version"
tool_requires = "top2/version"
def package_info(self):
self.cpp_info.requires = ["top::other"]
""")
t = TestClient()
t.save({'top.py': top_conanfile, 'consumer.py': consumer})
t.run('create top.py --name=top --version=version')
t.run('create top.py --name=top2 --version=version')
t.run('create consumer.py --name=wrong --version=version')
# This runs without crashing, because it is not chcking that top::other doesn't exist


# TODO: This is CMakeDeps Independent, move it out of here
def test_wrong_requirement(top_conanfile):
""" If we require a wrong requirement, we get a meaninful error.
This error is known when creating the package if the requirement is not there.
"""
consumer = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
requires = "top/version"
def package_info(self):
self.cpp_info.requires = ["top::cmp1", "other::other"]
""")
t = TestClient()
t.save({'top.py': top_conanfile, 'consumer.py': consumer})
t.run('create top.py --name=top --version=version')
t.run('create consumer.py --name=wrong --version=version', assert_error=True)
assert "ERROR: wrong/version: required component package 'other::' not in dependencies" in t.out


# TODO: This is CMakeDeps Independent, move it out of here
def test_missing_internal():
consumer = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
def package_info(self):
self.cpp_info.components["cmp1"].requires = ["other"]
""")
t = TestClient()
t.save({'conanfile.py': consumer})
t.run('create . --name=wrong --version=version', assert_error=True)
assert "ERROR: wrong/version: Internal components not found: ['other']" in t.out


@pytest.mark.tool("cmake")
def test_components_system_libs():
conanfile = textwrap.dedent("""
Expand Down
9 changes: 7 additions & 2 deletions test/integration/graph/core/test_build_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,17 @@ def test_build_require_transitive_shared(self):
self._cache_recipe("zlib/0.2", GenConanfile().with_shared_option(True))
self._cache_recipe("cmake/0.1", GenConanfile().with_require("zlib/0.1"))
self._cache_recipe("mingw/0.1", GenConanfile().with_require("zlib/0.2"))
self._cache_recipe("lib/0.1", GenConanfile().with_tool_requires("cmake/0.1",
"mingw/0.1"))
self._cache_recipe("lib/0.1", GenConanfile().with_tool_requires("cmake/0.1", "mingw/0.1"))
deps_graph = self.build_graph(GenConanfile("app", "0.1").with_require("lib/0.1"),
install=False)

assert type(deps_graph.error) == GraphRuntimeError
expected = "Runtime Conflict Error: There is a conflict between packages that will happen " \
"at runtime (but not at compile time), like different versions of the same " \
"shared library that would end in the path. Please check the 'application' " \
"and 'shared-library' package types and requirements with 'run=True' " \
"trait in your graph: 'mingw/0.1' with 'zlib/0.2'."
assert str(deps_graph.error) == expected

self.assertEqual(6, len(deps_graph.nodes))
app = deps_graph.root
Expand Down
99 changes: 99 additions & 0 deletions test/integration/test_components_error.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import textwrap

import pytest

from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.tools import TestClient


Expand Down Expand Up @@ -54,6 +57,7 @@ class t3Conan(ConanFile):
assert 'list(APPEND t2_FIND_DEPENDENCY_NAMES )' in c.load(f"t3/t2-release-{arch}-data.cmake")
assert not os.path.exists(os.path.join(c.current_folder, "t3/t1-config.cmake"))


def test_verify_get_property_check_type():
c = TestClient(light=True)
conanfile = textwrap.dedent("""
Expand All @@ -68,3 +72,98 @@ def package_info(self):
c.save({"conanfile.py": conanfile})
c.run("create .", assert_error=True)
assert 'The expected type for test_property is "list", but "str" was found' in c.out


@pytest.mark.parametrize("component", [True, False])
def test_unused_requirement(component):
""" Requires should include all listed requirements
This error is known when creating the package if the requirement is consumed.
"""

t = TestClient(light=True)
conanfile = textwrap.dedent(f"""
from conan import ConanFile
class Consumer(ConanFile):
name = "wrong"
version = "version"
requires = "top/version", "top2/version"
def package_info(self):
self.cpp_info{'.components["foo"]' if component else ''}.requires = ["top::other"]
""")
t.save({"top/conanfile.py": GenConanfile().with_package_info({"components": {"cmp1": {"libs": ["top_cmp1"]}}}),
"conanfile.py": conanfile})
t.run('create top --name=top --version=version')
t.run('create top --name=top2 --version=version')
t.run('create .', assert_error=True)
assert "ERROR: wrong/version: package_info(): The direct dependency 'top2' is not used " \
"by any '(cpp_info/components).requires'." in t.out


@pytest.mark.parametrize("component", [True, False])
def test_wrong_requirement(component):
""" If we require a wrong requirement, we get a meaninful error.
This error is known when creating the package if the requirement is not there.
"""
t = TestClient(light=True)
conanfile = textwrap.dedent(f"""
from conan import ConanFile
class Consumer(ConanFile):
name = "wrong"
version = "version"
requires = "top/version"
def package_info(self):
self.cpp_info{'.components["foo"]' if component else ''}.requires = ["top::cmp1", "other::other"]
""")
t.save({"top/conanfile.py": GenConanfile().with_package_info({"components": {"cmp1": {"libs": ["top_cmp1"]}}}),
"conanfile.py": conanfile})
t.run('create top --name=top --version=version')
t.run('create .', assert_error=True)
assert "ERROR: wrong/version: package_info(): There are '(cpp_info/components).requires' " \
"that includes package 'other::', but such package is not a a direct requirement " \
"of the recipe" in t.out


@pytest.mark.parametrize("component", [True, False])
def test_missing_internal(component):
consumer = textwrap.dedent(f"""
from conan import ConanFile
class Recipe(ConanFile):
def package_info(self):
self.cpp_info{'.components["foo"]' if component else ''}.requires = ["other", "another"]
self.cpp_info{'.components["bar"]' if component else ''}.requires = ["other", "another"]
""")
t = TestClient(light=True)
t.save({'conanfile.py': consumer})
t.run('create . --name=wrong --version=version', assert_error=True)
assert "ERROR: wrong/version: package_info(): There are '(cpp_info/components).requires' " \
"to other internal components that are not defined: ['other', 'another']" in t.out


def test_unused_tool_requirement():
""" Requires should include all listed requirements
This error is known when creating the package if the requirement is consumed.
"""
top_conanfile = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
def package_info(self):
self.cpp_info.components["cmp1"].libs = ["top_cmp1"]
self.cpp_info.components["cmp2"].libs = ["top_cmp2"]
""")
consumer = textwrap.dedent("""
from conan import ConanFile
class Recipe(ConanFile):
requires = "top/version"
tool_requires = "top2/version"
def package_info(self):
self.cpp_info.requires = ["top::other"]
""")
t = TestClient()
t.save({'top.py': top_conanfile, 'consumer.py': consumer})
t.run('create top.py --name=top --version=version')
t.run('create top.py --name=top2 --version=version')
t.run('create consumer.py --name=wrong --version=version')
# This runs without crashing, because it is not chcking that top::other doesn't exist

0 comments on commit 5857767

Please sign in to comment.