Repository: conan-io/conan
Pull request #16562 (2024-06-27 15:47:44)

Fix provides conflict error message not showing conflicting provides when a named reference matches a provider
Changelog: Fix: Fix provides conflict error message not showing conflicting provides when a named reference matches a provider
Docs: Omit

Found as part of my explanation to @perseoGI of Conan's codebase :)


Patch:
diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py
index 1e92cf74d..a7452ecfe 100644
--- a/conan/api/subapi/list.py
+++ b/conan/api/subapi/list.py
@@ -104,7 +104,9 @@ class ListAPI:
             options = data.get("options", {})
             for k, v in options.items():
                 for pattern, pattern_options in profile_options.items():
-                    if ref_matches(ref, pattern, None):
+                    # Accept &: as referring to the current package being listed,
+                    # even if it's not technically a "consumer"
+                    if ref_matches(ref, pattern, True):
                         value = pattern_options.get_safe(k)
                         if value is not None and value != v:
                             options_match = False
diff --git a/conan/tools/cmake/cmake.py b/conan/tools/cmake/cmake.py
index ca49da148..2efa8f37a 100644
--- a/conan/tools/cmake/cmake.py
+++ b/conan/tools/cmake/cmake.py
@@ -31,7 +31,7 @@ def _cmake_cmd_line_args(conanfile, generator):
     return args
 
 
-class CMake(object):
+class CMake:
     """ CMake helper to use together with the CMakeToolchain feature """
 
     def __init__(self, conanfile):
@@ -60,8 +60,6 @@ class CMake(object):
         """
 
         Reads the ``CMakePresets.json`` file generated by the
-        :param cli_args: List of arguments ``[arg1, arg2, ...]`` that will be passed as extra CLI
-                         arguments to pass to cmake invocation
         :ref:`CMakeToolchain<conan-cmake-toolchain>` to get:
 
            - The generator, to append ``-G="xxx"``.
@@ -78,6 +76,8 @@ class CMake(object):
                           work in the toolchain approach.
         :param build_script_folder: Path to the CMakeLists.txt in case it is not in the declared
                                     ``self.folders.source`` at the ``layout()`` method.
+        :param cli_args: List of arguments ``[arg1, arg2, ...]`` that will be passed
+                         as extra CLI arguments to pass to cmake invocation
         :param stdout: Use it to redirect stdout to this stream
         :param stderr: Use it to redirect stderr to this stream
         """
@@ -173,7 +173,7 @@ class CMake(object):
         :param target: The name of a single build target as a string, or names of multiple build targets in a
                        list of strings to be passed to the ``--target`` argument.
         :param cli_args: A list of arguments ``[arg1, arg2, ...]`` that will be passed to the
-                        ``cmake --build ... arg1 arg2`` command directly.
+                         ``cmake --build ... arg1 arg2`` command directly.
         :param build_tool_args: A list of arguments ``[barg1, barg2, ...]`` for the underlying
                                 build system that will be passed to the command
                                 line after the ``--`` indicator: ``cmake --build ... -- barg1 barg2``
@@ -192,9 +192,8 @@ class CMake(object):
                            It can fail if the build is single configuration (e.g. Unix Makefiles),
                            as in that case the build type must be specified at configure time,
                            not build type.
-        :param cli_args: A list of arguments ``[arg1, arg2, ...]`` for the underlying
-                         build system that will be passed to the command line:
-                         ``cmake --install ... arg1 arg2``
+        :param cli_args: A list of arguments ``[arg1, arg2, ...]`` for the underlying build system
+                         that will be passed to the command line: ``cmake --install ... arg1 arg2``
         :param stdout: Use it to redirect stdout to this stream
         :param stderr: Use it to redirect stderr to this stream
         """
@@ -234,8 +233,7 @@ class CMake(object):
                            time.
         :param target: Name of the build target to run, by default ``RUN_TESTS`` or ``test``
         :param cli_args: Same as above ``build()``, a list of arguments ``[arg1, arg2, ...]``
-                         to be passed as extra arguments for the underlying
-                         build system
+                         to be passed as extra arguments for the underlying build system
         :param build_tool_args: Same as above ``build()``
         :param stdout: Use it to redirect stdout to this stream
         :param stderr: Use it to redirect stderr to this stream
@@ -257,8 +255,7 @@ class CMake(object):
         """
         Equivalent to running ctest ...
 
-        :param cli_args: List of arguments ``[arg1, arg2, ...]`` to be passed
-                         as extra ctest command line arguments
+        :param cli_args: List of arguments ``[arg1, arg2, ...]`` to be passed as extra ctest command line arguments
         :param env: the environment files to activate, by default conanbuild + conanrun
         :param stdout: Use it to redirect stdout to this stream
         :param stderr: Use it to redirect stderr to this stream
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index a23669c88..7805878c2 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -65,10 +65,10 @@ def cppstd_compat(conanfile):
         new_combinations = []
         for comb in combinations:
             for f in factor:
-                comb = comb.copy()
-                comb.update(f)
-                new_combinations.append(comb)
-        combinations = new_combinations
+                new_comb = comb.copy()
+                new_comb.update(f)
+                new_combinations.append(new_comb)
+        combinations.extend(new_combinations)
 
     ret = []
     for comb in combinations:
diff --git a/conans/client/graph/graph_error.py b/conans/client/graph/graph_error.py
index cf2497e35..a438b87be 100644
--- a/conans/client/graph/graph_error.py
+++ b/conans/client/graph/graph_error.py
@@ -70,7 +70,7 @@ class GraphProvidesError(GraphError):
 
     def __str__(self):
         return f"Provide Conflict: Both '{self.node.ref}' and '{self.conflicting_node.ref}' " \
-               f"provide '{self.node.conanfile.provides}'."
+               f"provide '{self.node.conanfile.provides or self.conflicting_node.conanfile.provides}'."
 
 
 class GraphRuntimeError(GraphError):


Test patch:
diff --git a/test/integration/command_v2/list_test.py b/test/integration/command_v2/list_test.py
index b73c9742e..0139bb5d5 100644
--- a/test/integration/command_v2/list_test.py
+++ b/test/integration/command_v2/list_test.py
@@ -896,3 +896,13 @@ class TestListBinaryFilter:
         assert len(pkg["packages"]) == 1
         settings = pkg["packages"]["d2e97769569ac0a583d72c10a37d5ca26de7c9fa"]["info"]["settings"]
         assert settings == {"arch": "x86", "os": "Windows"}
+
+        # &: will also match every package being listed, as if it was a consumer
+        c.run(f"list *:* -fo &:shared=False --format=json {r}")
+        result = json.loads(c.stdout)
+        header = result[pkg_key]["header/1.0"]["revisions"]["747cc49983b14bdd00df50a0671bd8b3"]
+        assert header["packages"] == {"da39a3ee5e6b4b0d3255bfef95601890afd80709": {"info": {}}}
+        pkg = result[pkg_key]["pkg/1.0"]["revisions"]["03591c8b22497dd74214e08b3bf2a56f"]
+        assert len(pkg["packages"]) == 2
+        settings = pkg["packages"]["d2e97769569ac0a583d72c10a37d5ca26de7c9fa"]["info"]["settings"]
+        assert settings == {"arch": "x86", "os": "Windows"}
diff --git a/test/integration/graph/core/test_provides.py b/test/integration/graph/core/test_provides.py
index aef4095b4..da20d4f4a 100644
--- a/test/integration/graph/core/test_provides.py
+++ b/test/integration/graph/core/test_provides.py
@@ -281,3 +281,15 @@ def test_self_build_require():
     c.assert_listed_binary({"grpc/0.1": ("9a4eb3c8701508aa9458b1a73d0633783ecc2270", "Build")})
     c.assert_listed_binary({"grpc/0.1": ("ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715", "Cache")},
                            build=True)
+
+
+def test_name_provide_error_message():
+    tc = TestClient(light=True)
+    tc.save({"libjepg/conanfile.py": GenConanfile("libjpeg", "0.1"),
+             "mozjpeg/conanfile.py": GenConanfile("mozjpeg", "0.1").with_provides("libjpeg")})
+    tc.run("create libjepg")
+    tc.run("create mozjpeg")
+
+    tc.run("graph info --requires=mozjpeg/0.1 --requires=libjpeg/0.1", assert_error=True)
+    # This used to report that None was provided, but now it reports the name of the provides
+    assert "ERROR: Provide Conflict: Both 'libjpeg/0.1' and 'mozjpeg/0.1' provide '['libjpeg']'" in tc.out
diff --git a/test/integration/package_id/compatible_test.py b/test/integration/package_id/compatible_test.py
index dca4e3c06..a931701fc 100644
--- a/test/integration/package_id/compatible_test.py
+++ b/test/integration/package_id/compatible_test.py
@@ -400,3 +400,21 @@ class TestNewCompatibility:
 
         c.run("install --requires=pdfium/2020.9 -pr=myprofile -s build_type=Debug")
         assert "missing. Using compatible package" in c.out
+
+    def test_compatibility_msvc_and_cppstd(self):
+        """msvc 194 would not find compatible packages built with same version but different cppstd
+        due to an issue in the msvc fallback compatibility rule."""
+        tc = TestClient()
+        profile = textwrap.dedent("""
+                   [settings]
+                   compiler=msvc
+                   compiler.version=194
+                   compiler.runtime=dynamic
+                   """)
+        tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_setting("compiler"),
+                 "conanfile.py": GenConanfile("app", "1.0").with_require("dep/1.0").with_setting("compiler"),
+                 "profile": profile})
+
+        tc.run("create dep -pr=profile -s compiler.cppstd=20")
+        tc.run("create . -pr=profile -s compiler.cppstd=17")
+        tc.assert_listed_binary({"dep/1.0": ("b6d26a6bc439b25b434113982791edf9cab4d004", "Cache")})