Repository: conan-io/conan

Issue:
[feature] How CMakeToolchain could handle multiple user toolchains
Lets say that we have 2 different build_requires, one ``sdk`` and one ``compiler``. Building a package could use both, and each one would want to inject their own ``user_toolchain.cmake``. 

Or they want to propagate information to the consumer in other ways. Challenges:

- Passing information like CC/CXX via environment requires using VirtualBuildEnv, and activating env scripts, which is not the most convenient for IDE developers. Env-vars might not be enough for some cases, and still a user toolchain might be necessary.
- Passing information via ``self.conf_info`` to consumer can work, but at the moment can only provide 1 value for ``user_toolchain``. More than one value would require list aggregation or equivalent functionality

cc/ @Blejdfist


Discussion:
The ability to provide multiple files to be included in the `UserToolchain` block when using the `CMakeToolchain` generator would provide a lot of flexibility.
I am thinking that we might need a bit more of power in the ``Conf`` class to handle some scenarios. So far the ``Conf`` class is a pure ``key=value`` store, very simple and convenient. But there are current ``conan.conf`` cases that might not be covered:

- The list of hooks to apply
- If we want to evolve it and be able to pass general compilation flags, like CXX_FLAGS to the whole build.
- To be able to aggregate information coming from multiple ``build_requires``, in the consumer.

This would mean in practice that ``Conf`` would be an instance of the ``Environment`` class (and kind o makes sense now that we removed the dependency of ``Environment`` to ``conanfile`` instance, becoming fully abstract). Some questions:
- Should we evolve ``Conf`` to be equal or very similar to ``Environment``?
- Should it be an ``Environment`` instance? What is the overlap now with ``[buildenv]`` profile section?
- Does it make sense to have an ``Environment`` with "un-realizable" env-vars, like ``tools.cmake.cmake_toolchain:xxxx``?

Wdyt @lasote?
Lets try this approach of using something like ``Environment`` to extend the capabilities of current ``Conf``
We will try the following approach:

- Make explicit in a generator like ``ConfAggregator`` the behavior of accummulating conf from build-requires
- This generator can be explicitly instantiated to customize priorities and behavior
- The generator will be enabled in 1.X with ``auto_use`` as VirtualBuildEnv and automatic in 2.0
- CMakeToolchain will listen to that property as a potential list (both for profile, and from deps, agnostic in this point, just move the list to a list of ``include(xxxx)``)

Resolving patch:
diff --git a/conan/tools/cmake/toolchain.py b/conan/tools/cmake/toolchain.py
--- a/conan/tools/cmake/toolchain.py
+++ b/conan/tools/cmake/toolchain.py
@@ -1,3 +1,4 @@
+import json
 import os
 import re
 import textwrap
@@ -457,20 +458,16 @@ def context(self):
 
 class UserToolchain(Block):
     template = textwrap.dedent("""
-        {% if user_toolchain %}
+        {% for user_toolchain in paths %}
         include("{{user_toolchain}}")
-        {% endif %}
+        {% endfor %}
         """)
 
-    user_toolchain = None
-
     def context(self):
         # This is global [conf] injection of extra toolchain files
         user_toolchain = self._conanfile.conf["tools.cmake.cmaketoolchain:user_toolchain"]
-        user_toolchain = user_toolchain or self.user_toolchain
-        if user_toolchain:
-            user_toolchain = user_toolchain.replace("\\", "/")
-        return {"user_toolchain": user_toolchain}
+        toolchains = [user_toolchain.replace("\\", "/")] if user_toolchain else []
+        return {"paths": toolchains if toolchains else []}
 
 
 class CMakeFlagsInitBlock(Block):


Test patch:
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -115,3 +115,65 @@ def test_cmake_toolchain_without_build_type():
     toolchain = client.load("conan_toolchain.cmake")
     assert "CMAKE_MSVC_RUNTIME_LIBRARY" not in toolchain
     assert "CMAKE_BUILD_TYPE" not in toolchain
+
+
+def test_cmake_toolchain_multiple_user_toolchain():
+    """ A consumer consuming two packages that declare:
+            self.conf_info["tools.cmake.cmaketoolchain:user_toolchain"]
+        The consumer wants to use apply both toolchains in the CMakeToolchain.
+        There are two ways to customize the CMakeToolchain (parametrized):
+                1. Altering the context of the block (with_context = True)
+                2. Using the t.blocks["user_toolchain"].user_toolchains = [] (with_context = False)
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        import os
+        from conans import ConanFile
+        class Pkg(ConanFile):
+            exports_sources = "*"
+            def package(self):
+                self.copy("*")
+            def package_info(self):
+                f = os.path.join(self.package_folder, "mytoolchain.cmake")
+                self.conf_info["tools.cmake.cmaketoolchain:user_toolchain"] = f
+        """)
+    client.save({"conanfile.py": conanfile,
+                 "mytoolchain.cmake": 'message(STATUS "mytoolchain1.cmake !!!running!!!")'})
+    client.run("create . toolchain1/0.1@")
+    client.save({"conanfile.py": conanfile,
+                 "mytoolchain.cmake": 'message(STATUS "mytoolchain2.cmake !!!running!!!")'})
+    client.run("create . toolchain2/0.1@")
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.cmake import CMake, CMakeToolchain
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "CMakeLists.txt"
+            tool_requires = "toolchain1/0.1", "toolchain2/0.1"
+
+
+            def generate(self):
+                # Get the toolchains from "tools.cmake.cmaketoolchain:user_toolchain" conf at the
+                # tool_requires
+                user_toolchains = []
+                for dep in self.dependencies.direct_build.values():
+                    ut = dep.conf_info["tools.cmake.cmaketoolchain:user_toolchain"]
+                    if ut:
+                        user_toolchains.append(ut.replace('\\\\', '/'))
+
+                # Modify the context of the user_toolchain block
+                t = CMakeToolchain(self)
+                t.blocks["user_toolchain"].values["paths"] = user_toolchains
+                t.generate()
+
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+        """)
+
+    client.save({"conanfile.py": conanfile,
+                 "CMakeLists.txt": gen_cmakelists()}, clean_first=True)
+    client.run("create . pkg/0.1@")
+    assert "mytoolchain1.cmake !!!running!!!" in client.out
+    assert "mytoolchain2.cmake !!!running!!!" in client.out
diff --git a/conans/test/unittests/tools/cmake/test_cmaketoolchain.py b/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
--- a/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -134,7 +134,7 @@ def context(self):
 
 def test_user_toolchain(conanfile):
     toolchain = CMakeToolchain(conanfile)
-    toolchain.blocks["user_toolchain"].user_toolchain = "myowntoolchain.cmake"
+    toolchain.blocks["user_toolchain"].values["paths"] = ["myowntoolchain.cmake"]
     content = toolchain.content
     assert 'include("myowntoolchain.cmake")' in content