diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py index 470e6ff3857..b815f3267d3 100644 --- a/conan/tools/cmake/layout.py +++ b/conan/tools/cmake/layout.py @@ -1,5 +1,6 @@ import os +from conans.client.graph.graph import RECIPE_CONSUMER from conans.errors import ConanException @@ -50,13 +51,22 @@ def cmake_layout(conanfile, generator=None, src_folder=".", build_folder="build" def get_build_folder_custom_vars(conanfile): - + conanfile_vars = conanfile.folders.build_folder_vars + build_vars = conanfile.conf.get("tools.cmake.cmake_layout:build_folder_vars", check_type=list) if conanfile.tested_reference_str: - build_vars = ["settings.compiler", "settings.compiler.version", "settings.arch", + build_vars = build_vars or conanfile_vars or \ + ["settings.compiler", "settings.compiler.version", "settings.arch", "settings.compiler.cppstd", "settings.build_type", "options.shared"] else: - build_vars = conanfile.conf.get("tools.cmake.cmake_layout:build_folder_vars", - default=[], check_type=list) + try: + is_consumer = conanfile._conan_node.recipe == RECIPE_CONSUMER + except AttributeError: + is_consumer = False + if is_consumer: + build_vars = build_vars or conanfile_vars or [] + else: + build_vars = conanfile_vars or [] + ret = [] for s in build_vars: group, var = s.split(".", 1) @@ -66,7 +76,10 @@ def get_build_folder_custom_vars(conanfile): elif group == "options": value = conanfile.options.get_safe(var) if value is not None: - tmp = "{}_{}".format(var, value) + if var == "shared": + tmp = "shared" if value else "static" + else: + tmp = "{}_{}".format(var, value) else: raise ConanException("Invalid 'tools.cmake.cmake_layout:build_folder_vars' value, it has" " to start with 'settings.' or 'options.': {}".format(s)) diff --git a/conans/model/layout.py b/conans/model/layout.py index 57c50759fa3..e9149b001e3 100644 --- a/conans/model/layout.py +++ b/conans/model/layout.py @@ -54,6 +54,7 @@ def __init__(self): # conanfile.py, that makes most of the output folders defined in layouts (cmake_layout, etc) # start from the subproject again self.subproject = None + self.build_folder_vars = None def __repr__(self): return str(self.__dict__) diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py index 54fbfa2aff9..215b9c5b49b 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py @@ -744,14 +744,14 @@ def test_cmake_presets_options_single_config(): for shared in (True, False): client.run("install . {} -o shared={}".format(conf_layout, shared)) - shared_str = "shared_true" if shared else "shared_false" + shared_str = "shared" if shared else "static" assert os.path.exists(os.path.join(client.current_folder, "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", "{}-release-shared_false".format(default_compiler), + "build", "{}-release-static".format(default_compiler), "generators")) user_presets_path = os.path.join(client.current_folder, "CMakeUserPresets.json") @@ -760,7 +760,7 @@ def test_cmake_presets_options_single_config(): # We can build with cmake manually if platform.system() == "Darwin": for shared in (True, False): - shared_str = "shared_true" if shared else "shared_false" + shared_str = "shared" if shared else "static" client.run_command("cmake . --preset conan-apple-clang-release-{}".format(shared_str)) client.run_command("cmake --build --preset conan-apple-clang-release-{}".format(shared_str)) client.run_command("ctest --preset conan-apple-clang-release-{}".format(shared_str)) diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py index 8d6d2a1ce23..992db79906f 100644 --- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py +++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py @@ -9,7 +9,7 @@ from conan.tools.cmake.presets import load_cmake_presets from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient -from conans.util.files import rmdir +from conans.util.files import rmdir, load def test_cross_build(): @@ -957,7 +957,6 @@ def test_test_package_layout(): class Conan(ConanFile): settings = "os", "arch", "compiler", "build_type" generators = "CMakeToolchain" - test_type = "explicit" def requirements(self): self.requires(self.tested_reference_str) @@ -982,27 +981,69 @@ def test(self): def test_presets_not_found_error_msg(): client = TestClient() - test_conanfile = textwrap.dedent(""" + conanfile = textwrap.dedent(""" from conan import ConanFile - from conan.tools.cmake import cmake_layout, CMake + from conan.tools.cmake import CMake class Conan(ConanFile): settings = "build_type" - generators = "CMakeDeps" - test_type = "explicit" - - def requirements(self): - self.requires(self.tested_reference_str) def build(self): CMake(self).configure() - - def test(self): - pass """) - client.save({"conanfile.py": GenConanfile("pkg", "0.1"), - "test_package/conanfile.py": test_conanfile}) - client.run("create .", assert_error=True) + client.save({"conanfile.py": conanfile}) + client.run("build .", assert_error=True) assert "CMakePresets.json was not found" in client.out assert "Check that you are using CMakeToolchain as generator " \ "to ensure its correct initialization." in client.out + + +def test_recipe_build_folders_vars(): + 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", "arch", "build_type" + options = {"shared": [True, False]} + generators = "CMakeToolchain" + + def layout(self): + self.folders.build_folder_vars = ["settings.os", "options.shared"] + cmake_layout(self) + """) + client.save({"conanfile.py": conanfile}) + client.run("install . -s os=Windows -s arch=armv8 -s build_type=Debug -o shared=True") + presets = client.load("build/windows-shared/Debug/generators/CMakePresets.json") + assert "conan-windows-shared-debug" in presets + client.run("install . -s os=Linux -s arch=x86 -s build_type=Release -o shared=False") + presets = client.load("build/linux-static/Release/generators/CMakePresets.json") + assert "linux-static-release" in presets + + # CLI override has priority + client.run("install . -s os=Linux -s arch=x86 -s build_type=Release -o shared=False " + "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.os\"]'") + presets = client.load("build/linux/Release/generators/CMakePresets.json") + assert "conan-linux-release" in presets + + # Now we do the build in the cache, the recipe folders are still used + client.run("create . -s os=Windows -s arch=armv8 -s build_type=Debug -o shared=True") + ref = client.created_package_reference("pkg/0.1") + layout = client.get_latest_pkg_layout(ref) + build_folder = layout.build() + presets = load(os.path.join(build_folder, + "build/windows-shared/Debug/generators/CMakePresets.json")) + assert "conan-windows-shared-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 arch=armv8 -s build_type=Debug -o shared=True " + "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.os\"]'") + ref = client.created_package_reference("pkg/0.1") + layout = client.get_latest_pkg_layout(ref) + build_folder = layout.build() + presets = load(os.path.join(build_folder, + "build/windows-shared/Debug/generators/CMakePresets.json")) + assert "conan-windows-shared-debug" in presets