Repository: conan-io/conan
Pull request #16279 (2024-05-16 12:19:20)

let generator overwrite xxxConfig.cmake
Changelog: Bugfix: Let ``CMakeDeps`` generator overwrite the ``xxxConfig.cmake`` when it already exists.
Docs: Omit

This is exploratory PR to see if something breaks and trying to understand why it was there.

Close https://github.com/conan-io/conan/issues/16274


Patch:
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index 6fd61d179..0fb5e1108 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -36,7 +36,8 @@ class InstallAPI:
         installer.install_system_requires(graph, only_info)
 
     def install_sources(self, graph, remotes):
-        """ Install sources for dependency graph
+        """ Install sources for dependency graph of packages to BUILD or packages that match
+        tools.build:download_source conf
         :param remotes:
         :param graph: Dependency graph to install packages for
         """
diff --git a/conan/cli/command.py b/conan/cli/command.py
index 9daeb03fd..7a074dc7c 100644
--- a/conan/cli/command.py
+++ b/conan/cli/command.py
@@ -52,7 +52,8 @@ class BaseConanCommand:
                                  "to more verbose: -vquiet, -verror, -vwarning, -vnotice, -vstatus, "
                                  "-v or -vverbose, -vv or -vdebug, -vvv or -vtrace")
         parser.add_argument("-cc", "--core-conf", action="append",
-                            help="Global configuration for Conan")
+                            help="Define core configuration, overwriting global.conf "
+                                 "values. E.g.: -cc core:non_interactive=True")
 
     @property
     def _help_formatters(self):
diff --git a/conan/tools/cmake/cmakedeps/cmakedeps.py b/conan/tools/cmake/cmakedeps/cmakedeps.py
index f8a9cca02..e982c9ae5 100644
--- a/conan/tools/cmake/cmakedeps/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps/cmakedeps.py
@@ -1,4 +1,3 @@
-import os
 import textwrap
 
 import jinja2
@@ -15,7 +14,6 @@ from conan.tools.cmake.cmakedeps.templates.target_configuration import TargetCon
 from conan.tools.cmake.cmakedeps.templates.target_data import ConfigDataTemplate
 from conan.tools.cmake.cmakedeps.templates.targets import TargetsTemplate
 from conan.tools.files import save
-from conans.client.generators import relativize_generated_file
 from conan.errors import ConanException
 from conans.model.dependencies import get_transitive_requires
 
@@ -120,8 +118,7 @@ class CMakeDeps(object):
             ret[config_version.filename] = config_version.render()
 
         data_target = ConfigDataTemplate(self, require, dep, find_module_mode)
-        data_content = relativize_generated_file(data_target.render(), self._conanfile,
-                                                 "${CMAKE_CURRENT_LIST_DIR}")
+        data_content = data_target.render()
         ret[data_target.filename] = data_content
 
         target_configuration = TargetConfigurationTemplate(self, require, dep, find_module_mode)
@@ -131,11 +128,9 @@ class CMakeDeps(object):
         ret[targets.filename] = targets.render()
 
         config = ConfigTemplate(self, require, dep, find_module_mode)
-        # Check if the XXConfig.cmake exists to keep the first generated configuration
-        # to only include the build_modules from the first conan install. The rest of the
-        # file is common for the different configurations.
-        if not os.path.exists(config.filename):
-            ret[config.filename] = config.render()
+        # Only the latest configuration BUILD_MODULES and additional_variables will be used
+        # in multi-config they will be overwritten by the latest install
+        ret[config.filename] = config.render()
 
     def set_property(self, dep, prop, value, build_context=False):
         """
diff --git a/conan/tools/cmake/cmakedeps/templates/config.py b/conan/tools/cmake/cmakedeps/templates/config.py
index 6a161d293..68c456398 100644
--- a/conan/tools/cmake/cmakedeps/templates/config.py
+++ b/conan/tools/cmake/cmakedeps/templates/config.py
@@ -83,7 +83,7 @@ class ConfigTemplate(CMakeDepsFileTemplate):
 
         {% endfor %}
 
-        # Only the first installed configuration is included to avoid the collision
+        # Only the last installed configuration BUILD_MODULES are included to avoid the collision
         foreach(_BUILD_MODULE {{ pkg_var(pkg_name, 'BUILD_MODULES_PATHS', config_suffix) }} )
             message({% raw %}${{% endraw %}{{ file_name }}_MESSAGE_MODE} "Conan: Including build module from '${_BUILD_MODULE}'")
             include({{ '${_BUILD_MODULE}' }})
diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py
index e86d89be4..315e2cf30 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_data.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_data.py
@@ -5,7 +5,7 @@ from conan.tools.cmake.cmakedeps import FIND_MODE_NONE, FIND_MODE_CONFIG, FIND_M
     FIND_MODE_BOTH
 from conan.tools.cmake.cmakedeps.templates import CMakeDepsFileTemplate
 from conan.errors import ConanException
-
+from conans.client.generators import relativize_path
 
 """
 
@@ -57,6 +57,9 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         # Make the root_folder relative to the generated xxx-data.cmake file
         root_folder = self._root_folder
         root_folder = root_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
+        # it is the relative path of the caller, not the dependency
+        root_folder = relativize_path(root_folder, self.cmakedeps._conanfile,
+                                      "${CMAKE_CURRENT_LIST_DIR}")
 
         return {"global_cpp": global_cpp,
                 "has_components": self.conanfile.cpp_info.has_components,
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 02d6c4723..fbd719972 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -16,6 +16,7 @@ from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME
 from conan.tools.cmake.utils import is_multi_configuration
 from conan.tools.intel import IntelCC
 from conan.tools.microsoft.visual import msvc_version_to_toolset_version
+from conans.client.generators import relativize_path
 from conans.client.subsystems import deduce_subsystem, WINDOWS
 from conan.errors import ConanException
 from conans.util.files import load
@@ -146,8 +147,9 @@ class VSDebuggerEnvironment(Block):
         bin_dirs = [p for dep in host_deps for p in dep.cpp_info.aggregated_components().bindirs]
         test_bindirs = [p for dep in test_deps for p in dep.cpp_info.aggregated_components().bindirs]
         bin_dirs.extend(test_bindirs)
+        bin_dirs = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
+                    for p in bin_dirs]
         bin_dirs = [p.replace("\\", "/") for p in bin_dirs]
-
         bin_dirs = ";".join(bin_dirs) if bin_dirs else None
         if bin_dirs:
             config_dict[build_type] = bin_dirs
@@ -238,6 +240,8 @@ class LinkerScriptsBlock(Block):
         if not linker_scripts:
             return
         linker_scripts = [linker_script.replace('\\', '/') for linker_script in linker_scripts]
+        linker_scripts = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
+                          for p in linker_scripts]
         linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts]
         return {"linker_script_flags": " ".join(linker_script_flags)}
 
@@ -324,6 +328,8 @@ class AndroidSystemBlock(Block):
         if not android_ndk_path:
             raise ConanException('CMakeToolchain needs tools.android:ndk_path configuration defined')
         android_ndk_path = android_ndk_path.replace("\\", "/")
+        android_ndk_path = relativize_path(android_ndk_path, self._conanfile,
+                                           "${CMAKE_CURRENT_LIST_DIR}")
 
         use_cmake_legacy_toolchain = self._conanfile.conf.get("tools.android:cmake_legacy_toolchain",
                                                               check_type=bool)
@@ -430,6 +436,8 @@ class AppleSystemBlock(Block):
             "enable_visibility": enable_visibility
         }
         if host_sdk_name:
+            host_sdk_name = relativize_path(host_sdk_name, self._conanfile,
+                                            "${CMAKE_CURRENT_LIST_DIR}")
             ctxt_toolchain["cmake_osx_sysroot"] = host_sdk_name
         # this is used to initialize the OSX_ARCHITECTURES property on each target as it is created
         if host_architecture:
@@ -527,23 +535,22 @@ class FindFiles(Block):
                 host_runtime_dirs = {}
                 for k, v in matches:
                     host_runtime_dirs.setdefault(k, []).append(v)
-        
+
         # Calculate the dirs for the current build_type
         runtime_dirs = []
         for req in host_req:
             cppinfo = req.cpp_info.aggregated_components()
             runtime_dirs.extend(cppinfo.bindirs if is_win else cppinfo.libdirs)
-        
+
         build_type = settings.get_safe("build_type")
         host_runtime_dirs[build_type] = [s.replace("\\", "/") for s in runtime_dirs]
 
         return host_runtime_dirs
 
-    @staticmethod
-    def _join_paths(paths):
-        return " ".join(['"{}"'.format(p.replace('\\', '/')
-                                        .replace('$', '\\$')
-                                        .replace('"', '\\"')) for p in paths])
+    def _join_paths(self, paths):
+        paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}") for p in paths]
+        paths = [p.replace('\\', '/').replace('$', '\\$').replace('"', '\\"') for p in paths]
+        return " ".join([f'"{p}"' for p in paths])
 
     def context(self):
         # To find the generated cmake_find_package finders
@@ -630,7 +637,10 @@ class UserToolchain(Block):
         # This is global [conf] injection of extra toolchain files
         user_toolchain = self._conanfile.conf.get("tools.cmake.cmaketoolchain:user_toolchain",
                                                   default=[], check_type=list)
-        return {"paths": [ut.replace("\\", "/") for ut in user_toolchain]}
+        paths = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
+                 for p in user_toolchain]
+        paths = [p.replace("\\", "/") for p in paths]
+        return {"paths": paths}
 
 
 class ExtraFlagsBlock(Block):
@@ -849,9 +859,10 @@ class GenericSystemBlock(Block):
             toolset = settings.get_safe("compiler.toolset")
             if toolset is None:
                 compiler_version = str(settings.compiler.version)
-                compiler_update = str(settings.compiler.update)
+                msvc_update = conanfile.conf.get("tools.microsoft:msvc_update")
+                compiler_update = msvc_update or settings.get_safe("compiler.update")
                 toolset = msvc_version_to_toolset_version(compiler_version)
-                if compiler_update != "None":  # It is full one(19.28), not generic 19.2X
+                if compiler_update is not None:  # It is full one(19.28), not generic 19.2X
                     # The equivalent of compiler 19.26 is toolset 14.26
                     toolset += ",version=14.{}{}".format(compiler_version[-1], compiler_update)
         elif compiler == "clang":
@@ -867,6 +878,7 @@ class GenericSystemBlock(Block):
             toolset = toolset_arch if toolset is None else "{},{}".format(toolset, toolset_arch)
         toolset_cuda = conanfile.conf.get("tools.cmake.cmaketoolchain:toolset_cuda")
         if toolset_cuda is not None:
+            toolset_cuda = relativize_path(toolset_cuda, conanfile, "${CMAKE_CURRENT_LIST_DIR}")
             toolset_cuda = f"cuda={toolset_cuda}"
             toolset = toolset_cuda if toolset is None else f"{toolset},{toolset_cuda}"
         return toolset
@@ -990,6 +1002,9 @@ class GenericSystemBlock(Block):
         # This is handled by the tools.apple:sdk_path and CMAKE_OSX_SYSROOT in Apple
         cmake_sysroot = self._conanfile.conf.get("tools.build:sysroot")
         cmake_sysroot = cmake_sysroot.replace("\\", "/") if cmake_sysroot is not None else None
+        if cmake_sysroot is not None:
+            cmake_sysroot = relativize_path(cmake_sysroot, self._conanfile,
+                                            "${CMAKE_CURRENT_LIST_DIR}")
 
         result = self._get_winsdk_version(system_version, generator_platform)
         system_version, winsdk_version, gen_platform_sdk_version = result
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 549e2c0b3..6924a1d9f 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -19,7 +19,6 @@ from conan.tools.env import VirtualBuildEnv, VirtualRunEnv
 from conan.tools.intel import IntelCC
 from conan.tools.microsoft import VCVars
 from conan.tools.microsoft.visual import vs_ide_version
-from conans.client.generators import relativize_generated_file
 from conan.errors import ConanException
 from conans.model.options import _PackageOption
 from conans.util.files import save
@@ -202,7 +201,6 @@ class CMakeToolchain(object):
         context = self._context()
         content = Template(self._template, trim_blocks=True, lstrip_blocks=True,
                            keep_trailing_newline=True).render(**context)
-        content = relativize_generated_file(content, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
         return content
 
     @property
diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py
index 73f0d6d82..661b88db3 100644
--- a/conan/tools/files/files.py
+++ b/conan/tools/files/files.py
@@ -66,7 +66,7 @@ def rmdir(conanfile, path):
     _internal_rmdir(path)
 
 
-def rm(conanfile, pattern, folder, recursive=False):
+def rm(conanfile, pattern, folder, recursive=False, excludes=None):
     """
     Utility functions to remove files matching a ``pattern`` in a ``folder``.
 
@@ -74,10 +74,17 @@ def rm(conanfile, pattern, folder, recursive=False):
     :param pattern: Pattern that the files to be removed have to match (fnmatch).
     :param folder: Folder to search/remove the files.
     :param recursive: If ``recursive`` is specified it will search in the subfolders.
+    :param excludes: (Optional, defaulted to None) A tuple/list of fnmatch patterns or even a
+                     single one to be excluded from the remove pattern.
     """
+    if excludes and not isinstance(excludes, (tuple, list)):
+        excludes = (excludes,)
+    elif not excludes:
+        excludes = []
+
     for root, _, filenames in os.walk(folder):
         for filename in filenames:
-            if fnmatch(filename, pattern):
+            if fnmatch(filename, pattern) and not any(fnmatch(filename, it) for it in excludes):
                 fullname = os.path.join(root, filename)
                 os.unlink(fullname)
         if not recursive:
diff --git a/conan/tools/microsoft/visual.py b/conan/tools/microsoft/visual.py
index 1632e3b95..844031489 100644
--- a/conan/tools/microsoft/visual.py
+++ b/conan/tools/microsoft/visual.py
@@ -33,7 +33,8 @@ def check_min_vs(conanfile, version, raise_invalid=True):
                             "11": "170"}.get(compiler_version)
     elif compiler == "msvc":
         compiler_version = conanfile.settings.get_safe("compiler.version")
-        compiler_update = conanfile.settings.get_safe("compiler.update")
+        msvc_update = conanfile.conf.get("tools.microsoft:msvc_update")
+        compiler_update = msvc_update or conanfile.settings.get_safe("compiler.update")
         if compiler_version and compiler_update is not None:
             compiler_version += ".{}".format(compiler_update)
 
@@ -169,7 +170,7 @@ class VCVars:
 
         is_ps1 = conanfile.conf.get("tools.env.virtualenv:powershell", check_type=bool, default=False)
         if is_ps1:
-            content_ps1 = textwrap.dedent(rf"""\
+            content_ps1 = textwrap.dedent(rf"""
             if (-not $env:VSCMD_ARG_VCVARS_VER){{
                 Push-Location "$PSScriptRoot"
                 cmd /c "conanvcvars.bat&set" |
@@ -180,7 +181,7 @@ class VCVars:
                 }}
                 Pop-Location
                 write-host conanvcvars.ps1: Activated environment}}
-            """)
+            """).strip()
             conan_vcvars_ps1 = f"{CONAN_VCVARS}.ps1"
             create_env_script(conanfile, content_ps1, conan_vcvars_ps1, scope)
             _create_deactivate_vcvars_file(conanfile, conan_vcvars_ps1)
diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py
index eeed8ddbb..de0d06fd1 100644
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -207,15 +207,6 @@ def _generate_aggregated_env(conanfile):
         conanfile.output.info(f"Generated aggregated env files: {generated}")
 
 
-def relativize_generated_file(content, conanfile, placeholder):
-    abs_base_path, new_path = relativize_paths(conanfile, placeholder)
-    if abs_base_path is None:
-        return content
-    content = content.replace(abs_base_path, new_path)
-    content = content.replace(abs_base_path.replace("\\", "/"), new_path.replace("\\", "/"))
-    return content
-
-
 def relativize_paths(conanfile, placeholder):
     abs_base_path = conanfile.folders._base_generators
     if not abs_base_path or not os.path.isabs(abs_base_path):
@@ -229,3 +220,17 @@ def relativize_paths(conanfile, placeholder):
     new_path = placeholder if rel_path == "." else os.path.join(placeholder, rel_path)
     new_path = os.path.join(new_path, "")  # For the trailing / to dissambiguate matches
     return abs_base_path, new_path
+
+
+def relativize_path(path, conanfile, placeholder):
+    abs_base_path, new_path = relativize_paths(conanfile, placeholder)
+    if abs_base_path is None:
+        return path
+    if path.startswith(abs_base_path):
+        path = path.replace(abs_base_path, new_path, 1)
+    else:
+        abs_base_path = abs_base_path.replace("\\", "/")
+        new_path = new_path.replace("\\", "/")
+        if path.startswith(abs_base_path):
+            path = path.replace(abs_base_path, new_path, 1)
+    return path
diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py
index 967ddc90e..1d67e7cf5 100644
--- a/conans/client/graph/compatibility.py
+++ b/conans/client/graph/compatibility.py
@@ -41,8 +41,8 @@ def cppstd_compat(conanfile):
         cppstd_possible_values = supported_cppstd(conanfile)
         if cppstd_possible_values is None:
             conanfile.output.warning(f'No cppstd compatibility defined for compiler "{compiler}"')
-        else:
-            factors.append([{"compiler.cppstd": v} for v in cppstd_possible_values if v != cppstd])
+        else: # The current cppst must be included in case there is other factor
+            factors.append([{"compiler.cppstd": v} for v in cppstd_possible_values])
 
     if compiler == "msvc":
         msvc_fallback = {"194": "193"}.get(compiler_version)
diff --git a/conans/client/installer.py b/conans/client/installer.py
index 5f62d1527..ce4755728 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -176,11 +176,11 @@ class BinaryInstaller:
         self._hook_manager = app.hook_manager
         self._global_conf = global_conf
 
-    def _install_source(self, node, remotes):
+    def _install_source(self, node, remotes, need_conf=False):
         conanfile = node.conanfile
         download_source = conanfile.conf.get("tools.build:download_source", check_type=bool)
 
-        if not download_source and node.binary != BINARY_BUILD:
+        if not download_source and (need_conf or node.binary != BINARY_BUILD):
             return
 
         conanfile = node.conanfile
@@ -235,7 +235,7 @@ class BinaryInstaller:
         for level in install_order:
             for install_reference in level:
                 for package in install_reference.packages.values():
-                    self._install_source(package.nodes[0], remotes)
+                    self._install_source(package.nodes[0], remotes, need_conf=True)
 
     def install(self, deps_graph, remotes, install_order=None):
         assert not deps_graph.error, "This graph cannot be installed: {}".format(deps_graph)
diff --git a/conans/client/rest_client_local_recipe_index.py b/conans/client/rest_client_local_recipe_index.py
index 041406d50..9285d7942 100644
--- a/conans/client/rest_client_local_recipe_index.py
+++ b/conans/client/rest_client_local_recipe_index.py
@@ -194,6 +194,9 @@ class _LocalRecipesIndexLayout:
 
         loader = ConanFileLoader(None)
         for r in recipes:
+            if r.startswith("."):
+                # Skip hidden folders, no recipes should start with a dot
+                continue
             if not fnmatch(r, name_pattern):
                 continue
             folder = self._get_base_folder(r)
diff --git a/conans/model/conf.py b/conans/model/conf.py
index ef758d342..7809f4d9d 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -93,6 +93,7 @@ BUILT_IN_CONFS = {
     "tools.meson.mesontoolchain:backend": "Any Meson backend: ninja, vs, vs2010, vs2012, vs2013, vs2015, vs2017, vs2019, xcode",
     "tools.meson.mesontoolchain:extra_machine_files": "List of paths for any additional native/cross file references to be appended to the existing Conan ones",
     "tools.microsoft:winsdk_version": "Use this winsdk_version in vcvars",
+    "tools.microsoft:msvc_update": "Force the specific update irrespective of compiler.update (CMakeToolchain and VCVars)",
     "tools.microsoft.msbuild:vs_version": "Defines the IDE version (15, 16, 17) when using the msvc compiler. Necessary if compiler.version specifies a toolset that is not the IDE default",
     "tools.microsoft.msbuild:max_cpu_count": "Argument for the /m when running msvc to build parallel projects",
     "tools.microsoft.msbuild:installation_path": "VS install path, to avoid auto-detect via vswhere, like C:/Program Files (x86)/Microsoft Visual Studio/2019/Community. Use empty string to disable",


Test patch:
diff --git a/test/functional/toolchains/env/test_virtualenv_powershell.py b/test/functional/toolchains/env/test_virtualenv_powershell.py
index 0f4e2e3f6..787eda008 100644
--- a/test/functional/toolchains/env/test_virtualenv_powershell.py
+++ b/test/functional/toolchains/env/test_virtualenv_powershell.py
@@ -148,6 +148,10 @@ def test_vcvars():
     assert "conanvcvars.ps1: Activated environment" in client.out
     #check that the new env variables are set
     assert "VSCMD_ARG_VCVARS_VER" in client.out
+
+    client.run_command(r'powershell.exe ".\build\Release\generators\conanvcvars.ps1"')
+    assert client.out.strip() == "conanvcvars.ps1: Activated environment"
+
     conanbuild = client.load(r".\build\Release\generators\conanbuild.ps1")
     vcvars_ps1 = client.load(r".\build\Release\generators\conanvcvars.ps1")
     #check that the conanvcvars.ps1 is being added to the conanbuild.ps1
diff --git a/test/integration/command/test_forced_download_source.py b/test/integration/command/test_forced_download_source.py
index dfe814815..cc06ceae6 100644
--- a/test/integration/command/test_forced_download_source.py
+++ b/test/integration/command/test_forced_download_source.py
@@ -44,6 +44,9 @@ def test_install(client):
 def test_info(client):
     client.run("graph info --requires=dep/0.1")
     assert "RUNNING SOURCE" not in client.out
+    # Even if the package is to be built, it shouldn't download sources unless the conf is defined
+    client.run("graph info --requires=dep/0.1 --build=dep*")
+    assert "RUNNING SOURCE" not in client.out
     client.run("graph info --requires=dep/0.1 -c tools.build:download_source=True")
     assert "RUNNING SOURCE" in client.out
     client.run("graph info --requires=dep/0.1 -c tools.build:download_source=True")
diff --git a/test/integration/package_id/test_cache_compatibles.py b/test/integration/package_id/test_cache_compatibles.py
index 1a9952f51..35fe89e92 100644
--- a/test/integration/package_id/test_cache_compatibles.py
+++ b/test/integration/package_id/test_cache_compatibles.py
@@ -398,6 +398,13 @@ class TestDefaultCompat:
         assert "mylib/1.0: Main binary package 'e340edd75790e7156c595edebd3d98b10a2e091e' missing."\
                f"Using compatible package '{package_id1}'"
 
+        c.run("install --requires=mylib/1.0@ -s os=Windows -s arch=x86_64 -s build_type=Release "
+              "-s compiler=msvc "
+              "-s compiler.version=194 -s compiler.cppstd=17 "
+              "-s compiler.runtime=dynamic -pr:b=profile_build")
+        assert "mylib/1.0: Main binary package 'e340edd75790e7156c595edebd3d98b10a2e091e' missing." \
+               f"Using compatible package '{package_id1}'"
+
 
 class TestErrorsCompatibility:
     """ when the plugin fails, we want a clear message and a helpful trace
diff --git a/test/integration/remote/test_local_recipes_index.py b/test/integration/remote/test_local_recipes_index.py
index 2876832b6..2b5114968 100644
--- a/test/integration/remote/test_local_recipes_index.py
+++ b/test/integration/remote/test_local_recipes_index.py
@@ -59,12 +59,15 @@ def c3i_folder():
             """))
     save(os.path.join(recipes_folder, "libcurl", "all", "conanfile.py"),
          str(GenConanfile().with_require("openssl/2.0")))
+
+    save(os.path.join(recipes_folder, ".DS_Store", "foo"), "")
+
     return folder
 
 
 class TestSearchList:
     def test_basic_search(self, c3i_folder):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run(f"remote add local '{c3i_folder}' --type=local-recipes-index")  # Keep --type test
         assert "WARN" not in client.out  # Make sure it does not complain about url
         client.run("search *")
@@ -82,7 +85,7 @@ class TestSearchList:
             """) in client.out
 
     def test_list_refs(self, c3i_folder):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run(f"remote add local '{c3i_folder}'")
         client.run("list *#* -r=local --format=json")
         listjson = json.loads(client.stdout)
@@ -100,7 +103,7 @@ class TestSearchList:
         assert len(revs) == 1 and "6f5c31bb1219e9393743d1fbf2ee1b52" in revs
 
     def test_list_rrevs(self, c3i_folder):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run(f"remote add local '{c3i_folder}'")
         client.run("list libcurl/1.0#* -r=local --format=json")
         listjson = json.loads(client.stdout)
@@ -108,7 +111,7 @@ class TestSearchList:
         assert len(revs) == 1 and "e468388f0e4e098d5b62ad68979aebd5" in revs
 
     def test_list_binaries(self, c3i_folder):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run(f"remote add local '{c3i_folder}'")
         client.run("list libcurl/1.0:* -r=local --format=json")
         listjson = json.loads(client.stdout)
@@ -118,7 +121,7 @@ class TestSearchList:
 
 class TestInstall:
     def test_install(self, c3i_folder):
-        c = TestClient()
+        c = TestClient(light=True)
         c.run(f"remote add local '{c3i_folder}'")
         c.run("install --requires=libcurl/1.0 --build missing")
         assert "zlib/1.2.11: CONANDATA: {}" in c.out
@@ -178,7 +181,7 @@ class TestInstall:
                    {"boost/config.yml": boost_config,
                     "boost/all/conanfile.py": boost,
                     "boost/all/dependencies/myfile.json": deps_json})
-        c = TestClient()
+        c = TestClient(light=True)
         c.run(f"remote add local '{folder}'")
         c.run("install --requires=boost/[*] --build missing")
         assert 'boost/1.0: {"potato": 42}' in c.out
@@ -204,7 +207,7 @@ class TestInstall:
                    {"pkg/config.yml": config,
                     "pkg/all/conanfile.py": str(GenConanfile("pkg")),
                     "pkg/all/conandata.yml": conandata})
-        c = TestClient()
+        c = TestClient(light=True)
         c.run(f"remote add local '{folder}'")
         c.run("install --requires=pkg/1.0 --build missing -vvv")
         assert "pkg/1.0#86b609916bbdfe63c579f034ad0edfe7" in c.out
@@ -264,7 +267,7 @@ class TestInstall:
                     "zlib/all/conandata.yml": conandata_yml,
                     "zlib/all/patches/patch1": patch,
                     "zlib/all/main.cpp": "\n"})
-        client = TestClient()
+        client = TestClient(light=True)
         client.run(f"remote add local '{folder}'")
         client.run("install --requires=zlib/0.1 --build=missing -vv")
         assert "zlib/0.1: Copied 1 file: patch1" in client.out
@@ -300,7 +303,7 @@ class TestRestrictedOperations:
         folder = temp_folder()
         c3i_folder = os.path.join(folder, "recipes")
         mkdir(c3i_folder)
-        c = TestClient()
+        c = TestClient(light=True)
         c.run(f"remote add local '{c3i_folder}'")
         c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
         c.run("create .")
@@ -324,7 +327,7 @@ class TestErrorsUx:
         save_files(recipes_folder,
                    {"zlib/config.yml": zlib_config,
                     "zlib/all/conanfile.py": zlib})
-        c = TestClient()
+        c = TestClient(light=True)
         c.run(f"remote add local '{folder}'")
         c.run("install --requires=zlib/[*] --build missing", assert_error=True)
         assert "NameError: name 'ConanFile' is not defined" in c.out
diff --git a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
index 563f14069..50c2ec587 100644
--- a/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -708,6 +708,7 @@ def test_cmakedeps_set_property_overrides():
     other = c.load("app/other-release-data.cmake")
     assert 'set(other_other_mycomp1_NO_SONAME_MODE_RELEASE TRUE)' in other
 
+
 def test_cmakedeps_set_legacy_variable_name():
     client = TestClient()
     base_conanfile = str(GenConanfile("dep", "1.0"))
@@ -742,3 +743,17 @@ def test_cmakedeps_set_legacy_variable_name():
         assert f"prefix_{variable}" in dep_config
     # Check that variables are not duplicated
     assert dep_config.count("PREFIX_VERSION") == 1
+
+
+def test_different_versions():
+    # https://github.com/conan-io/conan/issues/16274
+    c = TestClient()
+    c.save({"dep/conanfile.py": GenConanfile("dep")})
+    c.run("create dep --version 1.2")
+    c.run("create dep --version 2.3")
+    c.run("install --requires=dep/1.2 -g CMakeDeps")
+    config = c.load("dep-config.cmake")
+    assert 'set(dep_VERSION_STRING "1.2")' in config
+    c.run("install --requires=dep/2.3 -g CMakeDeps")
+    config = c.load("dep-config.cmake")
+    assert 'set(dep_VERSION_STRING "2.3")' in config
diff --git a/test/unittests/tools/cmake/test_cmaketoolchain.py b/test/unittests/tools/cmake/test_cmaketoolchain.py
index 8bba79509..3d66adada 100644
--- a/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -201,7 +201,7 @@ def conanfile_msvc():
     c.settings.build_type = "Release"
     c.settings.arch = "x86"
     c.settings.compiler = "msvc"
-    c.settings.compiler.version = "193"
+    c.settings.compiler.version = "194"
     c.settings.compiler.cppstd = "20"
     c.settings.os = "Windows"
     c.settings_build = c.settings
@@ -221,11 +221,27 @@ def test_toolset(conanfile_msvc):
 
 
 def test_toolset_update_version(conanfile_msvc):
+    conanfile_msvc.settings.compiler.version = "193"
     conanfile_msvc.settings.compiler.update = "8"
     toolchain = CMakeToolchain(conanfile_msvc)
     assert 'set(CMAKE_GENERATOR_TOOLSET "v143,version=14.38" CACHE STRING "" FORCE)' in toolchain.content
 
 
+def test_toolset_update_version_conf(conanfile_msvc):
+    conanfile_msvc.settings.compiler.version = "193"
+    conanfile_msvc.conf.define("tools.microsoft:msvc_update", "7")
+    toolchain = CMakeToolchain(conanfile_msvc)
+    assert 'set(CMAKE_GENERATOR_TOOLSET "v143,version=14.37" CACHE STRING "" FORCE)' in toolchain.content
+
+
+def test_toolset_update_version_forced_conf(conanfile_msvc):
+    conanfile_msvc.settings.compiler.version = "193"
+    conanfile_msvc.settings.compiler.update = "8"
+    conanfile_msvc.conf.define("tools.microsoft:msvc_update", "7")
+    toolchain = CMakeToolchain(conanfile_msvc)
+    assert 'set(CMAKE_GENERATOR_TOOLSET "v143,version=14.37" CACHE STRING "" FORCE)' in toolchain.content
+
+
 def test_toolset_update_version_overflow(conanfile_msvc):
     # https://github.com/conan-io/conan/issues/15583
     conanfile_msvc.settings.compiler.version = "194"
diff --git a/test/unittests/tools/files/test_rm.py b/test/unittests/tools/files/test_rm.py
index 75f32fa84..b569ac331 100644
--- a/test/unittests/tools/files/test_rm.py
+++ b/test/unittests/tools/files/test_rm.py
@@ -1,4 +1,5 @@
 import os
+import pytest
 
 # Check it is importable from tools
 from conan.tools.files import rm, chdir
@@ -63,3 +64,46 @@ def test_remove_files_by_mask_non_recursively():
 
     assert os.path.exists(os.path.join(tmpdir, "1.txt"))
     assert os.path.exists(os.path.join(tmpdir, "subdir", "2.txt"))
+
+
+@pytest.mark.parametrize("recursive", [False, True])
+@pytest.mark.parametrize("results", [
+    ["*.dll", ("foo.dll",)],
+    [("*.dll",), ("foo.dll",)],
+    [["*.dll"], ("foo.dll",)],
+    [("*.dll", "*.lib"), ("foo.dll", "foo.dll.lib")],
+])
+def test_exclude_pattern_from_remove_list(recursive, results):
+    """ conan.tools.files.rm should not remove files that match the pattern but are excluded
+        by the excludes parameter.
+        It should obey the recursive parameter, only excluding the files in the root folder in case
+        it is False.
+    """
+    excludes, expected_files = results
+    temporary_folder = temp_folder()
+    with chdir(None, temporary_folder):
+        os.makedirs("subdir")
+
+    save_files(temporary_folder, {
+        "1.txt": "",
+        "1.pdb": "",
+        "1.pdb1": "",
+        "foo.dll": "",
+        "foo.dll.lib": "",
+        os.path.join("subdir", "2.txt"): "",
+        os.path.join("subdir", "2.pdb"): "",
+        os.path.join("subdir", "foo.dll"): "",
+        os.path.join("subdir", "foo.dll.lib"): "",
+        os.path.join("subdir", "2.pdb1"): ""})
+
+    rm(None, "*", temporary_folder, excludes=excludes, recursive=recursive)
+
+    for it in expected_files:
+        assert os.path.exists(os.path.join(temporary_folder, it))
+    assert not os.path.exists(os.path.join(temporary_folder, "1.pdb"))
+
+    # Check the recursive parameter and subfolder
+    condition = (lambda x: not x) if recursive else (lambda x: x)
+    assert condition(os.path.exists(os.path.join(temporary_folder, "subdir", "2.pdb")))
+    for it in expected_files:
+        assert os.path.exists(os.path.join(temporary_folder, "subdir", it))