Repository: conan-io/conan
Pull request #16563 (2024-06-27 17:28:51)

New ``tools.cmake.cmaketoolchain:enabled_blocks``
Changelog: Feature: New ``tools.cmake.cmaketoolchain:enabled_blocks`` configuration to define which blocks of ``CMakeToolchain`` should be active or not.
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/16376


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/internal/paths.py b/conan/internal/paths.py
index 41e4dd953..893a7fbe3 100644
--- a/conan/internal/paths.py
+++ b/conan/internal/paths.py
@@ -17,12 +17,13 @@ def get_conan_user_home():
 
     def _find_conanrc_file():
         path = Path(os.getcwd())
-        while path.is_dir() and len(path.parts) > 1:  # finish at '/'
+        while True:
             conanrc_file = path / ".conanrc"
             if conanrc_file.is_file():
                 return conanrc_file
-            else:
-                path = path.parent
+            if len(path.parts) == 1:  # finish at '/'
+                break
+            path = path.parent
 
     def _user_home_from_conanrc_file():
         try:
@@ -49,8 +50,8 @@ def get_conan_user_home():
         user_home = conan_expand_user(user_home)
     if not os.path.isabs(user_home):
         raise ConanException("Invalid CONAN_HOME value '%s', "
-                        "please specify an absolute or path starting with ~/ "
-                        "(relative to user home)" % user_home)
+                             "please specify an absolute or path starting with ~/ "
+                             "(relative to user home)" % user_home)
     return user_home
 
 
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/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 5d1f34051..1f4509cb0 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -1326,6 +1326,15 @@ class ToolchainBlocks:
         return self._blocks[name]
 
     def process_blocks(self):
+        blocks = self._conanfile.conf.get("tools.cmake.cmaketoolchain:enabled_blocks",
+                                          check_type=list)
+        if blocks is not None:
+            try:
+                new_blocks = OrderedDict((b, self._blocks[b]) for b in blocks)
+            except KeyError as e:
+                raise ConanException(f"Block {e} defined in tools.cmake.cmaketoolchain"
+                                     f":enabled_blocks doesn't exist in {list(self._blocks.keys())}")
+            self._blocks = new_blocks
         result = []
         for b in self._blocks.values():
             content = b.get_rendered_content()
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):
diff --git a/conans/model/conf.py b/conans/model/conf.py
index 49a27c3ff..1a36d24a5 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -74,6 +74,7 @@ BUILT_IN_CONFS = {
     "tools.cmake.cmaketoolchain:toolset_cuda": "(Experimental) Path to a CUDA toolset to use, or version if installed at the system level",
     "tools.cmake.cmaketoolchain:presets_environment": "String to define wether to add or not the environment section to the CMake presets. Empty by default, will generate the environment section in CMakePresets. Can take values: 'disabled'.",
     "tools.cmake.cmaketoolchain:extra_variables": "Dictionary with variables to be injected in CMakeToolchain (potential override of CMakeToolchain defined variables)",
+    "tools.cmake.cmaketoolchain:enabled_blocks": "Select the specific blocks to use in the conan_toolchain.cmake",
     "tools.cmake.cmake_layout:build_folder_vars": "Settings and Options that will produce a different build folder and different CMake presets names",
     "tools.cmake.cmake_layout:build_folder": "(Experimental) Allow configuring the base folder of the build for local builds",
     "tools.cmake.cmake_layout:test_folder": "(Experimental) Allow configuring the base folder of the build for test_package",


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")})
diff --git a/test/unittests/tools/cmake/test_cmaketoolchain.py b/test/unittests/tools/cmake/test_cmaketoolchain.py
index 8ba5ff7f0..3eabef9b4 100644
--- a/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -95,6 +95,34 @@ def test_select_blocks(conanfile):
     assert "########## 'preprocessor' block #############" not in content
 
 
+def test_enabled_blocks_conf(conanfile):
+    conanfile.conf.define("tools.cmake.cmaketoolchain:enabled_blocks", ["generic_system"])
+    toolchain = CMakeToolchain(conanfile)
+    content = toolchain.content
+    assert "########## 'generic_system' block #############" in content
+    assert "########## 'cmake_flags_init' block #############" not in content
+    assert "########## 'libcxx' block #############" not in content
+    assert "########## 'variables' block #############" not in content
+    assert "########## 'preprocessor' block #############" not in content
+
+    # remove multiple
+    conanfile.conf.define("tools.cmake.cmaketoolchain:enabled_blocks",
+                          ["generic_system", "cmake_flags_init"])
+    toolchain = CMakeToolchain(conanfile)
+    content = toolchain.content
+    assert "########## 'generic_system' block #############" in content
+    assert "########## 'cmake_flags_init' block #############" in content
+    assert "########## 'libcxx' block #############" not in content
+    assert "########## 'variables' block #############" not in content
+    assert "########## 'preprocessor' block #############" not in content
+
+    conanfile.conf.define("tools.cmake.cmaketoolchain:enabled_blocks", ["potato"])
+    toolchain = CMakeToolchain(conanfile)
+    with pytest.raises(ConanException) as e:
+        _ = toolchain.content
+    assert "Block 'potato' defined in tools.cmake.cmaketoolchain:enabled_blocks doesn't" in str(e)
+
+
 def test_dict_keys(conanfile):
     toolchain = CMakeToolchain(conanfile)
     assert "generic_system" in toolchain.blocks.keys()