Repository: conan-io/conan
Pull request #12841 (2023-01-04 14:06:36)

fix XcodeDeps in editable root path
Changelog: Bugfix: fix XcodeDeps in "editable" mode root package_folder=None
Docs: Omit

Close https://github.com/conan-io/conan/issues/12839



Patch:
diff --git a/conan/tools/apple/xcodedeps.py b/conan/tools/apple/xcodedeps.py
index fb79a0259..f4b6d9209 100644
--- a/conan/tools/apple/xcodedeps.py
+++ b/conan/tools/apple/xcodedeps.py
@@ -130,6 +130,7 @@ class XcodeDeps(object):
             merged = [var for cpp_info in transitive_cpp_infos for var in getattr(cpp_info, name)]
             return list(OrderedDict.fromkeys(merged).keys())
 
+        # TODO: Investigate if paths can be made relative to "root" folder
         fields = {
             'pkg_name': pkg_name,
             'comp_name': comp_name,
@@ -270,8 +271,10 @@ class XcodeDeps(object):
                     transitive_internal = list(OrderedDict.fromkeys(transitive_internal).keys())
                     transitive_external = list(OrderedDict.fromkeys(transitive_external).keys())
 
+                    # In case dep is editable and package_folder=None
+                    pkg_folder = dep.package_folder or dep.recipe_folder
                     component_content = self.get_content_for_component(dep_name, comp_name,
-                                                                       dep.package_folder,
+                                                                       pkg_folder,
                                                                        transitive_internal,
                                                                        transitive_external)
                     include_components_names.append((dep_name, comp_name))
@@ -289,7 +292,9 @@ class XcodeDeps(object):
                         public_deps.append((_format_name(d.ref.name),) * 2)
 
                 required_components = dep.cpp_info.required_components if dep.cpp_info.required_components else public_deps
-                root_content = self.get_content_for_component(dep_name, dep_name, dep.package_folder, [dep.cpp_info],
+                # In case dep is editable and package_folder=None
+                pkg_folder = dep.package_folder or dep.recipe_folder
+                root_content = self.get_content_for_component(dep_name, dep_name, pkg_folder, [dep.cpp_info],
                                                               required_components)
                 include_components_names.append((dep_name, dep_name))
                 result.update(root_content)
diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py
index f39ef6974..abddc047f 100644
--- a/conan/tools/cmake/layout.py
+++ b/conan/tools/cmake/layout.py
@@ -22,20 +22,21 @@ def cmake_layout(conanfile, generator=None, src_folder=".", build_folder="build"
         raise ConanException("'build_type' setting not defined, it is necessary for cmake_layout()")
 
     build_folder = build_folder if not subproject else os.path.join(subproject, build_folder)
-    custom_conf = get_build_folder_custom_vars(conanfile)
-    if custom_conf:
-        build_folder = os.path.join(build_folder, custom_conf)
+    config_build_folder, user_defined_build = get_build_folder_custom_vars(conanfile)
 
-    if multi:
-        conanfile.folders.build = build_folder
+    if config_build_folder:
+        conanfile.folders.build = os.path.join(build_folder, config_build_folder)
     else:
-        conanfile.folders.build = os.path.join(build_folder, build_type)
+        if not multi and not user_defined_build:
+            conanfile.folders.build = os.path.join(build_folder, build_type)
+        else:
+            conanfile.folders.build = build_folder
 
-    conanfile.folders.generators = os.path.join(build_folder, "generators")
+    conanfile.folders.generators = os.path.join(conanfile.folders.build, "generators")
 
     conanfile.cpp.source.includedirs = ["include"]
 
-    if multi:
+    if multi and not user_defined_build:
         conanfile.cpp.build.libdirs = ["{}".format(build_type)]
         conanfile.cpp.build.bindirs = ["{}".format(build_type)]
     else:
@@ -49,17 +50,11 @@ def get_build_folder_custom_vars(conanfile):
                                     default=[], check_type=list)
     ret = []
     for s in build_vars:
+        group, var = s.split(".", 1)
         tmp = None
-        if s.startswith("settings."):
-            _, var = s.split("settings.", 1)
-            if var == "build_type":
-                raise ConanException("Error, don't include 'settings.build_type' in the "
-                                     "'tools.cmake.cmake_layout:build_folder_vars' conf. It is "
-                                     "managed by default because 'CMakeToolchain' and 'CMakeDeps' "
-                                     "are multi-config generators.`")
+        if group == "settings":
             tmp = conanfile.settings.get_safe(var)
-        elif s.startswith("options."):
-            _, var = s.split("options.", 1)
+        elif group == "options":
             value = conanfile.options.get_safe(var)
             if value is not None:
                 tmp = "{}_{}".format(var, value)
@@ -69,4 +64,5 @@ def get_build_folder_custom_vars(conanfile):
         if tmp:
             ret.append(tmp.lower())
 
-    return "-".join(ret)
+    user_defined_build = "settings.build_type" in build_vars
+    return "-".join(ret), user_defined_build
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 6d4a9a735..dacf021ef 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -21,7 +21,10 @@ def _build_preset(conanfile, multiconfig):
 
 def _build_preset_name(conanfile):
     build_type = conanfile.settings.get_safe("build_type")
-    custom_conf = get_build_folder_custom_vars(conanfile)
+    custom_conf, user_defined_build = get_build_folder_custom_vars(conanfile)
+    if user_defined_build:
+        return custom_conf
+
     if custom_conf:
         if build_type:
             return "{}-{}".format(custom_conf, build_type.lower())
@@ -32,7 +35,10 @@ def _build_preset_name(conanfile):
 
 def _configure_preset_name(conanfile, multiconfig):
     build_type = conanfile.settings.get_safe("build_type")
-    custom_conf = get_build_folder_custom_vars(conanfile)
+    custom_conf, user_defined_build = get_build_folder_custom_vars(conanfile)
+
+    if user_defined_build:
+        return custom_conf
 
     if multiconfig or not build_type:
         return "default" if not custom_conf else custom_conf


Test patch:
diff --git a/conans/test/functional/layout/test_build_system_layout_helpers.py b/conans/test/functional/layout/test_build_system_layout_helpers.py
index 4bd7c8cd4..c683114b8 100644
--- a/conans/test/functional/layout/test_build_system_layout_helpers.py
+++ b/conans/test/functional/layout/test_build_system_layout_helpers.py
@@ -168,7 +168,8 @@ def test_cmake_layout_external_sources():
 
     # Local flow
     client.run("install . foo/1.0 -s os=Linux")
-    assert os.path.exists(os.path.join(client.current_folder, "build", "generators", "generate.txt"))
+    assert os.path.exists(os.path.join(client.current_folder,
+                                       "build", "Release", "generators", "generate.txt"))
     client.run("source .")
     assert os.path.exists(os.path.join(client.current_folder, "src", "source.txt"))
     client.run("build .")
@@ -294,4 +295,4 @@ def test_cmake_layout_custom_build_folder():
     client.save({"conanfile.py": conanfile})
     client.run("install .")
     assert os.path.exists(os.path.join(client.current_folder,
-                                       "mybuild/generators/conan_toolchain.cmake"))
+                                       "mybuild/Release/generators/conan_toolchain.cmake"))
diff --git a/conans/test/functional/layout/test_local_commands.py b/conans/test/functional/layout/test_local_commands.py
index bd036d617..0c07d5cdd 100644
--- a/conans/test/functional/layout/test_local_commands.py
+++ b/conans/test/functional/layout/test_local_commands.py
@@ -372,7 +372,11 @@ def test_start_dir_failure():
     c = TestClient()
     c.save(pkg_cmake("dep", "0.1"))
     c.run("install .")
-    expected_path = os.path.join(c.current_folder, "build", "generators", "conan_toolchain.cmake")
+    if platform.system() != "Windows":
+        gen_folder = os.path.join(c.current_folder, "build", "Release", "generators")
+    else:
+        gen_folder = os.path.join(c.current_folder, "build", "generators")
+    expected_path = os.path.join(gen_folder, "conan_toolchain.cmake")
     assert os.path.exists(expected_path)
     os.unlink(expected_path)
     with c.chdir("build"):
diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
index 45b5d1d97..c1ceb5d3a 100644
--- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -497,7 +497,7 @@ def test_system_dep():
     files = pkg_cmake("mylib", "0.1", requires=["zlib/0.1"])
     files["CMakeLists.txt"] = files["CMakeLists.txt"].replace("find_package(zlib)",
                                                               "find_package(ZLIB)")
-    files["CMakeLists.txt"] = files["CMakeLists.txt"].replace("zlib::zlib","ZLIB::ZLIB")
+    files["CMakeLists.txt"] = files["CMakeLists.txt"].replace("zlib::zlib", "ZLIB::ZLIB")
     client.save({os.path.join("mylib", name): content for name, content in files.items()})
     files = pkg_cmake("consumer", "0.1", requires=["mylib/0.1"])
     client.save({os.path.join("consumer", name): content for name, content in files.items()})
@@ -509,7 +509,7 @@ def test_system_dep():
     client.run("install consumer")
     if platform.system() != "Windows":
         host_arch = client.get_default_host_profile().settings['arch']
-        data = os.path.join(f"consumer/build/generators/mylib-release-{host_arch}-data.cmake")
+        data = f"consumer/build/Release/generators/mylib-release-{host_arch}-data.cmake"
         contents = client.load(data)
         assert 'set(ZLIB_FIND_MODE "")' in contents
 
diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py
index e03007054..06c6d1943 100644
--- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py
+++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_and_linker_flags.py
@@ -52,7 +52,8 @@ def test_shared_link_flags():
     client.run("new hello/1.0 -m cmake_lib")
     client.save({"conanfile.py": conanfile})
     client.run("create .")
-    t = os.path.join("test_package", "build", "generators", "hello-release-x86_64-data.cmake")
+    t = os.path.join("test_package", "build", "Release",
+                     "generators", "hello-release-x86_64-data.cmake")
     target_data_cmake_content = client.load(t)
     assert 'set(hello_SHARED_LINK_FLAGS_RELEASE "-z now;-z relro")' in target_data_cmake_content
     assert 'set(hello_EXE_LINK_FLAGS_RELEASE "-z now;-z relro")' in target_data_cmake_content
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 49f73cf5a..dbd70681e 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -297,8 +297,14 @@ def test_install_output_directories():
     p_folder = client.cache.package_layout(pref.ref).package(pref)
     assert os.path.exists(os.path.join(p_folder, "mylibs"))
     assert not os.path.exists(os.path.join(p_folder, "lib"))
+
     b_folder = client.cache.package_layout(pref.ref).build(pref)
-    toolchain = client.load(os.path.join(b_folder, "build", "generators", "conan_toolchain.cmake"))
+    if platform.system() != "Windows":
+        gen_folder = os.path.join(b_folder, "build", "Release", "generators")
+    else:
+        gen_folder = os.path.join(b_folder, "build", "generators")
+
+    toolchain = client.load(os.path.join(gen_folder, "conan_toolchain.cmake"))
     assert 'set(CMAKE_INSTALL_LIBDIR "mylibs")' in toolchain
 
 
@@ -962,15 +968,15 @@ def test_cmake_presets_with_conanfile_txt():
     assert "Hello World Release!" in c.out
 
 
-def test_cmake_presets_forbidden_build_type():
+def test_cmake_presets_not_forbidden_build_type():
     client = TestClient(path_with_spaces=False)
     client.run("new hello/0.1 --template cmake_exe")
     # client.run("new cmake_exe -d name=hello -d version=0.1")
     settings_layout = '-c tools.cmake.cmake_layout:build_folder_vars=' \
                       '\'["options.missing", "settings.build_type"]\''
-    client.run("install . {}".format(settings_layout), assert_error=True)
-    assert "Error, don't include 'settings.build_type' in the " \
-           "'tools.cmake.cmake_layout:build_folder_vars' conf" in client.out
+    client.run("install . {}".format(settings_layout))
+    assert os.path.exists(os.path.join(client.current_folder,
+                                       "build/release/generators/conan_toolchain.cmake"))
 
 
 def test_resdirs_cmake_install():
@@ -1116,7 +1122,8 @@ def test_cmake_toolchain_vars_when_option_declared():
     # the CMakeLists
     fpic_option = "-o mylib:fPIC=False" if platform.system() != "Windows" else ""
     t.run(f"install . -o mylib:shared=False {fpic_option}")
-    t.run_command("cmake -S . -B build/ -DCMAKE_TOOLCHAIN_FILE=build/generators/conan_toolchain.cmake")
+    folder = "build/generators" if platform.system() == "Windows" else "build/Release/generators"
+    t.run_command(f"cmake -S . -B build/ -DCMAKE_TOOLCHAIN_FILE={folder}/conan_toolchain.cmake")
     assert "mylib target type: STATIC_LIBRARY" in t.out
     assert f"mylib position independent code: OFF" in t.out
 
@@ -1185,8 +1192,6 @@ def test_find_program_for_tool_requires():
                 "build_profile": build_profile
                 })
 
-    xxx = client.get_default_build_profile()
-
     client.run("create . -pr:b build_profile -pr:h build_profile")
     client.run("create . -pr:b build_profile -pr:h host_profile")
 
@@ -1226,7 +1231,7 @@ def test_find_program_for_tool_requires():
     host_context_package_id = "bf544cd3bc20b82121fd76b82eacbb36d75fa167"
 
     with client.chdir("build"):
-        client.run_command("cmake .. -DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release")
+        client.run_command("cmake .. -DCMAKE_TOOLCHAIN_FILE=Release/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release")
         # Verify binary executable is found from build context package,
         # and library comes from host context package
         assert f"package/{build_context_package_id}/bin/foobin" in client.out
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
index 30b5622b2..f108945ef 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain_xcode_flags.py
@@ -38,7 +38,7 @@ def test_cmake_apple_bitcode_arc_and_visibility_flags_enabled(op_system, os_vers
     client.run("new hello/0.1 --template=cmake_lib")
     _add_message_status_flags(client)
     client.run("install . --profile:build=default --profile:host=host")
-    toolchain = client.load(os.path.join("build", "generators", "conan_toolchain.cmake"))
+    toolchain = client.load(os.path.join("build", "Release", "generators", "conan_toolchain.cmake"))
     # bitcode
     assert 'set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")' in toolchain
     assert 'set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode")' in toolchain
@@ -121,7 +121,7 @@ def test_cmake_apple_bitcode_arc_and_visibility_flags_disabled(op_system, os_ver
     client.run("new hello/0.1 --template=cmake_lib")
     _add_message_status_flags(client)
     client.run("install . --profile:build=default --profile:host=host")
-    toolchain = client.load(os.path.join("build", "generators", "conan_toolchain.cmake"))
+    toolchain = client.load(os.path.join("build", "Release", "generators", "conan_toolchain.cmake"))
     # bitcode
     assert 'set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")' in toolchain
     assert 'set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode")' not in toolchain
@@ -163,7 +163,7 @@ def test_cmake_apple_bitcode_arc_and_visibility_flags_are_none(op_system, os_ver
     client.run("new hello/0.1 --template=cmake_lib")
     _add_message_status_flags(client)
     client.run("install . --profile:build=default --profile:host=host")
-    toolchain = client.load(os.path.join("build", "generators", "conan_toolchain.cmake"))
+    toolchain = client.load(os.path.join("build", "Release", "generators", "conan_toolchain.cmake"))
     # bitcode
     assert 'set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")' not in toolchain
     assert 'set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode")' not in toolchain
diff --git a/conans/test/integration/graph/version_ranges/version_range_override_test.py b/conans/test/integration/graph/version_ranges/version_range_override_test.py
index c861a3992..0a23c78b3 100644
--- a/conans/test/integration/graph/version_ranges/version_range_override_test.py
+++ b/conans/test/integration/graph/version_ranges/version_range_override_test.py
@@ -90,3 +90,45 @@ class VersionRangeOverrideFailTestCase(unittest.TestCase):
         lock = t.load("conan.lock")
         self.assertIn("gtest/1.8.1@bloomberg/stable", lock)
         self.assertNotIn("gtest/1.8.0@PORT/stable", lock)
+
+    def test_override(self):
+        """
+        pkga -> ros_perception  -> ros_core
+           \\-----> pkgb  -----------/
+        """
+        # https://github.com/conan-io/conan/issues/8071
+        t = TestClient()
+        t.save({"conanfile.py": GenConanfile()})
+        t.run("create . ros_core/1.1.4@3rdparty/unstable")
+        t.run("create . ros_core/PR-53@3rdparty/snapshot")
+        t.save({"conanfile.py": GenConanfile().with_requires("ros_core/1.1.4@3rdparty/unstable")})
+        t.run("create . ros_perception/1.1.4@3rdparty/unstable")
+        t.run("create . ros_perception/PR-53@3rdparty/snapshot")
+        t.save({"conanfile.py": GenConanfile().with_requires("ros_core/[~=1.1]@3rdparty/unstable")})
+        t.run("create . pkgb/0.1@common/unstable")
+        t.save({"conanfile.py": GenConanfile("pkga", "0.1").with_requires(
+            "ros_perception/[~=1.1]@3rdparty/unstable",
+            "pkgb/[~=0]@common/unstable")})
+        t.run("create . ")
+        self.assertIn("ros_core/1.1.4@3rdparty/unstable", t.out)
+        self.assertIn("ros_perception/1.1.4@3rdparty/unstable", t.out)
+        self.assertNotIn("snapshot", t.out)
+
+        t.save({"conanfile.py": GenConanfile("pkga", "0.1")
+               .with_require("pkgb/[~=0]@common/unstable")
+               .with_require("ros_perception/PR-53@3rdparty/snapshot")
+               .with_require("ros_core/PR-53@3rdparty/snapshot", override=True)})
+
+        t.run("create .  --build=missing --build=pkga")
+        self.assertIn("ros_core/PR-53@3rdparty/snapshot", t.out)
+        self.assertIn("ros_perception/PR-53@3rdparty/snapshot", t.out)
+
+        # Override only the upstream without overriding the direct one
+        t.save({"conanfile.py": GenConanfile("pkga", "0.1")
+               .with_require("pkgb/[~=0]@common/unstable")
+               .with_require("ros_perception/[~=1.1]@3rdparty/unstable")
+               .with_require("ros_core/PR-53@3rdparty/snapshot", override=True)})
+
+        t.run("create .  --build=missing --build=pkga")
+        self.assertIn("ros_core/PR-53@3rdparty/snapshot", t.out)
+        self.assertIn("ros_perception/1.1.4@3rdparty/unstable", t.out)
diff --git a/conans/test/integration/layout/test_layout_paths.py b/conans/test/integration/layout/test_layout_paths.py
index 5d863f411..5049ac534 100644
--- a/conans/test/integration/layout/test_layout_paths.py
+++ b/conans/test/integration/layout/test_layout_paths.py
@@ -1,3 +1,4 @@
+import os
 import textwrap
 
 from conans.test.assets.genconanfile import GenConanfile
@@ -6,6 +7,7 @@ from conans.test.utils.tools import TestClient
 
 def test_editable_layout_paths():
     # https://github.com/conan-io/conan/issues/12521
+    # https://github.com/conan-io/conan/issues/12839
     c = TestClient()
     dep = textwrap.dedent("""
         import os
@@ -21,13 +23,16 @@ def test_editable_layout_paths():
             "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type", "arch")
                                                           .with_requires("dep/0.1")
                                                           .with_generator("CMakeDeps")
-                                                          .with_generator("PkgConfigDeps")})
+                                                          .with_generator("PkgConfigDeps")
+                                                          .with_generator("XcodeDeps")})
     c.run("editable add dep dep/0.1")
-    c.run("install pkg")
+    c.run("install pkg -s arch=x86_64")
     # It doesn't crash anymore
     assert "dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Editable" in c.out
-    arch = c.get_default_host_profile().settings['arch']
-    data = c.load(f"dep-release-{arch}-data.cmake")
+    data = c.load(f"dep-release-x86_64-data.cmake")
     assert 'set(dep_INCLUDE_DIRS_RELEASE "${dep_PACKAGE_FOLDER_RELEASE}/include")' in data
     pc = c.load("dep.pc")
     assert "includedir1=${prefix}/include" in pc
+    xcode = c.load("conan_dep_dep_release_x86_64.xcconfig")
+    dep_path = os.path.join(c.current_folder, "dep")
+    assert f"PACKAGE_ROOT_dep[config=Release][arch=x86_64][sdk=*] = {dep_path}" in xcode
diff --git a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
index 6d3e9b57d..b3da541f9 100644
--- a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
+++ b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py
@@ -1,4 +1,5 @@
 import os
+import platform
 import textwrap
 
 from conans.test.assets.genconanfile import GenConanfile
@@ -225,9 +226,14 @@ def test_dependency_props_from_consumer():
 
     client.save({"foo.py": foo.format(set_find_mode=""), "bar.py": bar}, clean_first=True)
 
-    module_file = os.path.join(client.current_folder, "build", "generators", "module-custom_bar_module_file_nameTargets.cmake")
-    components_module = os.path.join(client.current_folder, "build", "generators", "custom_bar_file_name-Target-release.cmake")
-    config_file = os.path.join(client.current_folder, "build", "generators", "custom_bar_file_nameTargets.cmake")
+    if platform.system() != "Windows":
+        gen_folder = os.path.join(client.current_folder, "build", "Release", "generators")
+    else:
+        gen_folder = os.path.join(client.current_folder, "build", "generators")
+
+    module_file = os.path.join(gen_folder, "module-custom_bar_module_file_nameTargets.cmake")
+    components_module = os.path.join(gen_folder, "custom_bar_file_name-Target-release.cmake")
+    config_file = os.path.join(gen_folder, "custom_bar_file_nameTargets.cmake")
 
     # uses cmake_find_mode set in bar: both
     client.run("create bar.py bar/1.0@")
@@ -311,19 +317,20 @@ def test_props_from_consumer_build_context_activated():
 
     client.save({"foo.py": foo.format(set_find_mode=""), "bar.py": bar}, clean_first=True)
 
-    module_file = os.path.join(client.current_folder, "build", "generators",
-                               "module-custom_bar_module_file_nameTargets.cmake")
-    components_module = os.path.join(client.current_folder, "build", "generators",
-                                     "custom_bar_file_name-Target-release.cmake")
-    config_file = os.path.join(client.current_folder, "build", "generators",
-                               "custom_bar_file_nameTargets.cmake")
+    if platform.system() != "Windows":
+        gen_folder = os.path.join(client.current_folder, "build", "Release", "generators")
+    else:
+        gen_folder = os.path.join(client.current_folder, "build", "generators")
+
+    module_file = os.path.join(gen_folder, "module-custom_bar_module_file_nameTargets.cmake")
+    components_module = os.path.join(gen_folder, "custom_bar_file_name-Target-release.cmake")
+    config_file = os.path.join(gen_folder, "custom_bar_file_nameTargets.cmake")
 
-    module_file_build = os.path.join(client.current_folder, "build", "generators",
+    module_file_build = os.path.join(gen_folder,
                                      "module-custom_bar_build_module_file_name_BUILDTargets.cmake")
-    components_module_build = os.path.join(client.current_folder, "build", "generators",
+    components_module_build = os.path.join(gen_folder,
                                            "custom_bar_build_file_name_BUILD-Target-release.cmake")
-    config_file_build = os.path.join(client.current_folder, "build", "generators",
-                                     "custom_bar_build_file_name_BUILDTargets.cmake")
+    config_file_build = os.path.join(gen_folder, "custom_bar_build_file_name_BUILDTargets.cmake")
 
     # uses cmake_find_mode set in bar: both
     client.run("create bar.py bar/1.0@ -pr:h=default -pr:b=default")
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index e219bcc5a..38a793f42 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -8,7 +8,6 @@ from mock import mock
 
 from conan.tools.cmake.presets import load_cmake_presets
 from conans.test.assets.genconanfile import GenConanfile
-from conans.test.utils.test_files import temp_folder
 from conans.test.utils.tools import TestClient
 from conans.util.files import rmdir
 
@@ -379,7 +378,7 @@ def test_cmake_presets_binary_dir_available():
     else:
         build_dir = os.path.join(client.current_folder, "build")
 
-    presets = load_cmake_presets(os.path.join(client.current_folder, "build", "generators"))
+    presets = load_cmake_presets(os.path.join(build_dir, "generators"))
     assert presets["configurePresets"][0]["binaryDir"] == build_dir
 
 
@@ -422,6 +421,7 @@ def test_cmake_presets_multiconfig():
     # Repeat one
     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["buildPresets"]) == 4
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert presets["buildPresets"][1]["configuration"] == "Debug"
@@ -517,6 +517,7 @@ def test_toolchain_cache_variables():
     assert "-DCMAKE_TOOLCHAIN_FILE=" in client.out
     assert f"-G {_format_val('MinGW Makefiles')}" in client.out
 
+
 def test_android_c_library():
     client = TestClient()
     conanfile = textwrap.dedent("""
@@ -845,3 +846,36 @@ def test_set_cmake_lang_compilers_and_launchers():
     assert 'set(CMAKE_C_COMPILER "/my/local/gcc")' in toolchain
     assert 'set(CMAKE_CXX_COMPILER "g++")' in toolchain
     assert 'set(CMAKE_RC_COMPILER "C:/local/rc.exe")' in toolchain
+
+
+def test_cmake_layout_toolchain_folder():
+    """ in single-config generators, the toolchain is a different file per configuration
+    https://github.com/conan-io/conan/issues/12827
+    """
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
+
+        class Conan(ConanFile):
+            settings = "os", "arch", "compiler", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                cmake_layout(self)
+            """)
+    c.save({"conanfile.py": conanfile})
+    c.run("install . -s os=Linux -s compiler=gcc -s compiler.version=7 -s build_type=Release "
+          "-s compiler.libcxx=libstdc++11")
+    assert os.path.exists(os.path.join(c.current_folder,
+                                       "build/Release/generators/conan_toolchain.cmake"))
+    c.run("install . -s os=Linux -s compiler=gcc -s compiler.version=7 -s build_type=Debug "
+          "-s compiler.libcxx=libstdc++11")
+    assert os.path.exists(os.path.join(c.current_folder,
+                                       "build/Debug/generators/conan_toolchain.cmake"))
+    c.run("install . -s os=Linux -s compiler=gcc -s compiler.version=7 -s build_type=Debug "
+          "-s compiler.libcxx=libstdc++11 -s arch=x86 "
+          "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.arch\", \"settings.build_type\"]'")
+    print(c.out)
+    assert os.path.exists(os.path.join(c.current_folder,
+                                       "build/x86-debug/generators/conan_toolchain.cmake"))