Repository: conan-io/conan

Issue:
[feature] Allow self.name in tools.cmake.cmake_layout:build_folder_vars
In our org we have multiple recipes with a shared code basis. The directory structure is as following:
```sh
.
├── CMakeLists.txt
├── simple_example.cc
├── sub1
│   └── conanfile.py
└── sub2
    └── conanfile.py
```
The conan files look something like this:
```python
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout

class sub2Recipe(ConanFile):
    name = "sub2"
    version = "1.0"
    def layout(self):
        cmake_layout(self, generator="Ninja", src_folder="..")

    def generate(self):
        tc = CMakeToolchain(self)
        tc.generate()

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()
```
When I now run:
```sh
 conan install sub1/conanfile.py
 conan install sub2/conanfile.py
``` 

I get a CMakeUserPresets.json that refers to both CMakePresets.json in their respective `subX/build/generators/CMakePresets.json` folder. The problem now is that for both CMakePresets the names are identical:
```json
    "configurePresets": [
        {
            "name": "release",
            "displayName": "'release' config",
            "description": "'release' configure using 'Unix Makefiles' generator",
            ...
        }
    ],
    "buildPresets": [
        {
            "name": "release",
            "configurePreset": "release"
        }
    ],
```

VSCode will show both displayNames and it is impossible to tell which one is going to build what.

So my idea of how to fix that would be the following, allow to run install like this:
```sh
conan install -ctools.cmake.cmake_layout:build_folder_vars="['self.name']" sub1/conanfile.py
```

Which results in:
```json
    "configurePresets": [
        {
            "name": "sub1-release",
            "displayName": "'sub1-release' config",
            "description": "'sub1-release' configure using 'Unix Makefiles' generator",
            ...
        }
    ],
    "buildPresets": [
        {
            "name": "sub1-release",
            "configurePreset": "sub1-release"
        }
    ],
```

Another nice side-effect is that I can also have a common build folder at the "root":
```
cmake_layout(self, generator="Ninja", src_folder="..", build_folder="../build")
```

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).



Discussion:
Hi @KarstenB,

Thanks a lot for the feedback. I think that being able to add those variables to the `cmake_layout:build_folder_vars` could make sense, but also I think that the project structure you are using could fit with using `self.folders.subproject` you can check the docs about this feature [here](https://docs.conan.io/en/latest/developing_packages/package_layout.html#example-multiple-subprojects). Another option would be to add the subproject to the preset name.
I'll mark this as look-into to discuss with the team and add to the next release milestone.
Thanks!
Thanks, I checked with the subproject structure, and don't like it. It doesn't generate a CMakeUserPreset.json in the folder I run install, and both generated CMakePresets.json also have the same names. So that doesn't help me much.
I also implemented the solution on my branch https://github.com/KarstenB/conan/tree/cmake_layout_allow_self_name and it works as I expected. I could contribute a PR if you're interested.
> Thanks, I checked with the subproject structure, and don't like it. It doesn't generate a CMakeUserPreset.json in the folder I run install, and both generated CMakePresets.json also have the same names. So that doesn't help me much.

Hi @KarstenB 

I am trying to understand a bit better the use case. The idea is that every ``conanfile.py`` represents its own package, which is an independent unit of development and release (as it can have its own version, different to others). So having a Presets with same names wasn't a concern for us, because they are actually different projects. It is not a mono-build, because 2 conanfiles can also install different and incompatible versions of dependencies, which ``xxx-config.cmake`` will conflict. So they cannot be built together in the general case as subprojects. As you pointed out the file is generated in a different subfolder ``subX/build/generators/CMakePresets.json``, but also together with a bunch of other files necessary for the build of ``subX``.

I think this is also aligned with how CMake sees the presets, which are to manage configurations, but not to manage projects. It is not fully clear how it works.

Can you please clarify a bit more about this? Do you release packages with different versions out of this? Does every ``conanfile.py`` has also its own ``CMakeLists.txt`` and they are ``add_subdirectory(subX)``? Why the common code to all ``conanfile.py`` is not another package? What happens if there are dependencies with different versions?

Thanks very much for your feedback!


Hi @memsharded, 
Thanks for taking your time to investigate this, to be clear, this is a developer workflow issue, not a build system issue. The packages are build independently just fine by the CI, what we worry about is making sure the developers are productive as quickly as possible.

To give you some better idea imagine Sub1 is packaging a kernel for an embedded device and Sub2 packaging the user space applications. Due to bad decisions decades ago (literally, it's a huge code basis too) it is impossible to create a clean intermediate package because everything is circularly inter-dependant. Thus we have two packages in one repository, plus we also already have way too many conan packages as dependencies.

One developer may work and ship a new user space application feature, without ever touching the kernel parts, and another developer may have to make adjustments only to the kernel side. Some developers need to touch both in order to realise the feature. 

So both packages are released in different versions. User space is much more frequently updated than the kernel, and sometimes the user space requires a new kernel version, but there can be new kernel updates without requiring new user space releases.

Right now most IDEs don't have native Conan support, so what we are doing is providing a list of CMakePresets that build for the various combinations of HW platform and Kernel/Userspace. In the CMakeLists.txt we then do the `conan install` for the given combination of conanfile and preset. 

My hope was that I could automatically generate the presets by doing the install for all conanfiles with all presets. But that also has the downside that all conan packages are downloaded, which may take a long time as we ship the compilers and sysroots via Conan as well.

So in an ideal world I could run something like this:
```sh
conan install kernel/conanfile.py -pr linux_x86_release --recipes-only
conan install kernel/conanfile.py -pr armv8_release --recipes-only
conan install kernel/conanfile.py -pr linux_x86_debug --recipes-only
conan install kernel/conanfile.py -pr armv8_debug --recipes-only

conan install userspace/conanfile.py -pr linux_x86_release --recipes-only
conan install userspace/conanfile.py -pr armv8_release --recipes-only
conan install userspace/conanfile.py -pr linux_x86_debug --recipes-only
conan install userspace/conanfile.py -pr armv8_debug --recipes-only

```
And would end up with a CMake(User)Presets.json containing the presets:

```
kernel - linux_x86 - release
kernel - linux_x86 - debug
kernel - armv8 - release
kernel - armv8 - debug

userspace - linux_x86 - release
userspace - linux_x86 - debug
userspace - armv8 - release
userspace - armv8 - debug
```

And the subsequent call to build for a preset would then download the required packages. That would be the ultimate goal :) Btw. due to bad planning decades ago we are also doing this on Windows....
Hi @KarstenB 

Sorry for the delay, I am resuming work checking this.

Quick question, what is the intent of `` --recipes-only``? It is not clear from your comment. If you mean, do not install the packages, that is the ``conan info`` command, but that command doesn't generate any file necessary for the build, so it is useless to prepare a build or to attempt to generate the CMake Presets
I am also trying to set-up a project that looks like that, but struggling with it:

- If I make Conan to create the ``CMakeUserPresets`` in the root, and include the different generated ``CMakePresets`` inside each package, that ``CMakeUserPresets`` will not work, because there is no ``CMakeLists.txt`` in the root of the project. And if I go inside any of the two subprojects (like userspace or kernel), then it is already an independent CMake build, it doesn't matter if the names are the same or not.
- If there is a root ``CMakeLists.txt`` adding subdirectories of the subprojects, then we have a very different problem, because then it will not work having each project to work with its own toolchain, its own dependencies, etc.

I think I still need to understand a bit better the setup, the constraints and the goals.
Hi @memsharded,
The `--recipes-only` is an imaginary new option that would download the recipes and use the information (like `conan info` would), but not download the actual `conan_package.tgz` which for a `sysroot` or `build-tools` could be a few 100 MB to several GB. This is to avoid traffic for users running the `conan install` commands simply to populate the CMakeUserPresets.

I don't quite have the same code problem with the CMakeLists as our kernel and user space code is sitting intertwined in a `src` directory. So something like this:

```
├── kernel
│ └── conanfile.py
├── src
│ ├── CMakeLists.txt
│ └── deviceinfo
│     ├── CMakeLists.txt
│     ├── kernel
│     │   └── CMakeLists.txt
│     └── userspace
│         └── CMakeLists.txt
└── userspace
    └── conanfile.py
```
And the file `src/deviceinfo/CMakeLists.txt` essentially contains a switch like this:
```
if (kernel)
  add_subdirectory(kernel)
else()
  add_subdirectory(userspace)
endif()
```

So both conanfiles are using the central CMakeLists at the root, but with different cmake variables to control which files to use.

I am not sure I understand what you mean by : "it will not work having each project to work with its own toolchain, its own dependencies, etc.". I think we got around by that with our if (kernel) functions, or maybe I am missing something.
> The --recipes-only is an imaginary new option that would download the recipes and use the information (like conan info would), but not download the actual conan_package.tgz which for a sysroot or build-tools could be a few 100 MB to several GB. This is to avoid traffic for users running the conan install commands simply to populate the CMakeUserPresets.

I am afraid this will not possible: the information about packages only happens if the binary is there, the ``package_info()`` that collects information about the package to be used by the consumers only works when the package binary has been downloaded and unzipped (there are tools there like ``self.cpp_info.libs = collect_libs()`` that basically reads the files in disk to obtain the list of libraries). This would be extremely difficult to change. The ``conan info`` is indeed the closest thing, but it will not use generators, because of the mentioned issue.

Also what is the purpose of generating a CMakePresets, if you cannot use it at all, because it will not build because the necessary dependencies are not there? It seems I am missing something in the flow?
Sorry this was never prioritized into Conan 1.X releases, it never got enough priority compared with others. I am moving this to the Conan 2.X releases, lets see if we can address it there.



Patch:
diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py
index 68b8561d2d2..24d75117de8 100644
--- a/conan/tools/cmake/layout.py
+++ b/conan/tools/cmake/layout.py
@@ -80,6 +80,8 @@ def get_build_folder_custom_vars(conanfile):
                     tmp = "shared" if value else "static"
                 else:
                     tmp = "{}_{}".format(var, value)
+        elif group == "self":
+            tmp = getattr(conanfile, var, None)
         else:
             raise ConanException("Invalid 'tools.cmake.cmake_layout:build_folder_vars' value, it has"
                                  " to start with 'settings.' or 'options.': {}".format(s))


Test patch:
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 3940befd7c9..e8e0e28d25b 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1170,6 +1170,52 @@ def layout(self):
     assert "conan-windows-shared-debug" in presets
 
 
+def test_build_folder_vars_self_name_version():
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
+
+        class Conan(ConanFile):
+            name = "pkg"
+            version = "0.1"
+            settings = "os", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                self.folders.build_folder_vars = ["settings.os", "self.name", "self.version"]
+                cmake_layout(self)
+        """)
+    client.save({"conanfile.py": conanfile})
+    client.run("install . -s os=Windows -s build_type=Debug")
+    presets = client.load("build/windows-pkg-0.1/Debug/generators/CMakePresets.json")
+    assert "conan-windows-pkg-0.1-debug" in presets
+    client.run("install . -s os=Linux -s build_type=Release")
+    presets = client.load("build/linux-pkg-0.1/Release/generators/CMakePresets.json")
+    assert "linux-pkg-0.1-release" in presets
+
+    # CLI override has priority
+    client.run("install . -s os=Linux  -s build_type=Release "
+               "-c tools.cmake.cmake_layout:build_folder_vars='[\"self.name\"]'")
+    presets = client.load("build/pkg/Release/generators/CMakePresets.json")
+    assert "conan-pkg-release" in presets
+
+    # Now we do the build in the cache, the recipe folders are still used
+    client.run("create . -s os=Windows -s build_type=Debug")
+    build_folder = client.created_layout().build()
+    presets = load(os.path.join(build_folder,
+                                "build/windows-pkg-0.1/Debug/generators/CMakePresets.json"))
+    assert "conan-windows-pkg-0.1-debug" in presets
+
+    # If we change the conf ``build_folder_vars``, it doesn't affect the cache build
+    client.run("create . -s os=Windows -s build_type=Debug "
+               "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.os\"]'")
+    build_folder = client.created_layout().build()
+    presets = load(os.path.join(build_folder,
+                                "build/windows-pkg-0.1/Debug/generators/CMakePresets.json"))
+    assert "conan-windows-pkg-0.1-debug" in presets
+
+
 def test_extra_flags():
     client = TestClient()
     conanfile = textwrap.dedent("""