Repository: conan-io/conan

Issue:
[feature] CMakePresets support for msvc toolset version
### What is your suggestion?

## Suggestion

Would it be possible for the Conan generated CMakePresets.json to include `msvc.toolset` version details (i.e. `v143`, `v142`, etc.) when populating the `toolset.value` field?

This would allow IDEs to know which `vcvarsall.bat` environment to use when building the project.

## Expected Behavior

For example, when cross-compiling with VS2022 x64 to x86, I'd expect to see the conan generated CMakePresets.json look something like:
```diff
"generator": "Ninja",
"cacheVariables": {
    "CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
    "CMAKE_BUILD_TYPE": "RelWithDebInfo"
},
"architecture": {
    "value": "x86",
    "strategy": "external"
},
+"toolset": {
+    "value": "v143,host=x64",
+    "strategy": "external"
+},
"toolchainFile": "C:\\projects\\build\\foo\\RelWithDebInfo\\generators\\conan_toolchain.cmake",
"binaryDir": "C:\\projects\\build\\foo\\RelWithDebInfo
```            

_I can use `tools.cmake.cmaketoolchain:toolset_arch=x64` to add the "host=x64" portion, but I don't have anyway to get `v143` added._

VSCode recognizes this `toolset` / `architecture` combination and correctly chooses VS2022 and calls `vcvarsall.bat amd64_x86` prior to calling CMake.

## Current Behavior

Conan *doesn't* include `toolset` details in the generated CMakePresets.json file, so in order to get IDEs like VSCode to find the right environment, we need to adjust CMakePresets.json (as described above) or worse adjust the CMakeUserPresets.json file as follows:

```diff
{
    "version": 4,
    "vendor": {
        "conan": {}
    },
    "include": [
        "C:\\path\\to\\conan\\CMakePresets.json"
-    ]
+    ],
+    "configurePresets": [
+      {
+        "name": "RelWithDebInfo",
+        "displayName": "RelWithDebInfo",
+        "inherits": "relwithdebinfo",
+        "toolset": {
+          "value": "v143,host=x64",
+          "strategy": "external"
+        }
+    ],
+    "buildPresets": [
+        {
+            "name": "RelWithDebInfo",
+            "displayName": "RelWithDebInfo",
+            "configurePreset": "RelWithDebInfo",
+            "configuration": "RelWithDebInfo"
+        }
+    ]
}
```

Conan already knows *which* vcvarsall to call, and and ensures the correct one gets called in the generated `conanvcvars.bat`.

Conan also appears to know about `msvc.toolset` version information:
https://github.com/conan-io/conan/blob/e33b55a486fd208223524dda49cce02dbe70c214/conans/client/conf/__init__.py#L117
_At least for the legacy toolsets_

And seems to know about the mapping:
https://github.com/conan-io/conan/blob/e0a8ee058bc8dc2d9811b8aeb6999f69aeb78d85/conan/tools/microsoft/visual.py#L53-L60

It seems like Conan could should be able to use the `msvc.toolset` information from the build profile and populate the version field correctly.

## Context
Follow up to https://github.com/conan-io/conan/issues/11623

Some IDEs are capable of targeting multiple toolsets (e.g. VS2015, VS2019, VS2022), and `toolset.value` is the way for the IDE to know which `vcvarsall.bat` to load and use internally while compiling.

> It's the same step that Visual Studio takes for you when the IDE invokes CMake. **Visual Studio parses the active Configure Preset for the host and target architecture specified by toolset and architecture. Visual Studio then sources the specified environment from vcvarsall.bat.** When you build from the Windows command line with Ninja, you'll need to take this step yourself.
_Excerpt from MSDN [Sourcing the environment when building with command-line generators on Windows](https://learn.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170#sourcing-the-environment-when-building-with-command-line-generators-on-windows)_

IDE | Support 
----|----------
VSCode | [Already Supported](https://github.com/microsoft/vscode-cmake-tools/search?q=varsForVSInstallation) <br> https://github.com/microsoft/vscode-cmake-tools/pull/2524
QtCreator | [In Progress](https://codereview.qt-project.org/c/qt-creator/qt-creator/+/457588/8/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp)<br> [QTCREATOR-BUG 28693](https://bugreports.qt.io/browse/QTCREATORBUG-28693)
CLion | [Feature Request](https://youtrack.jetbrains.com/issue/CPP-31353/Support-external-strategy-of-toolset-and-architecture-fields-in-CMakePresets.json-for-MSVC-compilers.)

Thanks for your support!



### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide

Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/conan-io/conan?pullRequest=16762) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/conan-io/conan?pullRequest=16762) before we can accept your contribution.<br/><hr/>**Peter Würth** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/conan-io/conan?pullRequest=16762) it.</sub>

Patch:
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index b7e616150..43739a955 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,13 @@ class _CMakePresets:
                     "strategy": "external"
                 }
 
+        # Set the compiler like in the toolchain. Some IDEs like VS or VSCode require the compiler
+        # being set to cl.exe in order to activate the environment using vcvarsall.bat according to
+        # the toolset and architecture settings.
+        compilers = CompilersBlock.get_compilers(conanfile)
+        for lang, compiler in compilers.items():
+            ret["cacheVariables"][f"CMAKE_{lang}_COMPILER"] = compiler.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
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 425e62b2a..1343865c9 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -852,9 +852,13 @@ class CompilersBlock(Block):
     """)
 
     def context(self):
+        return {"compilers": self.get_compilers(self._conanfile)}
+
+    @staticmethod
+    def get_compilers(conanfile):
         # Reading configuration from "tools.build:compiler_executables" -> {"C": "/usr/bin/gcc"}
-        compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
-                                                     check_type=dict)
+        compilers_by_conf = conanfile.conf.get("tools.build:compiler_executables", default={},
+                                               check_type=dict)
         # Map the possible languages
         compilers = {}
         # Allowed <LANG> variables (and <LANG>_LAUNCHER)
@@ -865,7 +869,7 @@ class CompilersBlock(Block):
             # To set CMAKE_<LANG>_COMPILER
             if comp in compilers_by_conf:
                 compilers[lang] = compilers_by_conf[comp]
-        return {"compilers": compilers}
+        return compilers
 
 
 class GenericSystemBlock(Block):
diff --git a/conans/model/requires.py b/conans/model/requires.py
index 23987d0dc..b1974a7dc 100644
--- a/conans/model/requires.py
+++ b/conans/model/requires.py
@@ -270,15 +270,18 @@ class Requirement:
 
         if require.build:  # public!
             # TODO: To discuss if this way of conflicting build_requires is actually useful or not
+            # Build-requires will propagate its main trait for running exes/shared to downstream
+            # consumers so run=require.run, irrespective of the 'self.run' trait
             downstream_require = Requirement(require.ref, headers=False, libs=False, build=True,
-                                             run=False, visible=self.visible, direct=False)
+                                             run=require.run, visible=self.visible, direct=False)
             return downstream_require
 
         if self.build:  # Build-requires
             # If the above is shared or the requirement is explicit run=True
+            # visible=self.visible will further propagate it downstream
             if dep_pkg_type is PackageType.SHARED or require.run:
                 downstream_require = Requirement(require.ref, headers=False, libs=False, build=True,
-                                                 run=True, visible=False, direct=False)
+                                                 run=True, visible=self.visible, direct=False)
                 return downstream_require
             return
 


Test patch:
diff --git a/test/integration/graph/core/test_build_requires.py b/test/integration/graph/core/test_build_requires.py
index acf8c40e2..7e396044b 100644
--- a/test/integration/graph/core/test_build_requires.py
+++ b/test/integration/graph/core/test_build_requires.py
@@ -639,7 +639,89 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # node, include, link, build, run
         _check_transitive(lib, [(cmake, False, False, True, True)])
         _check_transitive(app, [(lib, True, True, False, False),
-                                (cmake, False, False, True, False)])
+                                (cmake, False, False, True, True)])
+
+    def test_deep_dependency_tree(self):
+        # app -> liba -> libb-(br public) -> sfun -> libsfun -> libx -> liby -> libz
+        #                    -(normal req) -> libsfun -> libx -> liby -> libz
+        self.recipe_conanfile("libz/0.1", GenConanfile())
+        self.recipe_conanfile("liby/0.1", GenConanfile().with_requirement("libz/0.1", run=True))
+        self.recipe_conanfile("libx/0.1", GenConanfile().with_requirement("liby/0.1", run=True))
+        self.recipe_conanfile("libsfun/0.1", GenConanfile().with_requirement("libx/0.1", run=True))
+        self.recipe_conanfile("sfun/0.1", GenConanfile().with_requirement("libsfun/0.1", run=True))
+        self.recipe_conanfile("libb/0.1", GenConanfile()
+                              .with_tool_requirement("sfun/0.1", visible=True)
+                              .with_requirement("libsfun/0.1", run=True))
+        self.recipe_conanfile("liba/0.1", GenConanfile().with_requirement("libb/0.1", run=True))
+        deps_graph = self.build_graph(GenConanfile("app", "0.1").with_requirement("liba/0.1", run=True))
+
+        # Build requires always apply to the consumer
+        self.assertEqual(8 + 4, len(deps_graph.nodes))
+        app = deps_graph.root
+        liba = app.dependencies[0].dst
+        libb = liba.dependencies[0].dst
+        libsfun = libb.dependencies[0].dst
+        libx = libsfun.dependencies[0].dst
+        liby = libx.dependencies[0].dst
+        libz = liby.dependencies[0].dst
+        sfun = libb.dependencies[1].dst
+        libsfun_build = sfun.dependencies[0].dst
+        libx_build = libsfun_build.dependencies[0].dst
+        liby_build = libx_build.dependencies[0].dst
+        libz_build = liby_build.dependencies[0].dst
+
+        # TODO non-build-requires
+
+        self._check_node(app, "app/0.1@", deps=[liba], dependents=[])
+        self._check_node(liba, "liba/0.1#123", deps=[libb], dependents=[app])
+        self._check_node(libb, "libb/0.1#123", deps=[sfun, libsfun], dependents=[liba])
+        self._check_node(sfun, "sfun/0.1#123", deps=[libsfun_build], dependents=[libb])
+        self._check_node(libsfun_build, "libsfun/0.1#123", deps=[libx_build], dependents=[sfun])
+        self._check_node(libx_build, "libx/0.1#123", deps=[liby_build], dependents=[libsfun_build])
+        self._check_node(liby_build, "liby/0.1#123", deps=[libz_build], dependents=[libx_build])
+        self._check_node(libz_build, "libz/0.1#123", deps=[], dependents=[liby_build])
+
+        # node, include, link, build, run
+        _check_transitive(liby_build, [(libz_build, True, True, False, True)])
+        _check_transitive(libx_build, [(liby_build, True, True, False, True),
+                                       (libz_build, True, True, False, True)])
+        _check_transitive(libsfun_build, [(libx_build, True, True, False, True),
+                                          (liby_build, True, True, False, True),
+                                          (libz_build, True, True, False, True)])
+        _check_transitive(sfun, [(libsfun_build, True, True, False, True),
+                                 (libx_build, True, True, False, True),
+                                 (liby_build, True, True, False, True),
+                                 (libz_build, True, True, False, True)])
+        _check_transitive(libb, [(libsfun, True, True, False, True),
+                                 (libx, True, True, False, True),
+                                 (liby, True, True, False, True),
+                                 (libz, True, True, False, True),
+                                 (sfun, False, False, True, True),
+                                 (libsfun_build, False, False, True, True),
+                                 (libx_build, False, False, True, True),
+                                 (liby_build, False, False, True, True),
+                                 (libz_build, False, False, True, True)])
+        _check_transitive(liba, [(libb, True, True, False, True),
+                                 (libsfun, True, True, False, True),
+                                 (libx, True, True, False, True),
+                                 (liby, True, True, False, True),
+                                 (libz, True, True, False, True),
+                                 (sfun, False, False, True, True),
+                                 (libsfun_build, False, False, True, True),
+                                 (libx_build, False, False, True, True),
+                                 (liby_build, False, False, True, True),
+                                 (libz_build, False, False, True, True)])
+        _check_transitive(app, [(liba, True, True, False, True),
+                                (libb, True, True, False, True),
+                                (libsfun, True, True, False, True),
+                                (libx, True, True, False, True),
+                                (liby, True, True, False, True),
+                                (libz, True, True, False, True),
+                                (sfun, False, False, True, True),
+                                (libsfun_build, False, False, True, True),
+                                (libx_build, False, False, True, True),
+                                (liby_build, False, False, True, True),
+                                (libz_build, False, False, True, True)])
 
     def test_conflict_diamond(self):
         # app -> libb -(br public)-> cmake/0.1
@@ -668,6 +750,40 @@ class PublicBuildRequiresTest(GraphManagerTest):
         self._check_node(libb, "libb/0.1#123", deps=[cmake1], dependents=[app])
         self._check_node(cmake1, "cmake/0.1#123", deps=[], dependents=[libb])
 
+    def test_conflict_diamond_two_levels(self):
+        # app -> libd -> libb -(br public)-> cmake/0.1
+        #   \--> libe -> libc -(br public)-> cmake/0.2
+        self.recipe_conanfile("cmake/0.1", GenConanfile())
+        self.recipe_conanfile("cmake/0.2", GenConanfile())
+        self.recipe_conanfile("libb/0.1",
+                              GenConanfile().with_tool_requirement("cmake/0.1", visible=True))
+        self.recipe_conanfile("libc/0.1",
+                              GenConanfile().with_tool_requirement("cmake/0.2", visible=True))
+        self.recipe_conanfile("libd/0.1", GenConanfile().with_requires("libb/0.1"))
+        self.recipe_conanfile("libe/0.1", GenConanfile().with_requires("libc/0.1"))
+
+        deps_graph = self.build_graph(GenConanfile("app", "0.1").with_requires("libd/0.1",
+                                                                               "libe/0.1"),
+                                      install=False)
+
+        assert type(deps_graph.error) == GraphConflictError
+
+        # Build requires always apply to the consumer
+        self.assertEqual(6, len(deps_graph.nodes))
+        app = deps_graph.root
+        libd = app.dependencies[0].dst
+        libe = app.dependencies[1].dst
+        libb = libd.dependencies[0].dst
+        libc = libe.dependencies[0].dst
+        cmake1 = libb.dependencies[0].dst
+
+        self._check_node(app, "app/0.1@", deps=[libd, libe], dependents=[])
+        self._check_node(libd, "libd/0.1#123", deps=[libb], dependents=[app])
+        self._check_node(libe, "libe/0.1#123", deps=[libc], dependents=[app])
+        self._check_node(libb, "libb/0.1#123", deps=[cmake1], dependents=[libd])
+        self._check_node(libc, "libc/0.1#123", deps=[], dependents=[libe])
+        self._check_node(cmake1, "cmake/0.1#123", deps=[], dependents=[libb])
+
     def test_tool_requires(self):
         # app -> libb -(br public)-> protobuf/0.1
         #           \--------------> protobuf/0.2
@@ -695,7 +811,7 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # node, headers, lib, build, run
         _check_transitive(app, [(libb, True, True, False, False),
                                 (protobuf_host, True, True, False, False),
-                                (protobuf_build, False, False, True, False)])
+                                (protobuf_build, False, False, True, True)])
 
     def test_tool_requires_override(self):
         # app -> libb -(br public)-> protobuf/0.1
@@ -726,7 +842,7 @@ class PublicBuildRequiresTest(GraphManagerTest):
         # node, headers, lib, build, run
         _check_transitive(app, [(libb, True, True, False, False),
                                 (protobuf_host, True, True, False, False),
-                                (protobuf_build, False, False, True, False)])
+                                (protobuf_build, False, False, True, True)])
         _check_transitive(libb, [(protobuf_host, True, True, False, False),
                                  (protobuf_build, False, False, True, True)])
 
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 38ac9049b..c174193ec 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1046,6 +1046,30 @@ def test_set_cmake_lang_compilers_and_launchers():
     assert 'set(CMAKE_RC_COMPILER "C:/local/rc.exe")' in toolchain
 
 
+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", "rc": "C:\\local\\rc.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"]
+    assert cache_variables["CMAKE_C_COMPILER"] == "cl"
+    assert cache_variables["CMAKE_CXX_COMPILER"] == "cl.exe"
+    assert cache_variables["CMAKE_RC_COMPILER"] == "C:/local/rc.exe"
+
+
 def test_cmake_layout_toolchain_folder():
     """ in single-config generators, the toolchain is a different file per configuration
     https://github.com/conan-io/conan/issues/12827