Repository: conan-io/conan
Pull request #18280 (2025-05-09 15:16:19)

trying again to define CMAKE_XX_COMPILER in generated presets, only for cl
Changelog: Feature: define CMAKE_C/CXX_COMPILER in ``CMakeToolchain`` generated presets, only for MSVC cl-like compilers, automatically only for Ninja generator
Docs: Omit

Close https://github.com/conan-io/conan/issues/13136



Patch:
diff --git a/conan/api/subapi/cache.py b/conan/api/subapi/cache.py
index b0d3ede2d..5036772c8 100644
--- a/conan/api/subapi/cache.py
+++ b/conan/api/subapi/cache.py
@@ -6,7 +6,6 @@ from io import BytesIO
 
 from conan.api.model import PackagesList
 from conan.api.output import ConanOutput
-from conan.internal.api.uploader import gzopen_without_timestamps
 from conan.internal.cache.cache import PkgCache
 from conan.internal.cache.conan_reference_layout import EXPORT_SRC_FOLDER, EXPORT_FOLDER, SRC_FOLDER, \
     METADATA, DOWNLOAD_EXPORT_FOLDER
@@ -17,7 +16,7 @@ from conan.errors import ConanException
 from conan.api.model import PkgReference
 from conan.api.model import RecipeReference
 from conan.internal.util.dates import revision_timestamp_now
-from conan.internal.util.files import rmdir, mkdir, remove
+from conan.internal.util.files import rmdir, gzopen_without_timestamps, mkdir, remove
 
 
 class CacheAPI:
@@ -134,7 +133,7 @@ class CacheAPI:
         name = os.path.basename(tgz_path)
         compresslevel = global_conf.get("core.gzip:compresslevel", check_type=int)
         with open(tgz_path, "wb") as tgz_handle:
-            tgz = gzopen_without_timestamps(name, fileobj=tgz_handle,
+            tgz = gzopen_without_timestamps(name, mode="w", fileobj=tgz_handle,
                                             compresslevel=compresslevel)
             for ref, ref_bundle in package_list.refs().items():
                 ref_layout = cache.recipe_layout(ref)
diff --git a/conan/internal/api/config/config_installer.py b/conan/internal/api/config/config_installer.py
index e411897c9..120ee44c6 100644
--- a/conan/internal/api/config/config_installer.py
+++ b/conan/internal/api/config/config_installer.py
@@ -1,7 +1,6 @@
 import os
 import shutil
 import fnmatch
-import zipfile
 
 from urllib.parse import urlparse, urlsplit
 from contextlib import contextmanager
@@ -10,9 +9,8 @@ from conan.api.output import ConanOutput
 from conan.internal.paths import find_file_walk_up
 from conan.internal.rest.file_downloader import FileDownloader
 from conan.errors import ConanException
-from conan.internal.util.files import mkdir, rmdir, remove, chdir
+from conan.internal.util.files import mkdir, rmdir, remove, unzip, chdir
 from conan.internal.util.runners import detect_runner
-from conan.tools.files.files import untargz
 
 
 class _ConanIgnoreMatcher:
@@ -87,20 +85,7 @@ def _process_git_repo(config, cache_folder):
 
 
 def _process_zip_file(config, zippath, cache_folder, tmp_folder, first_remove=False):
-    # First, unzip. This is repeated with the tools.unzip, but better do not mess
-    # Same list as below
-    tgz_exts = (".tar.gz", ".tgz", ".tbz2", ".tar.bz2", ".tar", ".tar.xz", ".txz")
-    if any(zippath.endswith(e) for e in tgz_exts):
-        untargz(zippath, tmp_folder)
-    else:
-        full_path = os.path.normpath(os.path.join(os.getcwd(), tmp_folder))
-        with zipfile.ZipFile(zippath, "r") as z:
-            zip_info = z.infolist()
-            extracted_size = 0
-            for file_ in zip_info:
-                extracted_size += file_.file_size
-                z.extract(file_, full_path)
-
+    unzip(zippath, tmp_folder)
     if first_remove:
         os.unlink(zippath)
     _process_folder(config, tmp_folder, cache_folder)
@@ -205,7 +190,7 @@ def _is_compressed_file(filename):
     import zipfile
     if zipfile.is_zipfile(filename):
         return True
-    tgz_exts = (".tar.gz", ".tgz", ".tbz2", ".tar.bz2", ".tar", ".tar.xz", ".txz")
+    tgz_exts = (".tar.gz", ".tgz", ".tbz2", ".tar.bz2", ".tar", ".gz", ".tar.xz", ".txz")
     return any(filename.endswith(e) for e in tgz_exts)
 
 
diff --git a/conan/internal/api/uploader.py b/conan/internal/api/uploader.py
index b075e4f07..4433921ce 100644
--- a/conan/internal/api/uploader.py
+++ b/conan/internal/api/uploader.py
@@ -1,8 +1,6 @@
 import fnmatch
-import gzip
 import os
 import shutil
-import tarfile
 import time
 
 from conan.internal.conan_app import ConanApp
@@ -13,7 +11,7 @@ from conan.errors import ConanException
 from conan.internal.paths import (CONAN_MANIFEST, CONANFILE, EXPORT_SOURCES_TGZ_NAME,
                                   EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME, CONANINFO)
 from conan.internal.util.files import (clean_dirty, is_dirty, gather_files,
-                                       set_dirty_context_manager, mkdir, human_size)
+                                       gzopen_without_timestamps, set_dirty_context_manager, mkdir, human_size)
 
 UPLOAD_POLICY_FORCE = "force-upload"
 UPLOAD_POLICY_SKIP = "skip-upload"
@@ -256,28 +254,14 @@ class UploadExecutor:
         output.debug(f"Upload {pref} in {duration} time")
 
 
-def gzopen_without_timestamps(name, fileobj, compresslevel=None):
-    """ !! Method overrided by laso to pass mtime=0 (!=None) to avoid time.time() was
-        setted in Gzip file causing md5 to change. Not possible using the
-        previous tarfile open because arguments are not passed to GzipFile constructor
-    """
-    compresslevel = compresslevel if compresslevel is not None else 9  # default Gzip = 9
-    fileobj = gzip.GzipFile(name, "w", compresslevel, fileobj, mtime=0)
-    # Format is forced because in Python3.8, it changed and it generates different tarfiles
-    # with different checksums, which break hashes of tgzs
-    # PAX_FORMAT is the default for Py38, lets make it explicit for older Python versions
-    t = tarfile.TarFile.taropen(name, "w", fileobj, format=tarfile.PAX_FORMAT)
-    t._extfileobj = False
-    return t
-
-
 def compress_files(files, name, dest_dir, compresslevel=None, ref=None):
     t1 = time.time()
     # FIXME, better write to disk sequentially and not keep tgz contents in memory
     tgz_path = os.path.join(dest_dir, name)
     ConanOutput(scope=str(ref)).info(f"Compressing {name}")
     with set_dirty_context_manager(tgz_path), open(tgz_path, "wb") as tgz_handle:
-        tgz = gzopen_without_timestamps(name, fileobj=tgz_handle, compresslevel=compresslevel)
+        tgz = gzopen_without_timestamps(name, mode="w", fileobj=tgz_handle,
+                                        compresslevel=compresslevel)
         for filename, abs_path in sorted(files.items()):
             # recursive is False in case it is a symlink to a folder
             tgz.add(abs_path, filename, recursive=False)
diff --git a/conan/internal/graph/graph.py b/conan/internal/graph/graph.py
index 8e4dc2e29..8348af88d 100644
--- a/conan/internal/graph/graph.py
+++ b/conan/internal/graph/graph.py
@@ -342,9 +342,7 @@ class Overrides:
         return self._overrides.items()
 
     def serialize(self):
-        return {k.repr_notime():
-                    sorted([e.repr_notime() if e else None for e in v],
-                           key= lambda e: "" if e is None else e)
+        return {k.repr_notime(): [e.repr_notime() if e else None for e in v]
                 for k, v in self._overrides.items()}
 
     @staticmethod
diff --git a/conan/internal/model/info.py b/conan/internal/model/info.py
index e5f59d845..2e7798970 100644
--- a/conan/internal/model/info.py
+++ b/conan/internal/model/info.py
@@ -24,13 +24,15 @@ class _VersionRepr:
             return str(self._version.major)
         return ".".join([str(self._version.major), 'Y', 'Z'])
 
-    def minor(self):
+    def minor(self, fill=True):
         if not isinstance(self._version.major.value, int):
             return str(self._version.major)
 
         v0 = str(self._version.major)
         v1 = str(self._version.minor) if self._version.minor is not None else "0"
-        return ".".join([v0, v1, 'Z'])
+        if fill:
+            return ".".join([v0, v1, 'Z'])
+        return ".".join([v0, v1])
 
     def patch(self):
         if not isinstance(self._version.major.value, int):
@@ -133,6 +135,14 @@ class RequirementInfo:
         self.package_id = None
         self.recipe_revision = None
 
+    def full_recipe_mode(self):
+        self.name = self._ref.name
+        self.version = self._ref.version
+        self.user = self._ref.user
+        self.channel = self._ref.channel
+        self.package_id = None
+        self.recipe_revision = None
+
     def full_package_mode(self):
         self.name = self._ref.name
         self.version = self._ref.version
@@ -157,7 +167,6 @@ class RequirementInfo:
         self.package_id = self._package_id
         self.recipe_revision = self._ref.revision
 
-    full_recipe_mode = full_version_mode
     recipe_revision_mode = full_mode  # to not break everything and help in upgrade
 
 
diff --git a/conan/internal/util/files.py b/conan/internal/util/files.py
index 98746479a..fd03715ee 100644
--- a/conan/internal/util/files.py
+++ b/conan/internal/util/files.py
@@ -256,6 +256,40 @@ def mkdir(path):
     os.makedirs(path)
 
 
+def gzopen_without_timestamps(name, mode="r", fileobj=None, compresslevel=None, **kwargs):
+    """ !! Method overrided by laso to pass mtime=0 (!=None) to avoid time.time() was
+        setted in Gzip file causing md5 to change. Not possible using the
+        previous tarfile open because arguments are not passed to GzipFile constructor
+    """
+
+    if mode not in ("r", "w"):
+        raise ValueError("mode must be 'r' or 'w'")
+
+    try:
+        compresslevel = compresslevel if compresslevel is not None else 9  # default Gzip = 9
+        fileobj = gzip.GzipFile(name, mode, compresslevel, fileobj, mtime=0)
+    except OSError:
+        if fileobj is not None and mode == 'r':
+            raise tarfile.ReadError("not a gzip file")
+        raise
+
+    try:
+        # Format is forced because in Python3.8, it changed and it generates different tarfiles
+        # with different checksums, which break hashes of tgzs
+        # PAX_FORMAT is the default for Py38, lets make it explicit for older Python versions
+        t = tarfile.TarFile.taropen(name, mode, fileobj, format=tarfile.PAX_FORMAT, **kwargs)
+    except IOError:
+        fileobj.close()
+        if mode == 'r':
+            raise tarfile.ReadError("not a gzip file")
+        raise
+    except Exception:
+        fileobj.close()
+        raise
+    t._extfileobj = False
+    return t
+
+
 def tar_extract(fileobj, destination_dir):
     the_tar = tarfile.open(fileobj=fileobj)
     # NOTE: The errorlevel=2 has been removed because it was failing in Win10, it didn't allow to
@@ -294,6 +328,33 @@ def gather_files(folder):
     return file_dict, symlinked_folders
 
 
+# FIXME: This is very repeated with the tools.unzip, but wsa needed for config-install unzip
+def unzip(filename, destination="."):
+    from conan.tools.files.files import untargz  # FIXME, importing from conan.tools
+    if (filename.endswith(".tar.gz") or filename.endswith(".tgz") or
+            filename.endswith(".tbz2") or filename.endswith(".tar.bz2") or
+            filename.endswith(".tar")):
+        return untargz(filename, destination)
+    if filename.endswith(".gz"):
+        with gzip.open(filename, 'rb') as f:
+            file_content = f.read()
+        target_name = filename[:-3] if destination == "." else destination
+        save(target_name, file_content)
+        return
+    if filename.endswith(".tar.xz") or filename.endswith(".txz"):
+        return untargz(filename, destination)
+
+    import zipfile
+    full_path = os.path.normpath(os.path.join(os.getcwd(), destination))
+
+    with zipfile.ZipFile(filename, "r") as z:
+        zip_info = z.infolist()
+        extracted_size = 0
+        for file_ in zip_info:
+            extracted_size += file_.file_size
+            z.extract(file_, full_path)
+
+
 def human_size(size_bytes):
     """
     format a size in bytes into a 'human' file size, e.g. B, KB, MB, GB, TB, PB
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 084ff1613..0d35f80f8 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -5,7 +5,7 @@ import textwrap
 
 from conan.api.output import ConanOutput, Color
 from conan.tools.cmake.layout import get_build_folder_custom_vars
-from conan.tools.cmake.toolchain.blocks import GenericSystemBlock
+from conan.tools.cmake.toolchain.blocks import GenericSystemBlock, CompilersBlock
 from conan.tools.cmake.utils import is_multi_configuration
 from conan.tools.build import build_jobs
 from conan.tools.microsoft import is_msvc
@@ -158,6 +158,18 @@ class _CMakePresets:
                     "strategy": "external"
                 }
 
+        # Second attempt at https://github.com/conan-io/conan/issues/13136
+        # for cmake-tools to activate environment. Similar to CompilersBlock
+        # Only for cl/clang-cl, other compilers such clang can be breaking (Android)
+        compilers_by_conf = conanfile.conf.get("tools.build:compiler_executables", default={})
+        compiler = conanfile.settings.get_safe("compiler")
+        default_cl = "cl" if compiler == "msvc" and "Ninja" in str(generator) else None
+        for lang in ("c", "cpp"):
+            comp = compilers_by_conf.get(lang, default_cl)
+            if comp and os.path.basename(comp) in ("cl", "cl.exe", "clang-cl", "clang-cl.exe"):
+                lang = {"c": "C", "cpp": "CXX"}[lang]
+                ret["cacheVariables"][f"CMAKE_{lang}_COMPILER"] = comp.replace("\\", "/")
+
         ret["toolchainFile"] = toolchain_file
         if conanfile.build_folder:
             # If we are installing a ref: "conan install <ref>", we don't have build_folder, because


Test patch:
diff --git a/conan/test/utils/test_files.py b/conan/test/utils/test_files.py
index 7b3f01111..c65d6e169 100644
--- a/conan/test/utils/test_files.py
+++ b/conan/test/utils/test_files.py
@@ -8,11 +8,11 @@ import tempfile
 import time
 from io import BytesIO
 
-from conan.internal.api.uploader import gzopen_without_timestamps
 from conan.tools.files.files import untargz
 from conan.internal.subsystems import get_cased_path
 from conan.errors import ConanException
 from conan.internal.paths import PACKAGE_TGZ_NAME
+from conan.internal.util.files import gzopen_without_timestamps
 
 
 def wait_until_removed(folder):
@@ -85,7 +85,7 @@ def tgz_with_contents(files, output_path=None):
     file_path = output_path or os.path.join(folder, "myfile.tar.gz")
 
     with open(file_path, "wb") as tgz_handle:
-        tgz = gzopen_without_timestamps("myfile.tar.gz", fileobj=tgz_handle)
+        tgz = gzopen_without_timestamps("myfile.tar.gz", mode="w", fileobj=tgz_handle)
 
         for name, content in files.items():
             info = tarfile.TarInfo(name=name)
diff --git a/test/integration/command/upload/upload_test.py b/test/integration/command/upload/upload_test.py
index 633682087..7b0bbaa82 100644
--- a/test/integration/command/upload/upload_test.py
+++ b/test/integration/command/upload/upload_test.py
@@ -13,11 +13,10 @@ from requests import Response
 from conan.errors import ConanException
 from conan.api.model import PkgReference
 from conan.api.model import RecipeReference
-from conan.internal.api.uploader import gzopen_without_timestamps
 from conan.internal.paths import EXPORT_SOURCES_TGZ_NAME, PACKAGE_TGZ_NAME
 from conan.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient, TestServer, \
     GenConanfile, TestRequester, TestingResponse
-from conan.internal.util.files import is_dirty, save, set_dirty, sha1sum
+from conan.internal.util.files import gzopen_without_timestamps, is_dirty, save, set_dirty, sha1sum
 
 conanfile = """from conan import ConanFile
 from conan.tools.files import copy
@@ -149,10 +148,10 @@ class UploadTest(unittest.TestCase):
         pref = client.get_latest_package_reference(RecipeReference.loads("hello0/1.2.1@user/testing"),
                                                    NO_SETTINGS_PACKAGE_ID)
 
-        def gzopen_patched(name, fileobj, compresslevel=None):  # noqa
+        def gzopen_patched(name, mode="r", fileobj=None, **kwargs):
             if name == PACKAGE_TGZ_NAME:
                 raise ConanException("Error gzopen %s" % name)
-            return gzopen_without_timestamps(name, fileobj)
+            return gzopen_without_timestamps(name, mode, fileobj, **kwargs)
         with patch('conan.internal.api.uploader.gzopen_without_timestamps', new=gzopen_patched):
             client.run("upload * --confirm -r default", assert_error=True)
             self.assertIn("Error gzopen conan_package.tgz", client.out)
diff --git a/test/integration/package_id/package_id_requires_modes_test.py b/test/integration/package_id/package_id_requires_modes_test.py
index cad10498c..a3a067fad 100644
--- a/test/integration/package_id/package_id_requires_modes_test.py
+++ b/test/integration/package_id/package_id_requires_modes_test.py
@@ -1,4 +1,5 @@
 import textwrap
+import unittest
 
 import pytest
 
@@ -33,29 +34,7 @@ class TestPackageIDRequirementsModes:
             assert f"dep/{pattern}" in c.out
 
 
-@pytest.mark.parametrize("mode, pkg_id",
-                             [("unrelated_mode", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
-                              ("semver_mode", "13b9e753af3958dd1b2d4b3f935b04b8fb6b6760"),
-                              ("patch_mode", "38d7a3ec6a09165ab3e5306f81c539a2e0a784bd"),
-                              ("minor_mode", "a5e7ad26ccf4a5049090976846da1c6ed165cced"),
-                              ("major_mode", "6ac597ffb99c3747ed78699f206dc1041537a8df"),
-                              # This is equal to semver_mode for 0.X.Y.Z..
-                              ("full_version_mode", "13b9e753af3958dd1b2d4b3f935b04b8fb6b6760"),
-                              ("revision_mode", "ae5b9eeb74880aeb1cfa3db7f84c007a05ce3a76"),
-                              ("full_mode", "d1b2a9538cd69363b4bae7e66c9f900b8f4c58bb")])
-def test_modes(mode, pkg_id):
-    c = TestClient(light=True)
-    package_id_text = f'self.info.requires.{mode}()'
-    c.save({"dep/conanfile.py": GenConanfile("dep", "0.1.1.1").with_settings("os"),
-            "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_requires("dep/[*]")
-                                                          .with_package_id(package_id_text)})
-    c.run("create dep -s os=Linux")
-    c.run("create pkg -s os=Linux")
-    pkgid = c.created_package_id("pkg/0.1")
-    assert pkgid == pkg_id
-
-
-class TestPackageIDError:
+class PackageIDErrorTest(unittest.TestCase):
 
     def test_transitive_multi_mode_package_id(self):
         # https://github.com/conan-io/conan/issues/6942
@@ -73,7 +52,7 @@ class TestPackageIDError:
         client.save({"conanfile.py": GenConanfile().with_require("dep2/1.0@user/testing")
                                                    .with_require("dep3/1.0@user/testing")})
         client.run('create . --name=consumer --version=1.0 --user=user --channel=testing --build=*')
-        assert "consumer/1.0@user/testing: Created" in client.out
+        self.assertIn("consumer/1.0@user/testing: Created", client.out)
 
     def test_transitive_multi_mode2_package_id(self):
         # https://github.com/conan-io/conan/issues/6942
@@ -98,9 +77,9 @@ class TestPackageIDError:
                 """)
         client.save({"conanfile.py": consumer})
         client.run('create . --name=consumer --version=1.0 --user=user --channel=testing --build=*')
-        assert "dep2/1.0@user/testing: PkgNames: ['dep1']" in client.out
-        assert "consumer/1.0@user/testing: PKGNAMES: ['dep1', 'dep2']" in client.out
-        assert "consumer/1.0@user/testing: Created" in client.out
+        self.assertIn("dep2/1.0@user/testing: PkgNames: ['dep1']", client.out)
+        self.assertIn("consumer/1.0@user/testing: PKGNAMES: ['dep1', 'dep2']", client.out)
+        self.assertIn("consumer/1.0@user/testing: Created", client.out)
 
     def test_transitive_multi_mode_build_requires(self):
         # https://github.com/conan-io/conan/issues/6942
@@ -127,9 +106,9 @@ class TestPackageIDError:
                 """)
         client.save({"conanfile.py": consumer})
         client.run('create . --name=consumer --version=1.0 --user=user --channel=testing --build=*')
-        assert "dep2/1.0@user/testing: PkgNames: ['dep1']" in client.out
-        assert "consumer/1.0@user/testing: PKGNAMES: ['dep1', 'dep2']" in client.out
-        assert "consumer/1.0@user/testing: Created" in client.out
+        self.assertIn("dep2/1.0@user/testing: PkgNames: ['dep1']", client.out)
+        self.assertIn("consumer/1.0@user/testing: PKGNAMES: ['dep1', 'dep2']", client.out)
+        self.assertIn("consumer/1.0@user/testing: Created", client.out)
 
 
 class TestRequirementPackageId:
diff --git a/test/integration/package_id/python_requires_package_id_test.py b/test/integration/package_id/python_requires_package_id_test.py
index b7847b09a..7c547431b 100644
--- a/test/integration/package_id/python_requires_package_id_test.py
+++ b/test/integration/package_id/python_requires_package_id_test.py
@@ -1,8 +1,6 @@
 import textwrap
 import unittest
 
-import pytest
-
 from conan.test.utils.tools import TestClient, GenConanfile
 from conan.internal.util.files import save
 
@@ -178,23 +176,3 @@ class TestPythonRequiresHeaderOnly:
         c.run("create tool --version=1.2")
         c.run("install --requires=pkg/0.1")
         c.assert_listed_binary({"pkg/0.1": (pkgid, "Cache")})
-
-
-@pytest.mark.parametrize("mode, pkg_id",
-                         [("unrelated_mode", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
-                          ("semver_mode", "1f0070b00ccebfec93dc90854a163c7af229f587"),
-                          ("patch_mode", "b9ca872dfd5b48f5f1f69d66f0950fc35469d0cd"),
-                          ("minor_mode", "c53bd9e48dd09ceeaa1bb425830490d8b243e39c"),
-                          ("major_mode", "331c17383dcdf37f79bc2b86fa55ac56afdc6fec"),
-                          ("full_version_mode", "1f0070b00ccebfec93dc90854a163c7af229f587"),
-                          ("revision_mode", "0071dd0296afa0db533e21f924273485c87f0d32"),
-                          ("full_mode", "0071dd0296afa0db533e21f924273485c87f0d32")])
-def test_modes(mode, pkg_id):
-    c = TestClient(light=True)
-    c.save_home({"global.conf": f"core.package_id:default_python_mode={mode}"})
-    c.save({"dep/conanfile.py": GenConanfile("dep", "0.1.1.1"),
-            "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_python_requires("dep/[*]")})
-    c.run("create dep")
-    c.run("create pkg")
-    pkgid = c.created_package_id("pkg/0.1")
-    assert pkgid == pkg_id
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 186f0fbf8..70b27bf9a 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1747,3 +1747,27 @@ def test_declared_stdlib_and_passed():
     client.run('install . -s compiler=sun-cc -s compiler.libcxx=libstdcxx')
     tc = client.load("conan_toolchain.cmake")
     assert 'string(APPEND CONAN_CXX_FLAGS " -library=stdcxx4")' in tc
+
+
+def test_cmake_presets_compiler():
+    profile = textwrap.dedent(r"""
+        [settings]
+        os=Windows
+        arch=x86_64
+        compiler=msvc
+        compiler.version=193
+        compiler.runtime=dynamic
+        [conf]
+        tools.build:compiler_executables={"c": "cl", "cpp": "cl.exe"}
+        """)
+    client = TestClient()
+    conanfile = GenConanfile().with_settings("os", "arch", "compiler")\
+        .with_generator("CMakeToolchain")
+    client.save({"conanfile.py": conanfile,
+                 "profile": profile})
+    client.run("install . -pr:b profile -pr:h profile")
+    presets = json.loads(client.load("CMakePresets.json"))
+    cache_variables = presets["configurePresets"][0]["cacheVariables"]
+    # https://github.com/microsoft/vscode-cmake-tools/blob/a1ceda25ea93fc0060324de15970a8baa69addf6/src/presets/preset.ts#L1095C23-L1095C35
+    assert cache_variables["CMAKE_C_COMPILER"] == "cl"
+    assert cache_variables["CMAKE_CXX_COMPILER"] == "cl.exe"
diff --git a/test/unittests/util/files/strip_root_extract_test.py b/test/unittests/util/files/strip_root_extract_test.py
index 1e219be78..6906f7a11 100644
--- a/test/unittests/util/files/strip_root_extract_test.py
+++ b/test/unittests/util/files/strip_root_extract_test.py
@@ -3,7 +3,6 @@ import tarfile
 import unittest
 import zipfile
 
-from conan.internal.api.uploader import gzopen_without_timestamps
 from conan.tools.files.files import untargz, unzip
 from conan.errors import ConanException
 from conan.internal.model.manifest import gather_files
@@ -12,7 +11,7 @@ from conan.test.utils.mocks import RedirectedTestOutput
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import redirect_output
 from conan.internal.util.files import chdir, save
-from conan.internal.util.files import rmdir
+from conan.internal.util.files import gzopen_without_timestamps, rmdir
 
 
 class ZipExtractPlainTest(unittest.TestCase):
@@ -111,7 +110,7 @@ class TarExtractPlainTest(unittest.TestCase):
         # Create a tar.gz file with the files in the folder and an additional TarInfo entry
         # for the folder_entry (the gather files doesn't return empty dirs)
         with open(tgz_path, "wb") as tgz_handle:
-            tgz = gzopen_without_timestamps("name", fileobj=tgz_handle)
+            tgz = gzopen_without_timestamps("name", mode="w", fileobj=tgz_handle)
             if folder_entry:
                 # Create an empty folder in the tgz file
                 t = tarfile.TarInfo(folder_entry)
@@ -132,7 +131,7 @@ class TarExtractPlainTest(unittest.TestCase):
         tgz_path = os.path.join(tmp_folder, "foo.tgz")
 
         with open(tgz_path, "wb") as tgz_handle:
-            tgz = gzopen_without_timestamps("name", fileobj=tgz_handle)
+            tgz = gzopen_without_timestamps("name", mode="w", fileobj=tgz_handle)
 
             # Regular file
             info = tarfile.TarInfo(name="common/foo.txt")
@@ -287,7 +286,7 @@ def _compress_root_folder(folder, tgz_path, root_folder_name="root"):
     root/parent/bin/file2
     """
     with open(tgz_path, "wb") as tgz_handle:
-        tgz = gzopen_without_timestamps("name", fileobj=tgz_handle)
+        tgz = gzopen_without_timestamps("name", mode="w", fileobj=tgz_handle)
         files, _ = gather_files(folder)
         tgz.add(root_folder_name, arcname=os.path.basename(root_folder_name))
         tgz.close()
diff --git a/test/unittests/util/files/tar_extract_test.py b/test/unittests/util/files/tar_extract_test.py
index 6f031a56a..8327902bd 100644
--- a/test/unittests/util/files/tar_extract_test.py
+++ b/test/unittests/util/files/tar_extract_test.py
@@ -1,3 +1,5 @@
+# coding=utf-8
+
 import os
 import platform
 import tarfile
@@ -5,9 +7,8 @@ import unittest
 
 import pytest
 
-from conan.internal.api.uploader import gzopen_without_timestamps
 from conan.test.utils.test_files import temp_folder
-from conan.internal.util.files import tar_extract, save, gather_files, chdir
+from conan.internal.util.files import tar_extract, gzopen_without_timestamps, save, gather_files, chdir
 
 
 class TarExtractTest(unittest.TestCase):
@@ -25,7 +26,7 @@ class TarExtractTest(unittest.TestCase):
             # Create a tar.gz file with the above files
             self.tgz_file = os.path.join(self.tmp_folder, "file.tar.gz")
             with open(self.tgz_file, "wb") as tgz_handle:
-                tgz = gzopen_without_timestamps("name", fileobj=tgz_handle)
+                tgz = gzopen_without_timestamps("name", mode="w", fileobj=tgz_handle)
 
                 files, _ = gather_files(ori_files_dir)
                 for filename, abs_path in files.items():