Repository: conan-io/conan
Pull request #16552 (2024-06-26 13:08:25)

[deployers] Fix relative paths using`--deployer-folder` param
Changelog: Bugfix: `XXXDeps` generators did not include an absolute path in their generated files if `--deployer-folder=xxxx` param was used.
Docs: omit
Closes: https://github.com/conan-io/conan/issues/16543


Patch:
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index 0fb5e1108..5a5b677f6 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -1,3 +1,5 @@
+import os
+
 from conan.internal.conan_app import ConanApp
 from conan.internal.deploy import do_deploys
 from conans.client.generators import write_generators
@@ -70,7 +72,9 @@ class InstallAPI:
 
         # The previous .set_base_folders has already decided between the source_folder and output
         if deploy or deploy_package:
-            base_folder = deploy_folder or conanfile.folders.base_build
+            # Issue related: https://github.com/conan-io/conan/issues/16543
+            base_folder = os.path.abspath(deploy_folder) if deploy_folder \
+                else conanfile.folders.base_build
             do_deploys(self.conan_api, deps_graph, deploy, deploy_package, base_folder)
 
         final_generators = []
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/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py
index be968f3ff..4e2160989 100644
--- a/conan/tools/gnu/pkgconfigdeps.py
+++ b/conan/tools/gnu/pkgconfigdeps.py
@@ -78,12 +78,11 @@ class _PCContentGenerator:
         ret = {}
         for i, directory in enumerate(folders):
             directory = os.path.normpath(directory).replace("\\", "/")
-            prefix = ""
-            if not os.path.isabs(directory):
-                prefix = "${prefix}/"
-            elif directory.startswith(prefix_path_):
+            if directory.startswith(prefix_path_):
                 prefix = "${prefix}/"
                 directory = os.path.relpath(directory, prefix_path_).replace("\\", "/")
+            else:
+                prefix = "" if os.path.isabs(directory) else "${prefix}/"
             suffix = str(i) if i else ""
             var_name = f"{folder_name}{suffix}"
             ret[var_name] = f"{prefix}{directory}"
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_binaries.py b/conans/client/graph/graph_binaries.py
index fc1980d99..b589a3c2f 100644
--- a/conans/client/graph/graph_binaries.py
+++ b/conans/client/graph/graph_binaries.py
@@ -134,9 +134,13 @@ class GraphBinariesAnalyzer(object):
                                   f"compatible package '{pkg_id}': {diff}")
             # So they are available in package_info() method
             conanfile.info = compatible_pkg  # Redefine current
+
+            # TODO: Improve this interface
+            # The package_id method might have modified the settings to erase information,
+            # ensure we allow those new values
+            conanfile.settings = conanfile.settings.copy_conaninfo_settings()
             conanfile.settings.update_values(compatible_pkg.settings.values_list)
             # Trick to allow mutating the options (they were freeze=True)
-            # TODO: Improve this interface
             conanfile.options = conanfile.options.copy_conaninfo_options()
             conanfile.options.update_options(compatible_pkg.options)
 
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/conan_file.py b/conans/model/conan_file.py
index a174b260b..0c6bedc7e 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -352,6 +352,7 @@ class ConanFile:
         from conans.util.runners import conan_run
         if not quiet:
             ConanOutput().writeln(f"{self.display_name}: RUN: {command}", fg=Color.BRIGHT_BLUE)
+        ConanOutput().debug(f"{self.display_name}: Full command: {wrapped_cmd}")
         retcode = conan_run(wrapped_cmd, cwd=cwd, stdout=stdout, stderr=stderr, shell=shell)
         if not quiet:
             ConanOutput().writeln("")
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",
diff --git a/conans/model/settings.py b/conans/model/settings.py
index 347ab40a5..f0735f901 100644
--- a/conans/model/settings.py
+++ b/conans/model/settings.py
@@ -61,9 +61,9 @@ class SettingsItem:
         """ deepcopy, recursive
         This function adds "ANY" to lists, to allow the ``package_id()`` method to modify some of
         values, but not all, just the "final" values without subsettings.
-        We cannot let usres manipulate to random strings
+        We cannot let users manipulate to random strings
         things that contain subsettings like ``compiler``, because that would leave the thing
-        in a undefined state, with some now inconsistent subsettings, that cannot be accessed
+        in an undefined state, with some now inconsistent subsettings, that cannot be accessed
         anymore. So with this change the options are:
         - If you need more "binary-compatible" descriptions of a compiler, lets say like
         "gcc_or_clang", then you need to add that string to settings.yml. And add the subsettings


Test patch:
diff --git a/test/functional/toolchains/gnu/test_pkgconfigdeps_autotools.py b/test/functional/toolchains/gnu/test_pkgconfigdeps_autotools.py
index 1d328cd86..e6cd401db 100644
--- a/test/functional/toolchains/gnu/test_pkgconfigdeps_autotools.py
+++ b/test/functional/toolchains/gnu/test_pkgconfigdeps_autotools.py
@@ -110,5 +110,3 @@ def test_pkgconfigdeps_and_autotools():
         # Issue: https://github.com/conan-io/conan/issues/11867
         assert '-F/my/framework/file1' in client.out
         assert '-F/my/framework/file2' in client.out
-        assert "warning: directory not found for option '-F/my/framework/file1'" in client.out
-        assert "warning: directory not found for option '-F/my/framework/file2'" in client.out
diff --git a/test/functional/toolchains/qbs/__init__.py b/test/functional/toolchains/qbs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/functional/toolchains/scons/__init__.py b/test/functional/toolchains/scons/__init__.py
new file mode 100644
index 000000000..e69de29bb
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..4f2865f7b 100644
--- a/test/integration/package_id/compatible_test.py
+++ b/test/integration/package_id/compatible_test.py
@@ -400,3 +400,52 @@ 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_erase_package_id(self):
+        c = TestClient()
+        conanfile = textwrap.dedent("""
+            from conan import ConanFile
+
+            class PdfiumConan(ConanFile):
+                name = "diligent-core"
+                version = "1.0"
+                settings = "compiler"
+                options = {"foo": ["no"]}
+
+                def package_id(self):
+                    self.info.settings.compiler.runtime = "foobar"
+                    self.info.options.foo = "yes"
+            """)
+        profile = textwrap.dedent("""
+            [settings]
+            os = Windows
+            compiler=msvc
+            compiler.version=192
+            compiler.runtime=dynamic
+            build_type=Release
+            arch=x86_64
+            """)
+        c.save({"conanfile.py": conanfile,
+                "myprofile": profile})
+
+        c.run("create . -pr:a=myprofile -s compiler.cppstd=20")
+        c.run("install --requires=diligent-core/1.0 -pr:a=myprofile -s compiler.cppstd=17")
+        assert "ERROR: Invalid setting 'foobar' is not a valid 'settings.compiler.runtime' value." not 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/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
index 50c2ec587..bbbc124f4 100644
--- a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -757,3 +757,33 @@ def test_different_versions():
     c.run("install --requires=dep/2.3 -g CMakeDeps")
     config = c.load("dep-config.cmake")
     assert 'set(dep_VERSION_STRING "2.3")' in config
+
+
+def test_using_deployer_folder():
+    """
+    Related to: https://github.com/conan-io/conan/issues/16543
+
+    CMakeDeps was failing if --deployer-folder was used. The error looked like:
+
+    conans.errors.ConanException: Error in generator 'CMakeDeps': error generating context for 'dep/1.0': mydeploy/direct_deploy/dep/include is not absolute
+    """
+    c = TestClient()
+    profile = textwrap.dedent("""
+    [settings]
+    arch=x86_64
+    build_type=Release
+    compiler=apple-clang
+    compiler.cppstd=gnu17
+    compiler.libcxx=libc++
+    compiler.version=15
+    os=Macos
+    """)
+    c.save({
+        "profile": profile,
+        "dep/conanfile.py": GenConanfile("dep")})
+    c.run("create dep --version 1.0")
+    c.run("install --requires=dep/1.0 -pr profile --deployer=direct_deploy "
+          "--deployer-folder=mydeploy -g CMakeDeps")
+    content = c.load("dep-release-x86_64-data.cmake")
+    assert ('set(dep_PACKAGE_FOLDER_RELEASE "${CMAKE_CURRENT_LIST_DIR}/mydeploy/direct_deploy/dep")'
+            in content)
diff --git a/test/integration/toolchains/gnu/test_pkgconfigdeps.py b/test/integration/toolchains/gnu/test_pkgconfigdeps.py
index 29530e431..467d4f1f4 100644
--- a/test/integration/toolchains/gnu/test_pkgconfigdeps.py
+++ b/test/integration/toolchains/gnu/test_pkgconfigdeps.py
@@ -1098,3 +1098,23 @@ def test_pkg_config_deps_and_private_deps():
     # Now, it passes and creates the pc files correctly (the skipped one is not created)
     client.run("install .")
     assert "Requires:" not in client.load("pkg.pc")
+
+
+def test_using_deployer_folder():
+    """
+    Testing that the absolute path is kept as the prefix instead of the
+    relative path.
+
+    Issue related: https://github.com/conan-io/conan/issues/16543
+    """
+    client = TestClient()
+    client.save({"dep/conanfile.py": GenConanfile("dep", "0.1")})
+    client.run("create dep/conanfile.py")
+    client.run("install --requires=dep/0.1 --deployer=direct_deploy "
+               "--deployer-folder=mydeploy -g PkgConfigDeps")
+    content = client.load("dep.pc")
+    prefix_base = client.current_folder.replace('\\', '/')
+    assert f"prefix={prefix_base}/mydeploy/direct_deploy/dep" in content
+    assert "libdir=${prefix}/lib" in content
+    assert "includedir=${prefix}/include" in content
+    assert "bindir=${prefix}/bin" in content
diff --git a/test/integration/toolchains/qbs/__init__.py b/test/integration/toolchains/qbs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/unittests/model/version/test_version_range.py b/test/unittests/model/version/test_version_range.py
index 29b83685d..5b0101245 100644
--- a/test/unittests/model/version/test_version_range.py
+++ b/test/unittests/model/version/test_version_range.py
@@ -53,6 +53,7 @@ values = [
     ['>=2.0-pre.0, include_prerelease', [[['>=', '2.0-pre.0']]], ["2.1", "2.0-pre.1"], ["1.5"]],
     # Build metadata
     ['>=1.0.0+2', [[['>=', '1.0.0+2']]], ["1.0.0+2", "1.0.0+3"], ["1.0.0+1"]],
+    ['>=2.2.0+build.en.305 <2.2.0+build.en.306', [], ["2.2.0+build.en.305", "2.2.0+build.en.305.1", "2.2.0+build.en.305.2"], ["2.2.0+build.en.306"]]
 ]
 
 
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()