Repository: conan-io/conan
Pull request #16633 (2024-07-09 08:24:36)

build-folder-vars user.value
Changelog: Feature: Add ``build_folder_vars=["user.myvalue"]`` to create presets for user "myvalue" arbitrary values.
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/16557


Patch:
diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py
index dad6ce312..fbff89c20 100644
--- a/conan/tools/cmake/layout.py
+++ b/conan/tools/cmake/layout.py
@@ -94,9 +94,11 @@ def get_build_folder_custom_vars(conanfile):
                     tmp = "{}_{}".format(var, value)
         elif group == "self":
             tmp = getattr(conanfile, var, None)
+        elif group == "const":
+            tmp = var
         else:
             raise ConanException("Invalid 'tools.cmake.cmake_layout:build_folder_vars' value, it has"
-                                 " to start with 'settings.' or 'options.': {}".format(s))
+                                 f" to start with 'settings.', 'options.', 'self.' or 'const.': {s}")
         if tmp:
             ret.append(tmp.lower())
 
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 0d6585718..425e62b2a 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -724,7 +724,7 @@ class ExtraFlagsBlock(Block):
         {% if defines %}
         {% if config %}
         {% for define in defines %}
-        add_compile_definitions($<$<CONFIG:{{config}}>:"{{ define }}">)
+        add_compile_definitions("$<$<CONFIG:{{config}}>:{{ define }}>")
         {% endfor %}
         {% else %}
         add_compile_definitions({% for define in defines %} "{{ define }}"{% endfor %})


Test patch:
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index acd6c5ee1..b64c5c791 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -1913,3 +1913,106 @@ def test_cmake_toolchain_cxxflags_multi_config():
     assert 'DEFINE conan_test_answer=123' in c.out
     assert 'other=' not in c.out
     assert "CPLUSPLUS: __cplusplus19" in c.out
+
+
+@pytest.mark.tool("ninja")
+@pytest.mark.tool("cmake", "3.23")
+def test_cmake_toolchain_ninja_multi_config():
+    c = TestClient()
+    profile_release = textwrap.dedent(r"""
+        include(default)
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=42", "conan_test_other=24"]
+        """)
+    profile_debug = textwrap.dedent(r"""
+        include(default)
+        [settings]
+        build_type=Debug
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=123"]
+        """)
+    profile_relwithdebinfo = textwrap.dedent(r"""
+        include(default)
+        [settings]
+        build_type=RelWithDebInfo
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=456", "conan_test_other=abc", 'conan_test_complex="1 2"']
+        """)
+
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
+
+        class Test(ConanFile):
+            exports_sources = "CMakeLists.txt", "src/*"
+            settings = "os", "compiler", "arch", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                cmake_layout(self)
+
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+                cmake.build()
+        ''')
+
+    main = textwrap.dedent(r"""
+        #include <iostream>
+        #include <stdio.h>
+
+        #define STR(x)   #x
+        #define SHOW_DEFINE(x) printf("DEFINE %s=%s!\n", #x, STR(x))
+
+        int main() {
+            SHOW_DEFINE(conan_test_answer);
+            #ifdef conan_test_other
+            SHOW_DEFINE(conan_test_other);
+            #endif
+            #ifdef conan_test_complex
+            SHOW_DEFINE(conan_test_complex);
+            #endif
+        }
+        """)
+
+    cmakelists = textwrap.dedent("""
+        cmake_minimum_required(VERSION 3.15)
+        project(Test CXX)
+        add_executable(example src/main.cpp)
+        """)
+
+    c.save({"conanfile.py": conanfile,
+            "profile_release": profile_release,
+            "profile_debug": profile_debug,
+            "profile_relwithdebinfo": profile_relwithdebinfo,
+            "src/main.cpp": main,
+            "CMakeLists.txt": cmakelists})
+    c.run("install . -pr=./profile_release")
+    c.run("install . -pr=./profile_debug")
+    c.run("install . -pr=./profile_relwithdebinfo")
+
+    with c.chdir("build"):
+        env = r".\generators\conanbuild.bat &&" if platform.system() == "Windows" else ""
+        c.run_command(f"{env} cmake .. -G \"Ninja Multi-Config\" "
+                      "-DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake")
+        c.run_command(f"{env} cmake --build . --config Release")
+        c.run_command(f"{env} cmake --build . --config Debug")
+        c.run_command(f"{env} cmake --build . --config RelWithDebInfo")
+
+    c.run_command(os.sep.join([".", "build", "Release", "example"]))
+    assert 'DEFINE conan_test_answer=42!' in c.out
+    assert 'DEFINE conan_test_other=24!' in c.out
+    assert 'complex=' not in c.out
+
+    c.run_command(os.sep.join([".", "build", "Debug", "example"]))
+    assert 'DEFINE conan_test_answer=123' in c.out
+    assert 'other=' not in c.out
+    assert 'complex=' not in c.out
+
+    c.run_command(os.sep.join([".", "build", "RelWithDebInfo", "example"]))
+    assert 'DEFINE conan_test_answer=456!' in c.out
+    assert 'DEFINE conan_test_other=abc!' in c.out
+    assert 'DEFINE conan_test_complex="1 2"!' in c.out
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index dee27284e..38ac9049b 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1278,6 +1278,32 @@ def test_build_folder_vars_self_name_version():
     assert "conan-windows-pkg-0.1-debug" in presets
 
 
+def test_build_folder_vars_constants_user():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
+
+        class Conan(ConanFile):
+            name = "dep"
+            version = "0.1"
+            settings = "os", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                cmake_layout(self)
+        """)
+    c.save({"conanfile.py": conanfile})
+    conf = "tools.cmake.cmake_layout:build_folder_vars='[\"const.myvalue\"]'"
+    settings = " -s os=FreeBSD -s arch=armv8 -s build_type=Debug"
+    c.run("install . -c={} {}".format(conf, settings))
+    assert "cmake --preset conan-myvalue-debug" in c.out
+    assert os.path.exists(os.path.join(c.current_folder, "build", "myvalue", "Debug"))
+    presets = load(os.path.join(c.current_folder,
+                                "build/myvalue/Debug/generators/CMakePresets.json"))
+    assert "conan-myvalue-debug" in presets
+
+
 def test_extra_flags():
     client = TestClient()
     conanfile = textwrap.dedent("""