Repository: conan-io/conan
Pull request #11414 (2022-06-07 13:33:18)

Fixed two bugs with CMakePresets
Changelog: Bugfix: The `CMakePresets.json` file, when performing several `conan install` commands, ended-up with several entries of duplicated "buildPresets" if used with a `multi-configuration` cmake generator.
Changelog: Fix:  The "include" paths list in a `CMakeUserPresets.json`, when performing a new `conan install`, are now checked, and in case some of them is a missing path (deleted directory), it is cleaned from the list. 
Docs: omit

Closes https://github.com/conan-io/conan/issues/11413
Closes https://github.com/conan-io/conan/issues/11409


Patch:
diff --git a/conan/tools/apple/xcodedeps.py b/conan/tools/apple/xcodedeps.py
index 6011e030b..2cc64fbe6 100644
--- a/conan/tools/apple/xcodedeps.py
+++ b/conan/tools/apple/xcodedeps.py
@@ -245,7 +245,9 @@ class XcodeDeps(object):
         # Generate the config files for each component with name conan_pkgname_compname.xcconfig
         # If a package has no components the name is conan_pkgname_pkgname.xcconfig
         # Then all components are included in the conan_pkgname.xcconfig file
-        for dep in self._conanfile.dependencies.host.values():
+        host_req = self._conanfile.dependencies.host
+        test_req = self._conanfile.dependencies.test
+        for dep in list(host_req.values()) + list(test_req.values()):
             dep_name = _format_name(dep.ref.name)
 
             include_components_names = []
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 81461199d..ca5bf0d30 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -95,18 +95,15 @@ def write_cmake_presets(conanfile, toolchain_file, generator):
         # We append the new configuration making sure that we don't overwrite it
         data = json.loads(load(preset_path))
         if multiconfig:
-            build_presets = data["buildPresets"]
-            build_preset_name = _build_preset_name(conanfile)
-            already_exist = any([b["configuration"]
-                                 for b in build_presets if b == build_preset_name])
+            new_build_preset_name = _build_preset_name(conanfile)
+            already_exist = any([b["name"] for b in data["buildPresets"]
+                                 if b["name"] == new_build_preset_name])
             if not already_exist:
                 data["buildPresets"].append(_add_build_preset(conanfile, multiconfig))
         else:
-            configure_presets = data["configurePresets"]
-            configure_preset_name = _configure_preset_name(conanfile, multiconfig)
-            already_exist = any([c["name"]
-                                 for c in configure_presets
-                                 if c["name"] == configure_preset_name])
+            new_configure_preset_name = _configure_preset_name(conanfile, multiconfig)
+            already_exist = any([c["name"] for c in data["configurePresets"]
+                                 if c["name"] == new_configure_preset_name])
             if not already_exist:
                 conf_preset = _add_configure_preset(conanfile, generator, cache_variables,
                                                     toolchain_file, multiconfig)
@@ -126,6 +123,8 @@ def write_cmake_presets(conanfile, toolchain_file, generator):
                 data = {"version": 4, "include": [preset_path]}
             else:
                 data = json.loads(load(user_presets_path))
+                # Clear the folders that have been deleted
+                data["include"] = [i for i in data["include"] if os.path.exists(i)]
                 if preset_path not in data["include"]:
                     data["include"].append(preset_path)
 


Test patch:
diff --git a/conans/test/functional/toolchains/apple/test_xcodedeps_components.py b/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
index fb3458831..397b3d1fb 100644
--- a/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
+++ b/conans/test/functional/toolchains/apple/test_xcodedeps_components.py
@@ -202,3 +202,28 @@ def test_xcodedeps_components():
     assert "tcp/1.0: Hello World Release!" in client.out
     assert "client/1.0: Hello World Release!" in client.out
     assert "chat/1.0: Hello World Release!" in client.out
+
+
+@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for MacOS")
+@pytest.mark.tool_cmake
+def test_xcodedeps_test_require():
+    client = TestClient()
+    client.run("new gtest/1.0 -m cmake_lib")
+    # client.run("new cmake_lib -d name=app -d version=1.0")
+    client.run("create . -tf=None")
+
+    # Create library having build and test requires
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        class HelloLib(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            def build_requirements(self):
+                self.test_requires('gtest/1.0')
+        ''')
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("install . -g XcodeDeps")
+    assert os.path.isfile(os.path.join(client.current_folder, "conan_gtest.xcconfig"))
+    assert os.path.isfile(os.path.join(client.current_folder, "conan_gtest_gtest.xcconfig"))
+    assert os.path.isfile(os.path.join(client.current_folder, "conan_gtest_gtest_release_x86_64.xcconfig"))
+    assert os.path.isfile(os.path.join(client.current_folder, "conan_gtest_gtest_vars_release_x86_64.xcconfig"))
+    assert '#include "conan_gtest.xcconfig"' in client.load("conandeps.xcconfig")
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 8b1311a59..4f6e67e30 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -12,7 +12,7 @@ from conans.model.ref import ConanFileReference
 from conans.test.assets.cmake import gen_cmakelists
 from conans.test.assets.genconanfile import GenConanfile
 from conans.test.utils.tools import TestClient, TurboTestClient
-from conans.util.files import save, load
+from conans.util.files import save, load, rmdir
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
@@ -622,6 +622,56 @@ def test_cmake_presets_multiple_settings_single_config():
         assert "__cplusplus2020" in client.out
 
 
+@pytest.mark.parametrize("multiconfig", [True, False])
+def test_cmake_presets_duplicated_install(multiconfig):
+    # https://github.com/conan-io/conan/issues/11409
+    """Only failed when using a multiconfig generator"""
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=cmake_exe")
+    settings = '-s compiler=gcc -s compiler.version=5 -s compiler.libcxx=libstdc++11 ' \
+               '-c tools.cmake.cmake_layout:build_folder_vars=' \
+               '\'["settings.compiler", "settings.compiler.version"]\' '
+    if multiconfig:
+        settings += '-c tools.cmake.cmaketoolchain:generator="Multi-Config"'
+    client.run("install . {}".format(settings))
+    client.run("install . {}".format(settings))
+    presets_path = os.path.join(client.current_folder, "build", "gcc-5", "generators",
+                                "CMakePresets.json")
+    assert os.path.exists(presets_path)
+    contents = json.loads(load(presets_path))
+    assert len(contents["buildPresets"]) == 1
+
+
+def test_remove_missing_presets():
+    # https://github.com/conan-io/conan/issues/11413
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=cmake_exe")
+    settings = '-s compiler=gcc -s compiler.version=5 -s compiler.libcxx=libstdc++11 ' \
+               '-c tools.cmake.cmake_layout:build_folder_vars=' \
+               '\'["settings.compiler", "settings.compiler.version"]\' '
+    client.run("install . {}".format(settings))
+    client.run("install . {} -s compiler.version=6".format(settings))
+
+    presets_path_5 = os.path.join(client.current_folder, "build", "gcc-5")
+    assert os.path.exists(presets_path_5)
+
+    presets_path_6 = os.path.join(client.current_folder, "build", "gcc-6")
+    assert os.path.exists(presets_path_6)
+
+    rmdir(presets_path_5)
+
+    # If we generate another configuration, the missing one (removed) for gcc-5 is not included
+    client.run("install . {} -s compiler.version=11".format(settings))
+
+    user_presets_path = os.path.join(client.current_folder, "CMakeUserPresets.json")
+    assert os.path.exists(user_presets_path)
+
+    contents = json.loads(load(user_presets_path))
+    assert len(contents["include"]) == 2
+    assert "gcc-6" in contents["include"][0]
+    assert "gcc-11" in contents["include"][1]
+
+
 @pytest.mark.tool_cmake(version="3.23")
 def test_cmake_presets_options_single_config():
     client = TestClient(path_with_spaces=False)