Repository: conan-io/conan
Pull request #11507 (2022-06-22 15:39:48)

Aggregate transitive components information in the same package for XcodeDeps
Changelog: Feature: Aggregate transitive components information in the same package for XcodeDeps
Docs: https://github.com/conan-io/docs/pull/2619



Patch:
diff --git a/conan/tools/apple/xcodedeps.py b/conan/tools/apple/xcodedeps.py
index 24c3fc16a..e972423d2 100644
--- a/conan/tools/apple/xcodedeps.py
+++ b/conan/tools/apple/xcodedeps.py
@@ -1,5 +1,6 @@
 import os
 import textwrap
+from collections import OrderedDict
 
 from jinja2 import Template
 
@@ -60,14 +61,14 @@ class XcodeDeps(object):
     general_name = "conandeps.xcconfig"
 
     _conf_xconfig = textwrap.dedent("""\
-        // Compiler options for {{pkg_name}}::{{pkg_name}}
+        // Compiler options for {{pkg_name}}::{{comp_name}}
         HEADER_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{include_dirs}}
         GCC_PREPROCESSOR_DEFINITIONS_{{pkg_name}}_{{comp_name}}{{condition}} = {{definitions}}
         OTHER_CFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{c_compiler_flags}}
         OTHER_CPLUSPLUSFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{cxx_compiler_flags}}
         FRAMEWORK_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{frameworkdirs}}
 
-        // Link options for {{name}}
+        // Link options for {{pkg_name}}::{{comp_name}}
         LIBRARY_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{lib_dirs}}
         OTHER_LDFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{linker_flags}} {{libs}} {{system_libs}} {{frameworks}}
         """)
@@ -121,25 +122,28 @@ class XcodeDeps(object):
         for generator_file, content in generator_files.items():
             save(generator_file, content)
 
-    def _conf_xconfig_file(self, pkg_name, comp_name, cpp_info):
+    def _conf_xconfig_file(self, pkg_name, comp_name, transitive_cpp_infos):
         """
         content for conan_poco_x86_release.xcconfig, containing the activation
         """
+        def _merged_vars(name):
+            merged = [var for cpp_info in transitive_cpp_infos for var in getattr(cpp_info, name)]
+            return list(OrderedDict.fromkeys(merged).keys())
 
         fields = {
             'pkg_name': pkg_name,
             'comp_name': comp_name,
-            'include_dirs': " ".join('"{}"'.format(p) for p in cpp_info.includedirs),
-            'lib_dirs': " ".join('"{}"'.format(p) for p in cpp_info.libdirs),
-            'libs': " ".join("-l{}".format(lib) for lib in cpp_info.libs),
-            'system_libs': " ".join("-l{}".format(sys_lib) for sys_lib in cpp_info.system_libs),
-            'frameworksdirs': " ".join('"{}"'.format(p) for p in cpp_info.frameworkdirs),
-            'frameworks': " ".join("-framework {}".format(framework) for framework in cpp_info.frameworks),
-            'definitions': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.defines),
-            'c_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.cflags),
-            'cxx_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.cxxflags),
-            'linker_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.sharedlinkflags),
-            'exe_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.exelinkflags),
+            'include_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("includedirs")),
+            'lib_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("libdirs")),
+            'libs': " ".join("-l{}".format(lib) for lib in _merged_vars("libs")),
+            'system_libs': " ".join("-l{}".format(sys_lib) for sys_lib in _merged_vars("system_libs")),
+            'frameworksdirs': " ".join('"{}"'.format(p) for p in _merged_vars("frameworkdirs")),
+            'frameworks': " ".join("-framework {}".format(framework) for framework in _merged_vars("frameworks")),
+            'definitions': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("defines")),
+            'c_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("cflags")),
+            'cxx_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("cxxflags")),
+            'linker_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("sharedlinkflags")),
+            'exe_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("exelinkflags")),
             'condition': _xcconfig_conditional(self._conanfile.settings)
         }
 
@@ -193,17 +197,17 @@ class XcodeDeps(object):
                                                GLOBAL_XCCONFIG_TEMPLATE,
                                                [self.general_name])
 
-    def get_content_for_component(self, pkg_name, component_name, cpp_info, reqs):
+    def get_content_for_component(self, pkg_name, component_name, transitive_internal, transitive_external):
         result = {}
 
         conf_name = _xcconfig_settings_filename(self._conanfile.settings)
 
         props_name = "conan_{}_{}{}.xcconfig".format(pkg_name, component_name, conf_name)
-        result[props_name] = self._conf_xconfig_file(pkg_name, component_name, cpp_info)
+        result[props_name] = self._conf_xconfig_file(pkg_name, component_name, transitive_internal)
 
         # The entry point for each package
         file_dep_name = "conan_{}_{}.xcconfig".format(pkg_name, component_name)
-        dep_content = self._dep_xconfig_file(pkg_name, component_name, file_dep_name, props_name, reqs)
+        dep_content = self._dep_xconfig_file(pkg_name, component_name, file_dep_name, props_name, transitive_external)
 
         result[file_dep_name] = dep_content
         return result
@@ -213,28 +217,60 @@ class XcodeDeps(object):
 
         # Generate the config files for each component with name conan_pkgname_compname.xcconfig
         # If a package has no components the name is conan_pkgname_pkgname.xcconfig
-        # Then all components are included in the conan_pkgname.xcconfig file
+        # All components are included in the conan_pkgname.xcconfig file
         host_req = self._conanfile.dependencies.host
         test_req = self._conanfile.dependencies.test
-        for dep in list(host_req.values()) + list(test_req.values()):
+        all_deps = list(host_req.values()) + list(test_req.values())
+        for dep in all_deps:
+
             dep_name = _format_name(dep.ref.name)
 
             include_components_names = []
             if dep.cpp_info.has_components:
-                for comp_name, comp_cpp_info in dep.cpp_info.get_sorted_components().items():
-                    component_deps = []
-                    for req in comp_cpp_info.requires:
-                        req_pkg, req_cmp = req.split("::") if "::" in req else (dep_name, req)
-                        component_deps.append((req_pkg, req_cmp))
 
-                    component_content = self.get_content_for_component(dep_name, comp_name, comp_cpp_info, component_deps)
+                sorted_components = dep.cpp_info.get_sorted_components().items()
+                for comp_name, comp_cpp_info in sorted_components:
+
+                    # returns: ("list of cpp infos from required components in same package",
+                    #           "list of names from required components from other packages")
+                    def _get_component_requires(component):
+                        requires_external = [(req.split("::")[0], req.split("::")[1]) for req in
+                                             component.requires if "::" in req]
+                        requires_internal = [dep.cpp_info.components.get(req) for req in
+                                             component.requires if "::" not in req]
+                        return requires_internal, requires_external
+
+                    # these are the transitive dependencies between components of the same package
+                    transitive_internal = []
+                    # these are the transitive dependencies to components from other packages
+                    transitive_external = []
+
+                    # return the internal cpp_infos and external components names
+                    def _transitive_components(component):
+                        requires_internal, requires_external = _get_component_requires(component)
+                        transitive_internal.append(component)
+                        transitive_internal.extend(requires_internal)
+                        transitive_external.extend(requires_external)
+                        for require in requires_internal:
+                            _transitive_components(require)
+
+                    _transitive_components(comp_cpp_info)
+
+                    # remove duplicates
+                    transitive_internal = list(OrderedDict.fromkeys(transitive_internal).keys())
+                    transitive_external = list(OrderedDict.fromkeys(transitive_external).keys())
+
+                    component_content = self.get_content_for_component(dep_name, comp_name,
+                                                                       transitive_internal,
+                                                                       transitive_external)
                     include_components_names.append((dep_name, comp_name))
                     result.update(component_content)
             else:
                 public_deps = [(_format_name(d.ref.name),) * 2 for r, d in
                                dep.dependencies.direct_host.items() if r.visible]
                 required_components = dep.cpp_info.required_components if dep.cpp_info.required_components else public_deps
-                root_content = self.get_content_for_component(dep_name, dep_name, dep.cpp_info, required_components)
+                root_content = self.get_content_for_component(dep_name, dep_name, [dep.cpp_info],
+                                                              required_components)
                 include_components_names.append((dep_name, dep_name))
                 result.update(root_content)
 
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index b222091c9..918f6a831 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -42,7 +42,6 @@ def _configure_preset_name(conanfile, multiconfig):
         return str(build_type).lower()
 
 
-
 def _add_configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig):
     build_type = conanfile.settings.get_safe("build_type")
     name = _configure_preset_name(conanfile, multiconfig)
@@ -78,15 +77,19 @@ def _contents(conanfile, toolchain_file, cache_variables, generator):
     return ret
 
 
-def write_cmake_presets(conanfile, toolchain_file, generator):
-    cache_variables = {}
+def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables):
+    cache_variables = cache_variables or {}
     if platform.system() == "Windows" and generator == "MinGW Makefiles":
-        cache_variables["CMAKE_SH"] = "CMAKE_SH-NOTFOUND"
-        cmake_make_program = conanfile.conf.get("tools.gnu:make_program", default=None)
+        if "CMAKE_SH" not in cache_variables:
+            cache_variables["CMAKE_SH"] = "CMAKE_SH-NOTFOUND"
+
+        cmake_make_program = conanfile.conf.get("tools.gnu:make_program", default=cache_variables.get("CMAKE_MAKE_PROGRAM"))
         if cmake_make_program:
             cmake_make_program = cmake_make_program.replace("\\", "/")
             cache_variables["CMAKE_MAKE_PROGRAM"] = cmake_make_program
-    cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] = "NEW"
+
+    if "CMAKE_POLICY_DEFAULT_CMP0091" not in cache_variables:
+        cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] = "NEW"
 
     preset_path = os.path.join(conanfile.generators_folder, "CMakePresets.json")
     multiconfig = is_multi_configuration(generator)
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 1b5319d18..9b74a5b76 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -119,6 +119,8 @@ class CMakeToolchain(object):
         self._conanfile = conanfile
         self.generator = self._get_generator(generator)
         self.variables = Variables()
+        # This doesn't support multi-config, they go to the same configPreset common in multi-config
+        self.cache_variables = {}
         self.preprocessor_definitions = Variables()
 
         self.blocks = ToolchainBlocks(self._conanfile, self,
@@ -175,7 +177,14 @@ class CMakeToolchain(object):
         elif self.generator is not None and "Visual" not in self.generator:
             VCVars(self._conanfile).generate()
         toolchain = os.path.join(self._conanfile.generators_folder, toolchain_file or self.filename)
-        write_cmake_presets(self._conanfile, toolchain, self.generator)
+        cache_variables = {}
+        for name, value in self.cache_variables.items():
+            if isinstance(value, bool):
+                cache_variables[name] = "ON" if value else "OFF"
+            else:
+                cache_variables[name] = value
+
+        write_cmake_presets(self._conanfile, toolchain, self.generator, cache_variables)
 
     def _get_generator(self, recipe_generator):
         # Returns the name of the generator to be used by CMake
diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py
index a42c34485..edb1c6777 100644
--- a/conan/tools/env/virtualbuildenv.py
+++ b/conan/tools/env/virtualbuildenv.py
@@ -69,5 +69,4 @@ class VirtualBuildEnv:
 
     def generate(self, scope="build"):
         build_env = self.environment()
-        if build_env:  # Only if there is something defined
-            build_env.vars(self._conanfile, scope=scope).save_script(self._filename)
+        build_env.vars(self._conanfile, scope=scope).save_script(self._filename)
diff --git a/conan/tools/env/virtualrunenv.py b/conan/tools/env/virtualrunenv.py
index a25f480eb..af342e16d 100644
--- a/conan/tools/env/virtualrunenv.py
+++ b/conan/tools/env/virtualrunenv.py
@@ -73,5 +73,4 @@ class VirtualRunEnv:
 
     def generate(self, scope="run"):
         run_env = self.environment()
-        if run_env:
-            run_env.vars(self._conanfile, scope=scope).save_script(self._filename)
+        run_env.vars(self._conanfile, scope=scope).save_script(self._filename)
diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py
index 76a438e98..151b7bc50 100644
--- a/conan/tools/microsoft/msbuilddeps.py
+++ b/conan/tools/microsoft/msbuilddeps.py
@@ -83,45 +83,20 @@ class MSBuildDeps(object):
         </Project>
         """)
 
-    _dep_props = textwrap.dedent("""\
-        <?xml version="1.0" encoding="utf-8"?>
-        <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-          <ImportGroup Label="PropertySheets">
-          </ImportGroup>
-          <PropertyGroup>
-            <conan_{{name}}_props_imported>True</conan_{{name}}_props_imported>
-          </PropertyGroup>
-        </Project>
-        """)
-
-    _all_props = textwrap.dedent("""\
-        <?xml version="1.0" encoding="utf-8"?>
-        <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-          <ImportGroup Label="PropertySheets">
-          </ImportGroup>
-        </Project>
-        """)
-
     def __init__(self, conanfile):
         self._conanfile = conanfile
         self.configuration = conanfile.settings.build_type
+        # TODO: This platform is not exactly the same as ``msbuild_arch``, because it differs
+        # in x86=>Win32
         self.platform = {'x86': 'Win32',
-                         'x86_64': 'x64'}.get(str(conanfile.settings.arch))
-        # ca_exclude section
-        # TODO: Accept single strings, not lists
-        self.exclude_code_analysis = self._conanfile.conf.get("tools.microsoft.msbuilddeps:exclude_code_analysis",
-                                                              check_type=list)
+                         'x86_64': 'x64',
+                         'armv7': 'ARM',
+                         'armv8': 'ARM64'}.get(str(conanfile.settings.arch))
+        ca_exclude = "tools.microsoft.msbuilddeps:exclude_code_analysis"
+        self.exclude_code_analysis = self._conanfile.conf.get(ca_exclude, check_type=list)
         check_using_build_profile(self._conanfile)
 
     def generate(self):
-        # TODO: Apply config from command line, something like
-        # configuration = self.conanfile.config.generators["msbuild"].configuration
-        # if configuration is not None:
-        #     self.configuration = configuration
-        # platform
-        # config_filename
-        # TODO: This is duplicated in write_generators() function, would need to be moved
-        # to generators and called from there
         if self.configuration is None:
             raise ConanException("MSBuildDeps.configuration is None, it should have a value")
         if self.platform is None:
@@ -131,7 +106,6 @@ class MSBuildDeps(object):
             save(generator_file, content)
 
     def _config_filename(self):
-        # Default name
         props = [("Configuration", self.configuration),
                  ("Platform", self.platform)]
         name = "".join("_%s" % v for _, v in props)
@@ -143,7 +117,61 @@ class MSBuildDeps(object):
         condition = " And ".join("'$(%s)' == '%s'" % (k, v) for k, v in props)
         return condition
 
-    def _vars_props_file(self, dep, name, cpp_info, deps, build=False):
+    def _conf_props_file(self, dep_name, vars_props_name, deps, build):
+        """
+        content for conan_poco_x86_release.props, containing the activation
+        """
+        # TODO: This must include somehow the user/channel, most likely pattern to exclude/include
+        # Probably also the negation pattern, exclude all not @mycompany/*
+        ca_exclude = any(fnmatch.fnmatch(dep_name, p) for p in self.exclude_code_analysis or ())
+
+        deps = [] if build else deps  # build-requires do not propagate dependencies
+        template = Template(self._conf_props, trim_blocks=True, lstrip_blocks=True)
+        content_multi = template.render(host_context=not build,
+                                        name=dep_name, ca_exclude=ca_exclude,
+                                        vars_filename=vars_props_name, deps=deps)
+        return content_multi
+
+    @staticmethod
+    def _dep_props_file(name, name_general, dep_props_filename, condition):
+        # Current directory is the generators_folder
+        multi_path = name_general
+        if os.path.isfile(multi_path):
+            content_multi = load(multi_path)
+        else:
+            content_multi = textwrap.dedent("""\
+            <?xml version="1.0" encoding="utf-8"?>
+            <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+              <ImportGroup Label="PropertySheets">
+              </ImportGroup>
+              <PropertyGroup>
+                <conan_{{name}}_props_imported>True</conan_{{name}}_props_imported>
+              </PropertyGroup>
+            </Project>
+            """)
+            content_multi = Template(content_multi).render({"name": name})
+
+        # parse the multi_file and add new import statement if needed
+        dom = minidom.parseString(content_multi)
+        import_vars = dom.getElementsByTagName('ImportGroup')[0]
+
+        # Current vars
+        children = import_vars.getElementsByTagName("Import")
+        for node in children:
+            if (dep_props_filename == node.getAttribute("Project") and
+                    condition == node.getAttribute("Condition")):
+                break  # the import statement already exists
+        else:  # create a new import statement
+            import_node = dom.createElement('Import')
+            import_node.setAttribute('Condition', condition)
+            import_node.setAttribute('Project', dep_props_filename)
+            import_vars.appendChild(import_node)
+
+        content_multi = dom.toprettyxml()
+        content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip())
+        return content_multi
+
+    def _vars_props_file(self, dep, name, cpp_info, deps, build):
         """
         content for conan_vars_poco_x86_release.props, containing the variables
         """
@@ -154,6 +182,12 @@ class MSBuildDeps(object):
 
         pkg_placeholder = "$(Conan{}RootFolder)".format(name)
 
+        def escape_path(path):
+            # https://docs.microsoft.com/en-us/visualstudio/msbuild/
+            #                          how-to-escape-special-characters-in-msbuild
+            # https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-special-characters
+            return path.replace("\\", "/").lstrip("/")
+
         def join_paths(paths):
             # ALmost copied from CMakeDeps TargetDataContext
             ret = []
@@ -162,15 +196,14 @@ class MSBuildDeps(object):
 
                 if p.startswith(package_folder):
                     rel = p[len(package_folder):]
-                    rel = rel.replace('\\', '/').replace('$', '\\$').replace('"', '\\"').lstrip("/")
+                    rel = escape_path(rel)
                     norm_path = ("${%s}/%s" % (pkg_placeholder, rel))
                 else:
-                    norm_path = p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
+                    norm_path = escape_path(p)
                 ret.append(norm_path)
             return "".join("{};".format(e) for e in ret)
 
-        package_folder = dep.package_folder.replace('\\', '/')\
-                                           .replace('$', '\\$').replace('"', '\\"')
+        package_folder = escape_path(dep.package_folder)
 
         fields = {
             'name': name,
@@ -192,72 +225,28 @@ class MSBuildDeps(object):
                                       lstrip_blocks=True).render(**fields)
         return formatted_template
 
-    def _conf_props_file(self, dep_name, vars_props_name, deps, build=False):
-        """
-        content for conan_poco_x86_release.props, containing the activation
-        """
-        # TODO: This must include somehow the user/channel, most likely pattern to exclude/include
-        # Probably also the negation pattern, exclude all not @mycompany/*
-        ca_exclude = False
-        if isinstance(self.exclude_code_analysis, list):
-            for pattern in self.exclude_code_analysis:
-                if fnmatch.fnmatch(dep_name, pattern):
-                    ca_exclude = True
-                    break
-        else:
-            ca_exclude = self.exclude_code_analysis
-
-        deps = [] if build else deps  # build-requires do not propagate dependencies
-        template = Template(self._conf_props, trim_blocks=True, lstrip_blocks=True)
-        content_multi = template.render(host_context=not build,
-                                        name=dep_name, ca_exclude=ca_exclude,
-                                        vars_filename=vars_props_name, deps=deps)
-        return content_multi
-
-    def _dep_props_file(self, name, name_general, dep_props_filename, condition):
-        # Current directory is the generators_folder
-        multi_path = name_general
-        if os.path.isfile(multi_path):
-            content_multi = load(multi_path)
-        else:
-            content_multi = self._dep_props
-            content_multi = Template(content_multi).render({"name": name})
-
-        # parse the multi_file and add new import statement if needed
-        dom = minidom.parseString(content_multi)
-        import_vars = dom.getElementsByTagName('ImportGroup')[0]
-
-        # Current vars
-        children = import_vars.getElementsByTagName("Import")
-        for node in children:
-            if (dep_props_filename == node.getAttribute("Project") and
-                    condition == node.getAttribute("Condition")):
-                break  # the import statement already exists
-        else:  # create a new import statement
-            import_node = dom.createElement('Import')
-            import_node.setAttribute('Condition', condition)
-            import_node.setAttribute('Project', dep_props_filename)
-            import_vars.appendChild(import_node)
-
-        content_multi = dom.toprettyxml()
-        content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip())
-        return content_multi
-
-    def _all_props_file(self, name_general, deps):
-        """ this is a .props file including all declared dependencies
+    def _all_props_file(self, name_general):
+        """ this is a .props file including direct declared dependencies
         """
         # Current directory is the generators_folder
         multi_path = name_general
         if os.path.isfile(multi_path):
             content_multi = load(multi_path)
         else:
-            content_multi = self._all_props
+            content_multi = textwrap.dedent("""\
+            <?xml version="1.0" encoding="utf-8"?>
+            <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+              <ImportGroup Label="PropertySheets">
+              </ImportGroup>
+            </Project>
+            """)
 
         # parse the multi_file and add a new import statement if needed
         dom = minidom.parseString(content_multi)
         import_group = dom.getElementsByTagName('ImportGroup')[0]
         children = import_group.getElementsByTagName("Import")
-        for req, dep in deps.items():
+        direct_deps = self._conanfile.dependencies.filter({"direct": True})
+        for req, dep in direct_deps.items():
             dep_name = dep.ref.name.replace(".", "_")
             if req.build:
                 dep_name += "_build"
@@ -279,58 +268,56 @@ class MSBuildDeps(object):
         content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip())
         return content_multi
 
+    def _package_props_files(self, dep, build=False):
+        """ all the files for a given package:
+        - conan_pkgname_vars_config.props: definition of variables, one per config
+        - conan_pkgname_config.props: The one using those variables. This is very different for
+                                      Host and build, build only activate <ExecutablePath>
+        - conan_pkgname.props: Conditional aggregate xxx_config.props based on active config
+        """
+        conf_name = self._config_filename()
+        condition = self._condition()
+        dep_name = dep.ref.name
+        dep_name = dep_name.replace(".", "_")
+        if build:
+            dep_name += "_build"
+        cpp_info = dep.cpp_info.aggregated_components()
+        public_deps = [d.ref.name.replace(".", "_")
+                       for r, d in dep.dependencies.direct_host.items() if r.visible]
+        result = {}
+        # One file per configuration, with just the variables
+        vars_props_name = "conan_%s_vars%s.props" % (dep_name, conf_name)
+        result[vars_props_name] = self._vars_props_file(dep, dep_name, cpp_info, public_deps,
+                                                        build=build)
+        props_name = "conan_%s%s.props" % (dep_name, conf_name)
+        result[props_name] = self._conf_props_file(dep_name, vars_props_name, public_deps,
+                                                   build=build)
+
+        # The entry point for each package, it will have conditionals to the others
+        file_dep_name = "conan_%s.props" % dep_name
+        dep_content = self._dep_props_file(dep_name, file_dep_name, props_name, condition)
+        result[file_dep_name] = dep_content
+        return result
+
     def _content(self):
-        # We cannot use self._conanfile.warn(), because that fails for virtual conanfile
-        print("*** The 'msbuild' generator is EXPERIMENTAL ***")
         if not self._conanfile.settings.get_safe("build_type"):
             raise ConanException("The 'msbuild' generator requires a 'build_type' setting value")
         result = {}
-        general_name = "conandeps.props"
-        conf_name = self._config_filename()
-        condition = self._condition()
 
         host_req = list(self._conanfile.dependencies.host.values())
         test_req = list(self._conanfile.dependencies.test.values())
 
         for dep in host_req + test_req:
-            dep_name = dep.ref.name
-            dep_name = dep_name.replace(".", "_")
-            cpp_info = dep.cpp_info.aggregated_components()
-            public_deps = [d.ref.name.replace(".", "_")
-                           for r, d in dep.dependencies.direct_host.items() if r.visible]
-            # One file per configuration, with just the variables
-            vars_props_name = "conan_%s_vars%s.props" % (dep_name, conf_name)
-            result[vars_props_name] = self._vars_props_file(dep, dep_name, cpp_info, public_deps)
-            props_name = "conan_%s%s.props" % (dep_name, conf_name)
-            result[props_name] = self._conf_props_file(dep_name, vars_props_name, public_deps)
-
-            # The entry point for each package, it will have conditionals to the others
-            file_dep_name = "conan_%s.props" % dep_name
-            dep_content = self._dep_props_file(dep_name, file_dep_name, props_name, condition)
-            result[file_dep_name] = dep_content
+            files = self._package_props_files(dep, build=False)
+            result.update(files)
 
         build_req = list(self._conanfile.dependencies.build.values())
         for dep in build_req:
-            dep_name = dep.ref.name
-            dep_name = dep_name.replace(".", "_") + "_build"
-            cpp_info = dep.cpp_info.aggregated_components()
-            public_deps = [d.ref.name.replace(".", "_")
-                           for r, d in dep.dependencies.direct_host.items() if r.visible]
-            # One file per configuration, with just the variables
-            vars_props_name = "conan_%s_vars%s.props" % (dep_name, conf_name)
-            result[vars_props_name] = self._vars_props_file(dep, dep_name, cpp_info, public_deps,
-                                                            build=True)
-            props_name = "conan_%s%s.props" % (dep_name, conf_name)
-            result[props_name] = self._conf_props_file(dep_name, vars_props_name, public_deps,
-                                                       build=True)
-
-            # The entry point for each package, it will have conditionals to the others
-            file_dep_name = "conan_%s.props" % dep_name
-            dep_content = self._dep_props_file(dep_name, file_dep_name, props_name, condition)
-            result[file_dep_name] = dep_content
+            files = self._package_props_files(dep, build=True)
+            result.update(files)
 
         # Include all direct build_requires for host context. This might change
-        direct_deps = self._conanfile.dependencies.filter({"direct": True})
-        result[general_name] = self._all_props_file(general_name, direct_deps)
+        conandeps_filename = "conandeps.props"
+        result[conandeps_filename] = self._all_props_file(conandeps_filename)
 
         return result
diff --git a/conan/tools/microsoft/toolchain.py b/conan/tools/microsoft/toolchain.py
index dc1e3fc27..24c2fede8 100644
--- a/conan/tools/microsoft/toolchain.py
+++ b/conan/tools/microsoft/toolchain.py
@@ -55,9 +55,12 @@ class MSBuildToolchain(object):
 
     def _name_condition(self, settings):
         props = [("Configuration", self.configuration),
-                 # FIXME: This probably requires mapping ARM architectures
+                 # TODO: refactor, put in common with MSBuildDeps. Beware this is != msbuild_arch
+                 #  because of Win32
                  ("Platform", {'x86': 'Win32',
-                               'x86_64': 'x64'}.get(settings.get_safe("arch")))]
+                               'x86_64': 'x64',
+                               'armv7': 'ARM',
+                               'armv8': 'ARM64'}.get(settings.get_safe("arch")))]
 
         name = "".join("_%s" % v for _, v in props if v is not None)
         condition = " And ".join("'$(%s)' == '%s'" % (k, v) for k, v in props if v is not None)
diff --git a/conans/model/build_info.py b/conans/model/build_info.py
index a29b09bda..ee5478825 100644
--- a/conans/model/build_info.py
+++ b/conans/model/build_info.py
@@ -608,8 +608,15 @@ class DepCppInfo(object):
                             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


Test patch:
diff --git a/conans/test/conftest.py b/conans/test/conftest.py
index ea39c11b2..07905171f 100644
--- a/conans/test/conftest.py
+++ b/conans/test/conftest.py
@@ -169,65 +169,65 @@ def _get_tool(name, version):
     # True = tool not available, test error
     # (path, env) = tool available
     cached = _cached_tools.setdefault(name, {}).get(version)
-    if cached is None:
-        tool = tools_locations.get(name, {})
-        if tool.get("disabled"):
-            _cached_tools[name][version] = False
-            return False
+    if cached is not None:
+        return cached
+    result = _get_individual_tool(name, version)
+    _cached_tools[name][version] = result
+    return result
 
-        tool_platform = platform.system()
-        if tool.get("platform", tool_platform) != tool_platform:
-            _cached_tools[name][version] = None, None
-            return None, None
 
-        exe = tool.get("exe", name)
-        version = version or tool.get("default")
-        tool_version = tool.get(version)
-        if tool_version is not None:
-            assert isinstance(tool_version, dict)
-            if tool_version.get("disabled"):
-                _cached_tools[name][version] = False
-                return False
-            tool_path = tool_version.get("path", {}).get(tool_platform)
-            # To allow to skip for a platform, we can put the path to None
-            # "cmake": { "3.23": {
-            #               "path": {'Windows': 'C:/cmake/cmake-3.23.1-win64-x64/bin',
-            #                        'Darwin': '/Users/jenkins/cmake/cmake-3.23.1/bin',
-            #                        'Linux': None}}
-            #          }
-            if tool_path is None:
-                _cached_tools[name][version] = False
-                return False
+def _get_individual_tool(name, version):
+    tool = tools_locations.get(name, {})
+    if tool.get("disabled"):
+        return False
 
-        else:
-            if version is not None:  # if the version is specified, it should be in the conf
-                _cached_tools[name][version] = True
-                return True
-            tool_path = None
+    tool_platform = platform.system()
+    if tool.get("platform", tool_platform) != tool_platform:
+        return None, None
 
-        try:
-            tool_env = tools_environments[name][tool_platform]
-        except KeyError:
-            tool_env = None
+    version = version or tool.get("default")
+    tool_version = tool.get(version)
+    if tool_version is not None:
+        assert isinstance(tool_version, dict)
+        if tool_version.get("disabled"):
+            return False
+        if name == "visual_studio":
+            if vswhere():  # TODO: Missing version detection
+                return None, None
+
+        tool_path = tool_version.get("path", {}).get(tool_platform)
+        # To allow to skip for a platform, we can put the path to None
+        # "cmake": { "3.23": {
+        #               "path": {'Windows': 'C:/cmake/cmake-3.23.1-win64-x64/bin',
+        #                        'Darwin': '/Users/jenkins/cmake/cmake-3.23.1/bin',
+        #                        'Linux': None}}
+        #          }
+        if tool_path is None:
+            return False
+    else:
+        if version is not None:  # if the version is specified, it should be in the conf
+            return True
+        tool_path = None
 
-        cached = tool_path, tool_env
+    try:
+        tool_env = tools_environments[name][tool_platform]
+    except KeyError:
+        tool_env = None
 
-        # Check this particular tool is installed
-        if name == "visual_studio":
-            if not vswhere():  # TODO: Missing version detection
-                cached = True
-        else:  # which based detection
-            old_environ = None
-            if tool_path is not None:
-                old_environ = dict(os.environ)
-                os.environ["PATH"] = tool_path + os.pathsep + os.environ["PATH"]
-            if not which(exe):  # TODO: This which doesn't detect version either
-                cached = True
-            if old_environ is not None:
-                os.environ.clear()
-                os.environ.update(old_environ)
+    cached = tool_path, tool_env
 
-        _cached_tools[name][version] = cached
+    # Check this particular tool is installed
+    old_environ = None
+    if tool_path is not None:
+        old_environ = dict(os.environ)
+        os.environ["PATH"] = tool_path + os.pathsep + os.environ["PATH"]
+    exe = tool.get("exe", name)
+    exe_found = which(exe)  # TODO: This which doesn't detect version either
+    if not exe_found:
+        cached = True
+    if old_environ is not None:
+        os.environ.clear()
+        os.environ.update(old_environ)
 
     return cached
 
diff --git a/conans/test/functional/toolchains/apple/test_xcodedeps_components.py b/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
index 4ccd40f28..d4898bff1 100644
--- a/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
+++ b/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
@@ -9,8 +9,6 @@ from conans.test.utils.tools import TestClient
 
 @pytest.mark.skipif(platform.system() != "Darwin", reason="Only for MacOS")
 @pytest.mark.tool_cmake
-@pytest.mark.tool_xcodebuild
-@pytest.mark.tool_xcodegen
 def test_xcodedeps_components():
     """
     tcp/1.0 is a lib without components
diff --git a/conans/test/functional/tools/scm/test_git.py b/conans/test/functional/tools/scm/test_git.py
index 2ba29783f..c92d968ea 100644
--- a/conans/test/functional/tools/scm/test_git.py
+++ b/conans/test/functional/tools/scm/test_git.py
@@ -456,3 +456,58 @@ class TestGitMonorepoSCMFlow:
         assert "pkg1/0.1: MYFILE-BUILD: myheader1!" in c2.out
         assert "pkg2/0.1: MYCMAKE-BUILD: mycmake2!" in c2.out
         assert "pkg2/0.1: MYFILE-BUILD: my2header!" in c2.out
+
+
+class TestConanFileSubfolder:
+    """verify that we can have a conanfile in a subfolder
+        # https://github.com/conan-io/conan/issues/11275
+    """
+
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.scm import Git
+        from conan.tools.files import update_conandata, load
+
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+
+            def export(self):
+                git = Git(self, os.path.dirname(self.recipe_folder))
+                url, commit = git.get_url_and_commit()
+                # We store the current url and commit in conandata.yml
+                update_conandata(self, {"sources": {"commit": commit, "url": url}})
+                self.output.info("URL: {}".format(url))
+                self.output.info("COMMIT: {}".format(commit))
+
+            def layout(self):
+                pass # self.folders.source = "source"
+
+            def source(self):
+                git = Git(self)
+                sources = self.conan_data["sources"]
+                url = sources["url"]
+                commit = sources["commit"]
+                git.clone(url=url, target=".")
+                git.checkout(commit=commit)
+                self.output.info("MYCMAKE: {}".format(load(self, "CMakeLists.txt")))
+                self.output.info("MYFILE: {}".format(load(self, "src/myfile.h")))
+        """)
+
+    def test_conanfile_subfolder(self):
+        """
+        A local repo, without remote, will have commit, but no URL
+        """
+        c = TestClient()
+        c.save({"conan/conanfile.py": self.conanfile,
+                "CMakeLists.txt": "mycmakelists",
+                "src/myfile.h": "myheader"})
+        commit = c.init_git_repo()
+        c.run("export conan")
+        assert "pkg/0.1: COMMIT: {}".format(commit) in c.out
+        assert "pkg/0.1: URL: {}".format(c.current_folder.replace("\\", "/")) in c.out
+
+        c.run("create conan")
+        assert "pkg/0.1: MYCMAKE: mycmakelists" in c.out
+        assert "pkg/0.1: MYFILE: myheader" in c.out
diff --git a/conans/test/integration/environment/test_env.py b/conans/test/integration/environment/test_env.py
index cfcd45cd1..aad9b27bd 100644
--- a/conans/test/integration/environment/test_env.py
+++ b/conans/test/integration/environment/test_env.py
@@ -663,3 +663,23 @@ def test_skip_virtualbuildenv_run():
     # client.run("create . --name consumer --version 1.0")
     client.run("create .  consumer/1.0@ -pr:h=default -pr:b=default")
     assert "FOO is BAR" not in client.out
+
+
+def test_files_always_created():
+    """ test that even if there are no env-variables, the generators always create files,
+    they will be mostly empty, but exist
+    """
+    c = TestClient()
+    c.save({"dep/conanfile.py": GenConanfile("dep", "0.1"),
+            "consumer/conanfile.txt": "[requires]\ndep/0.1"})
+    c.run("create dep")
+    c.run("install consumer -g VirtualBuildEnv -g VirtualRunEnv")
+    ext = "bat" if platform.system() == "Windows" else "sh"
+
+    assert os.path.isfile(os.path.join(c.current_folder, "conanbuild.{}".format(ext)))
+    assert os.path.isfile(os.path.join(c.current_folder, "conanrun.{}".format(ext)))
+    assert os.path.isfile(os.path.join(c.current_folder,
+                                       "conanbuildenv-release-x86_64.{}".format(ext)))
+    assert os.path.isfile(os.path.join(c.current_folder,
+                                       "conanbuildenv-release-x86_64.{}".format(ext)))
+
diff --git a/conans/test/integration/test_components.py b/conans/test/integration/test_components.py
new file mode 100644
index 000000000..202dae8d3
--- /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"
diff --git a/conans/test/integration/toolchains/apple/test_xcodedeps.py b/conans/test/integration/toolchains/apple/test_xcodedeps.py
index 1a5ddb5f4..c60ea523a 100644
--- a/conans/test/integration/toolchains/apple/test_xcodedeps.py
+++ b/conans/test/integration/toolchains/apple/test_xcodedeps.py
@@ -1,5 +1,6 @@
 import os
 import platform
+import textwrap
 
 import pytest
 
@@ -85,3 +86,91 @@ def test_generator_files():
         assert '#include "conandeps.xcconfig"' in conan_config
 
         check_contents(client, ["hello", "goodbye"], build_type, "x86_64", "macosx", "12.1")
+
+
+@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for MacOS")
+def test_xcodedeps_aggregate_components():
+    client = TestClient()
+
+    conanfile_py = textwrap.dedent("""
+        from conan import ConanFile
+        class LibConan(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            def package_info(self):
+                self.cpp_info.includedirs = ["liba_include"]
+        """)
+
+    client.save({"conanfile.py": conanfile_py})
+
+    client.run("create . liba/1.0@")
+
+    r""""
+        1   a
+       / \ /
+      2   3
+       \ /
+        4   5  6
+        |   |  /
+         \ / /
+           7
+    """
+
+    conanfile_py = textwrap.dedent("""
+        from conan import ConanFile
+        class LibConan(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            requires = "liba/1.0"
+            def package_info(self):
+                self.cpp_info.components["libb_comp1"].includedirs = ["libb_comp1"]
+                self.cpp_info.components["libb_comp1"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp2"].includedirs = ["libb_comp2"]
+                self.cpp_info.components["libb_comp2"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp2"].requires = ["libb_comp1"]
+                self.cpp_info.components["libb_comp3"].includedirs = ["libb_comp3"]
+                self.cpp_info.components["libb_comp3"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp3"].requires = ["libb_comp1", "liba::liba"]
+                self.cpp_info.components["libb_comp4"].includedirs = ["libb_comp4"]
+                self.cpp_info.components["libb_comp4"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp4"].requires = ["libb_comp2", "libb_comp3"]
+                self.cpp_info.components["libb_comp5"].includedirs = ["libb_comp5"]
+                self.cpp_info.components["libb_comp5"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp6"].includedirs = ["libb_comp6"]
+                self.cpp_info.components["libb_comp6"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp7"].includedirs = ["libb_comp7"]
+                self.cpp_info.components["libb_comp7"].libdirs = ["mylibdir"]
+                self.cpp_info.components["libb_comp7"].requires = ["libb_comp4", "libb_comp5", "libb_comp6"]
+        """)
+
+    client.save({"conanfile.py": conanfile_py})
+
+    client.run("create . libb/1.0@")
+
+    client.run("install libb/1.0@ -g XcodeDeps")
+
+    lib_entry = client.load("conan_libb.xcconfig")
+
+    for index in range(1, 8):
+        assert f"conan_libb_libb_comp{index}.xcconfig" in lib_entry
+
+    component7_entry = client.load("conan_libb_libb_comp7.xcconfig")
+    assert '#include "conan_liba_liba.xcconfig"' in component7_entry
+
+    component7_vars = client.load("conan_libb_libb_comp7_release_x86_64.xcconfig")
+
+    # all of the transitive required components and the component itself are added
+    for index in range(1, 8):
+        assert f"libb_comp{index}" in component7_vars
+
+    assert "mylibdir" in component7_vars
+
+    component4_vars = client.load("conan_libb_libb_comp4_release_x86_64.xcconfig")
+
+    # all of the transitive required components and the component itself are added
+    for index in range(1, 5):
+        assert f"libb_comp{index}" in component4_vars
+
+    for index in range(5, 8):
+        assert f"libb_comp{index}" not in component4_vars
+
+    # folders are aggregated
+    assert "mylibdir" in component4_vars
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 41a46681e..967329a5d 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -4,6 +4,7 @@ import platform
 import textwrap
 
 import pytest
+from mock import mock
 
 from conan.tools.cmake.presets import load_cmake_presets
 from conans.test.assets.genconanfile import GenConanfile
@@ -439,3 +440,36 @@ def test_cmake_presets_singleconfig():
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
     assert len(presets["configurePresets"]) == 2
+
+
+def test_toolchain_cache_variables():
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.cmake import CMakeToolchain, CMake
+
+        class Conan(ConanFile):
+            settings = "os", "arch", "compiler", "build_type"
+
+            def generate(self):
+                toolchain = CMakeToolchain(self)
+                toolchain.cache_variables["foo"] = True
+                toolchain.cache_variables["foo2"] = False
+                toolchain.cache_variables["var"] = "23"
+                toolchain.cache_variables["CMAKE_SH"] = "THIS VALUE HAS PRIORITY"
+                toolchain.cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] = "THIS VALUE HAS PRIORITY"
+                toolchain.cache_variables["CMAKE_MAKE_PROGRAM"] = "THIS VALUE HAS NO PRIORITY"
+                toolchain.generate()
+        """)
+    client.save({"conanfile.py": conanfile})
+    with mock.patch("platform.system", mock.MagicMock(return_value="Windows")):
+        client.run("install . mylib/1.0@ -c tools.cmake.cmaketoolchain:generator='MinGW Makefiles' "
+                   "-c tools.gnu:make_program='MyMake'")
+    presets = json.loads(client.load("CMakePresets.json"))
+    cache_variables = presets["configurePresets"][0]["cacheVariables"]
+    assert cache_variables["foo"] == 'ON'
+    assert cache_variables["foo2"] == 'OFF'
+    assert cache_variables["var"] == '23'
+    assert cache_variables["CMAKE_SH"] == "THIS VALUE HAS PRIORITY"
+    assert cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] == "THIS VALUE HAS PRIORITY"
+    assert cache_variables["CMAKE_MAKE_PROGRAM"] == "MyMake"
diff --git a/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py b/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py
new file mode 100644
index 000000000..94cea9522
--- /dev/null
+++ b/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py
@@ -0,0 +1,23 @@
+import os
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+
+
+@pytest.mark.parametrize(
+    "arch,exp_platform",
+    [
+        ("x86", "Win32"),
+        ("x86_64", "x64"),
+        ("armv7", "ARM"),
+        ("armv8", "ARM64"),
+    ],
+)
+def test_msbuilddeps_maps_architecture_to_platform(arch, exp_platform):
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=msbuild_lib")
+    client.run(f"install . -g MSBuildDeps -s arch={arch} -pr:b=default -if=install")
+    toolchain = client.load(os.path.join("conan", "conantoolchain.props"))
+    expected_import = f"""<Import Condition="'$(Configuration)' == 'Release' And '$(Platform)' == '{exp_platform}'" Project="conantoolchain_release_{exp_platform.lower()}.props"/>"""
+    assert expected_import in toolchain
diff --git a/conans/test/unittests/tools/cmake/test_cmake_presets_definitions.py b/conans/test/unittests/tools/cmake/test_cmake_presets_definitions.py
index cb3f30709..d350c184b 100644
--- a/conans/test/unittests/tools/cmake/test_cmake_presets_definitions.py
+++ b/conans/test/unittests/tools/cmake/test_cmake_presets_definitions.py
@@ -40,7 +40,7 @@ def test_cmake_make_program(conanfile):
     conanfile.conf.define("tools.gnu:make_program", "C:\\mymake.exe")
 
     with mock.patch("platform.system", mock.MagicMock(return_value='Windows')):
-        write_cmake_presets(conanfile, "the_toolchain.cmake", "MinGW Makefiles")
+        write_cmake_presets(conanfile, "the_toolchain.cmake", "MinGW Makefiles", {})
 
     cmake = CMake(conanfile)
     cmake.configure()