Repository: conan-io/conan
Pull request #13004 (2023-01-29 22:45:45)

Fix cmake presets
Changelog: Fix: ``CMakeToolchain`` generates preset files with more than 1 configure preset, which is impossible.
Docs: Omit

Also, very minor refactors.


Patch:
diff --git a/conan/tools/cmake/cmake.py b/conan/tools/cmake/cmake.py
index 9b8e1a0f0..d52a8ecb4 100644
--- a/conan/tools/cmake/cmake.py
+++ b/conan/tools/cmake/cmake.py
@@ -1,7 +1,7 @@
 import os
 
 from conan.tools.build import build_jobs
-from conan.tools.cmake.presets import load_cmake_presets, get_configure_preset
+from conan.tools.cmake.presets import load_cmake_presets
 from conan.tools.cmake.utils import is_multi_configuration
 from conan.tools.files import chdir, mkdir
 from conan.tools.microsoft.msbuild import msbuild_verbosity_cmd_line_arg
@@ -53,7 +53,8 @@ class CMake(object):
         self._conanfile = conanfile
 
         cmake_presets = load_cmake_presets(conanfile.generators_folder)
-        configure_preset = get_configure_preset(cmake_presets, conanfile)
+        # Conan generated presets will have exactly 1 configurePresets, no more
+        configure_preset = cmake_presets["configurePresets"][0]
 
         self._generator = configure_preset["generator"]
         self._toolchain_file = configure_preset.get("toolchainFile")
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 11a443a62..5dd3822eb 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -9,18 +9,11 @@ from conans.errors import ConanException
 from conans.util.files import save, load
 
 
-def _build_preset(conanfile, multiconfig):
-    return _common_build_and_test_preset_fields(conanfile, multiconfig)
-
-
-def _test_preset(conanfile, multiconfig):
-    return _common_build_and_test_preset_fields(conanfile, multiconfig)
-
-
-def _common_build_and_test_preset_fields(conanfile, multiconfig):
+def _build_and_test_preset_fields(conanfile, multiconfig):
     build_type = conanfile.settings.get_safe("build_type")
     configure_preset_name = _configure_preset_name(conanfile, multiconfig)
-    ret = {"name": _build_and_test_preset_name(conanfile),
+    build_preset_name = _build_and_test_preset_name(conanfile)
+    ret = {"name": build_preset_name,
            "configurePreset": configure_preset_name}
     if multiconfig:
         ret["configuration"] = build_type
@@ -151,17 +144,16 @@ def _contents(conanfile, toolchain_file, cache_variables, generator):
     Contents for the CMakePresets.json
     It uses schema version 3 unless it is forced to 2
     """
+    multiconfig = is_multi_configuration(generator)
+    conf = _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
+    build = _build_and_test_preset_fields(conanfile, multiconfig)
     ret = {"version": _schema_version(conanfile, default=3),
+           "vendor": {"conan": {}},
            "cmakeMinimumRequired": {"major": 3, "minor": 15, "patch": 0},
-           "configurePresets": [],
-           "buildPresets": [],
-           "testPresets": []
-          }
-    multiconfig = is_multi_configuration(generator)
-    ret["buildPresets"].append(_build_preset(conanfile, multiconfig))
-    ret["testPresets"].append(_test_preset(conanfile, multiconfig))
-    _conf = _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
-    ret["configurePresets"].append(_conf)
+           "configurePresets": [conf],
+           "buildPresets": [build],
+           "testPresets": [build]
+           }
     return ret
 
 
@@ -187,55 +179,55 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables,
 
     preset_path = os.path.join(conanfile.generators_folder, "CMakePresets.json")
     multiconfig = is_multi_configuration(generator)
-
-    if os.path.exists(preset_path):
+    if os.path.exists(preset_path) and multiconfig:
         data = json.loads(load(preset_path))
-        build_preset = _build_preset(conanfile, multiconfig)
+        build_preset = _build_and_test_preset_fields(conanfile, multiconfig)
         _insert_preset(data, "buildPresets", build_preset)
-
-        test_preset = _test_preset(conanfile, multiconfig)
-        _insert_preset(data, "testPresets", test_preset)
-
+        _insert_preset(data, "testPresets", build_preset)
         configure_preset = _configure_preset(conanfile, generator, cache_variables, toolchain_file,
                                              multiconfig)
-        _insert_preset(data, "configurePresets", configure_preset)
+        # Conan generated presets should have only 1 configurePreset, no more, overwrite it
+        data["configurePresets"] = [configure_preset]
     else:
         data = _contents(conanfile, toolchain_file, cache_variables, generator)
 
-    data = json.dumps(data, indent=4)
-    save(preset_path, data)
-    save_cmake_user_presets(conanfile, preset_path, user_presets_path)
+    preset_content = json.dumps(data, indent=4)
+    save(preset_path, preset_content)
+    _save_cmake_user_presets(conanfile, preset_path, user_presets_path)
 
 
-def save_cmake_user_presets(conanfile, preset_path, user_presets_path=None):
-    if user_presets_path is False:
+def _save_cmake_user_presets(conanfile, preset_path, user_presets_path):
+    if not user_presets_path:
         return
 
-    # Try to save the CMakeUserPresets.json if layout declared and CMakeLists.txt found
-    if conanfile.source_folder and conanfile.source_folder != conanfile.generators_folder:
-        if user_presets_path:
-            output_dir = os.path.join(conanfile.source_folder, user_presets_path) \
-                if not os.path.isabs(user_presets_path) else user_presets_path
-        else:
-            output_dir = conanfile.source_folder
-
-        if user_presets_path or os.path.exists(os.path.join(output_dir, "CMakeLists.txt")):
-            """
-                Contents for the CMakeUserPresets.json
-                It uses schema version 4 unless it is forced to 2
-            """
-            user_presets_path = os.path.join(output_dir, "CMakeUserPresets.json")
-            if not os.path.exists(user_presets_path):
-                data = {"version": _schema_version(conanfile, default=4),
-                        "vendor": {"conan": dict()}}
-            else:
-                data = json.loads(load(user_presets_path))
-                if "conan" not in data.get("vendor", {}):
-                    # The file is not ours, we cannot overwrite it
-                    return
-            data = _append_user_preset_path(conanfile, data, preset_path)
-            data = json.dumps(data, indent=4)
-            save(user_presets_path, data)
+    # If generators folder is the same as source folder, do not create the user presets
+    # we already have the CMakePresets.json right there
+    if not (conanfile.source_folder and conanfile.source_folder != conanfile.generators_folder):
+        return
+
+    user_presets_path = os.path.join(conanfile.source_folder, user_presets_path)
+    if os.path.isdir(user_presets_path):  # Allows user to specify only the folder
+        output_dir = user_presets_path
+        user_presets_path = os.path.join(user_presets_path, "CMakeUserPresets.json")
+    else:
+        output_dir = os.path.dirname(user_presets_path)
+
+    if not os.path.exists(os.path.join(output_dir, "CMakeLists.txt")):
+        return
+
+    # It uses schema version 4 unless it is forced to 2
+    if not os.path.exists(user_presets_path):
+        data = {"version": _schema_version(conanfile, default=4),
+                "vendor": {"conan": dict()}}
+    else:
+        data = json.loads(load(user_presets_path))
+        if "conan" not in data.get("vendor", {}):
+            # The file is not ours, we cannot overwrite it
+            return
+    data = _append_user_preset_path(conanfile, data, preset_path)
+
+    data = json.dumps(data, indent=4)
+    save(user_presets_path, data)
 
 
 def _get_already_existing_preset_index(name, presets):
@@ -281,23 +273,3 @@ def _append_user_preset_path(conanfile, data, preset_path):
 def load_cmake_presets(folder):
     tmp = load(os.path.join(folder, "CMakePresets.json"))
     return json.loads(tmp)
-
-
-def get_configure_preset(cmake_presets, conanfile):
-    expected_name = _configure_preset_name(conanfile, multiconfig=False)
-    # Do we find a preset for the current configuration?
-    for preset in cmake_presets["configurePresets"]:
-        if preset["name"] == expected_name:
-            return preset
-
-    expected_name = _configure_preset_name(conanfile, multiconfig=True)
-    # In case of multi-config generator or None build_type
-    for preset in cmake_presets["configurePresets"]:
-        if preset["name"] == expected_name:
-            return preset
-
-    # FIXME: Might be an issue if someone perform several conan install that involves different
-    #        CMake generators (multi and single config). Would be impossible to determine which
-    #        is the correct configurePreset because the generator IS in the configure preset.
-
-    raise ConanException("Not available configurePreset, expected name is {}".format(expected_name))
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 0bfb04bff..a18d515fb 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -148,7 +148,7 @@ class CMakeToolchain(object):
                                        ("output_dirs", OutputDirsBlock)])
 
         check_using_build_profile(self._conanfile)
-        self.user_presets_path = None
+        self.user_presets_path = "CMakeUserPresets.json"
 
     def _context(self):
         """ Returns dict, the context for the template
diff --git a/conan/tools/microsoft/nmaketoolchain.py b/conan/tools/microsoft/nmaketoolchain.py
index ef4795385..c9c1c666f 100644
--- a/conan/tools/microsoft/nmaketoolchain.py
+++ b/conan/tools/microsoft/nmaketoolchain.py
@@ -1,5 +1,6 @@
-from conan.tools._compilers import cppstd_flag, build_type_flags
+from conan.tools._compilers import cppstd_flag, build_type_flags, build_type_link_flags
 from conan.tools.env import Environment
+from conan.tools.microsoft.visual import msvc_runtime_flag, VCVars
 
 
 class NMakeToolchain(object):
@@ -15,42 +16,100 @@ class NMakeToolchain(object):
         :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
         """
         self._conanfile = conanfile
-        self._environment = None
+
+        # Flags
+        self.extra_cflags = []
+        self.extra_cxxflags = []
+        self.extra_ldflags = []
+        self.extra_defines = []
+
+    def _format_options(self, options):
+        return [f"{opt[0].replace('-', '/')}{opt[1:]}" for opt in options if len(opt) > 1]
+
+    def _format_defines(self, defines):
+        formated_defines = []
+        for define in defines:
+            if "=" in define:
+                # CL env-var can't accept '=' sign in /D option, it can be replaced by '#' sign:
+                # https://learn.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
+                macro, value = define.split("=", 1)
+                if value and not value.isnumeric():
+                    value = f'\\"{value}\\"'
+                define = f"{macro}#{value}"
+            formated_defines.append(f"/D{define}")
+        return formated_defines
 
     @property
-    def cl_flags(self):
-        cppflags = []
-        build_type = self._conanfile.settings.get_safe("build_type")
-        if build_type in ['Release', 'RelWithDebInfo', 'MinSizeRel']:
-            cppflags.append("/DNDEBUG")
+    def _cl(self):
+        bt_flags = build_type_flags(self._conanfile.settings)
+        bt_flags = bt_flags if bt_flags else []
 
-        bt = build_type_flags(self._conanfile.settings)
-        if bt:
-            cppflags.extend(bt)
+        rt_flags = msvc_runtime_flag(self._conanfile)
+        rt_flags = [f"/{rt_flags}"] if rt_flags else []
 
+        cflags = []
+        cflags.extend(self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list))
+        cflags.extend(self.extra_cflags)
+
+        cxxflags = []
         cppstd = cppstd_flag(self._conanfile.settings)
         if cppstd:
-            cppflags.append(cppstd)
-        from conan.tools.microsoft import msvc_runtime_flag
-        flag = msvc_runtime_flag(self._conanfile)
-        if flag:
-            cppflags.append("-{}".format(flag))
-        return " ".join(cppflags).replace("-", "/")
+            cxxflags.append(cppstd)
+        cxxflags.extend(self._conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list))
+        cxxflags.extend(self.extra_cxxflags)
+
+        defines = []
+        build_type = self._conanfile.settings.get_safe("build_type")
+        if build_type in ["Release", "RelWithDebInfo", "MinSizeRel"]:
+            defines.append("NDEBUG")
+        defines.extend(self._conanfile.conf.get("tools.build:defines", default=[], check_type=list))
+        defines.extend(self.extra_defines)
+
+        return ["/nologo"] + \
+               self._format_options(bt_flags + rt_flags + cflags + cxxflags) + \
+               self._format_defines(defines)
 
     @property
+    def _link(self):
+        bt_ldflags = build_type_link_flags(self._conanfile.settings)
+        bt_ldflags = bt_ldflags if bt_ldflags else []
+
+        ldflags = []
+        ldflags.extend(bt_ldflags)
+        ldflags.extend(self._conanfile.conf.get("tools.build:sharedlinkflags", default=[], check_type=list))
+        ldflags.extend(self._conanfile.conf.get("tools.build:exelinkflags", default=[], check_type=list))
+        ldflags.extend(self.extra_ldflags)
+
+        return ["/nologo"] + self._format_options(ldflags)
+
     def environment(self):
-        # TODO: Seems we want to make this uniform, equal to other generators
-        if self._environment is None:
-            env = Environment()
-            # The whole injection of toolchain happens in CL env-var, the others LIBS, _LINK_
-            env.append("CL", self.cl_flags)
-            self._environment = env
-        return self._environment
-
-    def vars(self, scope="build"):
-        return self.environment.vars(self._conanfile, scope=scope)
-
-    def generate(self, scope="build"):
-        self.vars(scope).save_script("conannmaketoolchain")
-        from conan.tools.microsoft import VCVars
-        VCVars(self._conanfile).generate()
+        env = Environment()
+        # Injection of compile flags in CL env-var:
+        # https://learn.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
+        env.append("CL", self._cl)
+        # Injection of link flags in _LINK_ env-var:
+        # https://learn.microsoft.com/en-us/cpp/build/reference/linking
+        env.append("_LINK_", self._link)
+        # Also define some special env-vars which can override special NMake macros:
+        # https://learn.microsoft.com/en-us/cpp/build/reference/special-nmake-macros
+        conf_compilers = self._conanfile.conf.get("tools.build:compiler_executables", default={}, check_type=dict)
+        if conf_compilers:
+            compilers_mapping = {
+                "AS": "asm",
+                "CC": "c",
+                "CPP": "cpp",
+                "CXX": "cpp",
+                "RC": "rc",
+            }
+            for env_var, comp in compilers_mapping.items():
+                if comp in conf_compilers:
+                    env.define(env_var, conf_compilers[comp])
+        return env
+
+    def vars(self):
+        return self.environment().vars(self._conanfile, scope="build")
+
+    def generate(self, env=None, scope="build"):
+        env = env or self.environment()
+        env.vars(self._conanfile, scope=scope).save_script("conannmaketoolchain")
+        VCVars(self._conanfile).generate(scope=scope)


Test patch:
diff --git a/conans/test/conftest.py b/conans/test/conftest.py
index e70221448..f41b99dba 100644
--- a/conans/test/conftest.py
+++ b/conans/test/conftest.py
@@ -87,8 +87,7 @@ tools_locations = {
         },
         "3.23": {
             "path": {'Windows': 'C:/cmake/cmake-3.23.1-win64-x64/bin',
-                     'Darwin': '/Users/jenkins/cmake/cmake-3.23.1/bin'
-                               if not MacOS_arm else "skip-tests",
+                     'Darwin': '/Users/jenkins/cmake/cmake-3.23.1/bin',
                      # Not available in Linux
                      'Linux': "skip-tests"}
         }
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 19d7479bd..bfa6239c0 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -556,20 +556,20 @@ def test_cmake_toolchain_runtime_types_cmake_older_than_3_15():
 def test_cmake_presets_missing_option():
     client = TestClient(path_with_spaces=False)
     client.run("new hello/0.1 --template=cmake_exe")
-    settings_layout = '-c tools.cmake.cmake_layout:build_folder_vars=' \
-                      '\'["options.missing"]\''
+    settings_layout = '-c tools.cmake.cmake_layout:build_folder_vars=\'["options.missing"]\' ' \
+                      '-c tools.cmake.cmaketoolchain:generator=Ninja'
     client.run("install . {}".format(settings_layout))
-    assert os.path.exists(os.path.join(client.current_folder, "build", "generators"))
+    assert os.path.exists(os.path.join(client.current_folder, "build", "Release", "generators"))
 
 
 @pytest.mark.tool_cmake(version="3.23")
 def test_cmake_presets_missing_setting():
     client = TestClient(path_with_spaces=False)
     client.run("new hello/0.1 --template=cmake_exe")
-    settings_layout = '-c tools.cmake.cmake_layout:build_folder_vars=' \
-                      '\'["settings.missing"]\''
+    settings_layout = '-c tools.cmake.cmake_layout:build_folder_vars=\'["settings.missing"]\' ' \
+                      '-c tools.cmake.cmaketoolchain:generator=Ninja'
     client.run("install . {}".format(settings_layout))
-    assert os.path.exists(os.path.join(client.current_folder, "build", "generators"))
+    assert os.path.exists(os.path.join(client.current_folder, "build", "Release", "generators"))
 
 
 @pytest.mark.tool_cmake(version="3.23")
@@ -733,8 +733,8 @@ def test_remove_missing_presets():
 def test_cmake_presets_options_single_config():
     client = TestClient(path_with_spaces=False)
     client.run("new hello/0.1 --template=cmake_lib")
-    conf_layout = '-c tools.cmake.cmake_layout:build_folder_vars=\'["settings.compiler", ' \
-                  '"options.shared"]\''
+    conf_layout = '-c tools.cmake.cmake_layout:build_folder_vars=\'["settings.compiler",' \
+                  '"settings.build_type", "options.shared"]\''
 
     default_compiler = {"Darwin": "apple-clang",
                         "Windows": "visual studio",  # FIXME:  replace it with 'msvc' in develop2
@@ -744,12 +744,12 @@ def test_cmake_presets_options_single_config():
         client.run("install . {} -o shared={}".format(conf_layout, shared))
         shared_str = "shared_true" if shared else "shared_false"
         assert os.path.exists(os.path.join(client.current_folder,
-                                           "build", "{}-{}".format(default_compiler, shared_str),
+                                           "build", "{}-release-{}".format(default_compiler, shared_str),
                                            "generators"))
 
     client.run("install . {}".format(conf_layout))
     assert os.path.exists(os.path.join(client.current_folder,
-                                       "build", "{}-shared_false".format(default_compiler),
+                                       "build", "{}-release-shared_false".format(default_compiler),
                                        "generators"))
 
     user_presets_path = os.path.join(client.current_folder, "CMakeUserPresets.json")
@@ -759,12 +759,12 @@ def test_cmake_presets_options_single_config():
     if platform.system() == "Darwin":
         for shared in (True, False):
             shared_str = "shared_true" if shared else "shared_false"
-            client.run_command("cmake . --preset apple-clang-{}-release".format(shared_str))
-            client.run_command("cmake --build --preset apple-clang-{}-release".format(shared_str))
-            client.run_command("ctest --preset apple-clang-{}-release".format(shared_str))
+            client.run_command("cmake . --preset apple-clang-release-{}".format(shared_str))
+            client.run_command("cmake --build --preset apple-clang-release-{}".format(shared_str))
+            client.run_command("ctest --preset apple-clang-release-{}".format(shared_str))
             the_lib = "libhello.a" if not shared else "libhello.dylib"
             path = os.path.join(client.current_folder,
-                                "build", "apple-clang-{}".format(shared_str), "release", the_lib)
+                                "build", "apple-clang-release-{}".format(shared_str), the_lib)
             assert os.path.exists(path)
 
 
@@ -986,9 +986,6 @@ def test_cmake_presets_with_conanfile_txt():
 
     c.run("install .")
     c.run("install . -s build_type=Debug")
-    assert os.path.exists(os.path.join(c.current_folder, "CMakeUserPresets.json"))
-    presets_path = os.path.join(c.current_folder, "build", "generators", "CMakePresets.json")
-    assert os.path.exists(presets_path)
 
     if platform.system() != "Windows":
         c.run_command("cmake --preset debug")
diff --git a/conans/test/functional/toolchains/test_nmake_toolchain.py b/conans/test/functional/toolchains/test_nmake_toolchain.py
index e1a2409bf..f45b97592 100644
--- a/conans/test/functional/toolchains/test_nmake_toolchain.py
+++ b/conans/test/functional/toolchains/test_nmake_toolchain.py
@@ -8,11 +8,19 @@ from conans.test.functional.utils import check_exe_run
 from conans.test.utils.tools import TestClient
 
 
-@pytest.mark.parametrize("compiler, version, runtime, cppstd, build_type",
-                         [("msvc", "190", "dynamic", "14", "Release"),
-                          ("msvc", "191", "static", "17", "Debug")])
+@pytest.mark.parametrize(
+    "compiler, version, runtime, cppstd, build_type, defines, cflags, cxxflags, sharedlinkflags, exelinkflags",
+    [
+        ("msvc", "190", "dynamic", "14", "Release", [], [], [], [], []),
+        ("msvc", "190", "dynamic", "14", "Release",
+         ["TEST_DEFINITION1", "TEST_DEFINITION2=0", "TEST_DEFINITION3=", "TEST_DEFINITION4=TestPpdValue4"],
+         ["/GL"], ["/GL"], ["/LTCG"], ["/LTCG"]),
+        ("msvc", "191", "static", "17", "Debug", [], [], [], [], []),
+    ],
+)
 @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
-def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type):
+def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type,
+                         defines, cflags, cxxflags, sharedlinkflags, exelinkflags):
     client = TestClient(path_with_spaces=False)
     settings = {"compiler": compiler,
                 "compiler.version": version,
@@ -21,11 +29,31 @@ def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type):
                 "build_type": build_type,
                 "arch": "x86_64"}
 
+    serialize_array = lambda arr: "[{}]".format(",".join([f"'{v}'" for v in arr]))
+    conf = {
+        "tools.build:defines": serialize_array(defines) if defines else "",
+        "tools.build:cflags": serialize_array(cflags) if cflags else "",
+        "tools.build:cxxflags": serialize_array(cxxflags) if cxxflags else "",
+        "tools.build:sharedlinkflags": serialize_array(sharedlinkflags) if sharedlinkflags else "",
+        "tools.build:exelinkflags": serialize_array(exelinkflags) if exelinkflags else "",
+    }
+
     # Build the profile according to the settings provided
     settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings.items() if v)
+    conf = " ".join(f'-c {k}="{v}"' for k, v in conf.items() if v)
     client.run("new dep/1.0 -m=cmake_lib")
-    client.run(f'create . -tf=None {settings} '
-               f'-c tools.cmake.cmaketoolchain:generator="Visual Studio 15"')
+    client.run(f'create . -tf=None {settings} {conf}')
+
+    # Rearrange defines to macro / value dict
+    conf_preprocessors = {}
+    for define in defines:
+        if "=" in define:
+            key, value = define.split("=", 1)
+            # gen_function_cpp doesn't properly handle empty macros
+            if value:
+                conf_preprocessors[key] = value
+        else:
+            conf_preprocessors[define] = "1"
 
     conanfile = textwrap.dedent("""
         from conan import ConanFile
@@ -41,17 +69,17 @@ def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type):
         all: simple.exe
 
         .cpp.obj:
-          cl $(cppflags) $*.cpp
+          $(CPP) $*.cpp
 
         simple.exe: simple.obj
-          cl $(cppflags) simple.obj
+          $(CPP) simple.obj
         """)
     client.save({"conanfile.py": conanfile,
                  "makefile": makefile,
-                 "simple.cpp": gen_function_cpp(name="main", includes=["dep"], calls=["dep"])},
+                 "simple.cpp": gen_function_cpp(name="main", includes=["dep"], calls=["dep"], preprocessor=conf_preprocessors.keys())},
                 clean_first=True)
-    client.run("install . {}".format(settings))
+    client.run(f"install . {settings} {conf}")
     client.run("build .")
     client.run_command("simple.exe")
     assert "dep/1.0" in client.out
-    check_exe_run(client.out, "main", "msvc", version, build_type, "x86_64", cppstd)
+    check_exe_run(client.out, "main", "msvc", version, build_type, "x86_64", cppstd, conf_preprocessors)
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index c308a6279..50f1eb789 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -399,6 +399,7 @@ def test_cmake_presets_multiconfig():
 
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Release --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
+    assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert len(presets["testPresets"]) == 1
@@ -406,6 +407,7 @@ def test_cmake_presets_multiconfig():
 
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
+    assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 2
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert presets["buildPresets"][1]["configuration"] == "Debug"
@@ -417,6 +419,7 @@ def test_cmake_presets_multiconfig():
                "--profile:h=profile")
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=MinSizeRel --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
+    assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 4
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert presets["buildPresets"][1]["configuration"] == "Debug"
@@ -432,6 +435,7 @@ def test_cmake_presets_multiconfig():
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
+    assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 4
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert presets["buildPresets"][1]["configuration"] == "Debug"
@@ -449,6 +453,9 @@ def test_cmake_presets_multiconfig():
 
 
 def test_cmake_presets_singleconfig():
+    """ without defining a layout, single config always overwrites
+    the existing CMakePresets.json
+    """
     client = TestClient()
     profile = textwrap.dedent("""
         [settings]
@@ -472,22 +479,22 @@ def test_cmake_presets_singleconfig():
     assert len(presets["testPresets"]) == 1
     assert presets["testPresets"][0]["configurePreset"] == "release"
 
-    # Now two configurePreset, but named correctly
+    # This overwrites the existing profile, as there is no layout
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
-    assert len(presets["configurePresets"]) == 2
-    assert presets["configurePresets"][1]["name"] == "debug"
+    assert len(presets["configurePresets"]) == 1
+    assert presets["configurePresets"][0]["name"] == "debug"
 
-    assert len(presets["buildPresets"]) == 2
-    assert presets["buildPresets"][1]["configurePreset"] == "debug"
+    assert len(presets["buildPresets"]) == 1
+    assert presets["buildPresets"][0]["configurePreset"] == "debug"
 
-    assert len(presets["testPresets"]) == 2
-    assert presets["testPresets"][1]["configurePreset"] == "debug"
+    assert len(presets["testPresets"]) == 1
+    assert presets["testPresets"][0]["configurePreset"] == "debug"
 
     # Repeat configuration, it shouldn't add a new one
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
-    assert len(presets["configurePresets"]) == 2
+    assert len(presets["configurePresets"]) == 1
 
 
 def test_toolchain_cache_variables():