diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py index 18f78c5e3b6..6a464f5c3e1 100644 --- a/conan/tools/cmake/layout.py +++ b/conan/tools/cmake/layout.py @@ -14,13 +14,14 @@ def cmake_layout(conanfile, generator=None, src_folder="."): else: multi = False - conanfile.folders.source = src_folder + subproject = conanfile.folders.subproject + conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) try: build_type = str(conanfile.settings.build_type) except ConanException: raise ConanException("'build_type' setting not defined, it is necessary for cmake_layout()") - build_folder = "build" + build_folder = "build" if not subproject else os.path.join(subproject, "build") custom_conf = get_build_folder_custom_vars(conanfile) if custom_conf: build_folder = "{}/{}".format(build_folder, custom_conf) diff --git a/conan/tools/layout/__init__.py b/conan/tools/layout/__init__.py index 9cd97171289..8f64e14e730 100644 --- a/conan/tools/layout/__init__.py +++ b/conan/tools/layout/__init__.py @@ -6,10 +6,12 @@ def basic_layout(conanfile, src_folder="."): - conanfile.folders.build = "build" + subproject = conanfile.folders.subproject + + conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) + conanfile.folders.build = "build" if not subproject else os.path.join(subproject, "build") if conanfile.settings.get_safe("build_type"): conanfile.folders.build += "-{}".format(str(conanfile.settings.build_type).lower()) conanfile.folders.generators = os.path.join(conanfile.folders.build, "conan") conanfile.cpp.build.bindirs = ["."] conanfile.cpp.build.libdirs = ["."] - conanfile.folders.source = src_folder diff --git a/conan/tools/microsoft/layout.py b/conan/tools/microsoft/layout.py index 9d3a045e00c..4da792179e4 100644 --- a/conan/tools/microsoft/layout.py +++ b/conan/tools/microsoft/layout.py @@ -15,13 +15,14 @@ def vs_layout(conanfile): if not arch: raise ConanException("The 'vs_layout' doesn't " "work with the arch '{}'".format(conanfile.settings.arch)) - base = os.path.join(arch, str(conanfile.settings.build_type)) + bindirs = os.path.join(arch, str(conanfile.settings.build_type)) else: - base = str(conanfile.settings.build_type) + bindirs = str(conanfile.settings.build_type) - conanfile.folders.build = "." - conanfile.folders.generators = "conan" - conanfile.folders.source = "." - conanfile.cpp.build.libdirs = [base] - conanfile.cpp.build.bindirs = [base] + subproject = conanfile.folders.subproject + conanfile.folders.build = subproject or "." + conanfile.folders.generators = os.path.join(subproject, "conan") if subproject else "conan" + conanfile.folders.source = subproject or "." + conanfile.cpp.build.libdirs = [bindirs] + conanfile.cpp.build.bindirs = [bindirs] conanfile.cpp.source.includedirs = ["include"] diff --git a/conans/model/layout.py b/conans/model/layout.py index cce15ddd59f..d3c4828cb65 100644 --- a/conans/model/layout.py +++ b/conans/model/layout.py @@ -31,6 +31,10 @@ def __init__(self): # Relative location of the project root, if the conanfile is not in that project root, but # in a subfolder: e.g: If the conanfile is in a subfolder then self.root = ".." self.root = None + # The relative location with respect to the project root of the subproject containing the + # conanfile.py, that makes most of the output folders defined in layouts (cmake_layout, etc) + # start from the subproject again + self.subproject = None def __repr__(self): return str(self.__dict__) diff --git a/conans/test/functional/layout/test_in_subfolder.py b/conans/test/functional/layout/test_in_subfolder.py index 33925fa67b6..af3f58293d9 100644 --- a/conans/test/functional/layout/test_in_subfolder.py +++ b/conans/test/functional/layout/test_in_subfolder.py @@ -1,5 +1,9 @@ import textwrap +import pytest + +from conans.test.assets.cmake import gen_cmakelists +from conans.test.assets.sources import gen_function_cpp from conans.test.utils.tools import TestClient @@ -54,3 +58,113 @@ def build(self): c.run("build conan") assert "conanfile.py (pkg/0.1): MYCMAKE-BUILD: mycmake!" in c.out assert c.load("build/mylib.a") == "mylib" + + +def test_exports_sources_common_code(): + """ very similar to the above, but intended for a multi-package project sharing some + common code + """ + c = TestClient() + conanfile = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.files import load, copy, save + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + + def layout(self): + self.folders.root = ".." + self.folders.source = "pkg" + self.folders.build = "build" + + def export_sources(self): + source_folder = os.path.join(self.recipe_folder, "..") + copy(self, "*.txt", source_folder, self.export_sources_folder) + copy(self, "*.cmake", source_folder, self.export_sources_folder) + + def source(self): + cmake = load(self, "CMakeLists.txt") + self.output.info("MYCMAKE-SRC: {}".format(cmake)) + + def build(self): + path = os.path.join(self.source_folder, "CMakeLists.txt") + cmake = load(self, path) + self.output.info("MYCMAKE-BUILD: {}".format(cmake)) + + path = os.path.join(self.export_sources_folder, "common", "myutils.cmake") + cmake = load(self, path) + self.output.info("MYUTILS-BUILD: {}".format(cmake)) + """) + c.save({"pkg/conanfile.py": conanfile, + "pkg/CMakeLists.txt": "mycmake!", + "common/myutils.cmake": "myutils!"}) + c.run("create pkg") + assert "pkg/0.1: MYCMAKE-SRC: mycmake!" in c.out + assert "pkg/0.1: MYCMAKE-BUILD: mycmake!" in c.out + assert "pkg/0.1: MYUTILS-BUILD: myutils!" in c.out + + # Local flow + c.run("install pkg") + # SOURCE NOT CALLED! It doesnt make sense (will fail due to local exports) + c.run("build pkg") + assert "conanfile.py (pkg/0.1): MYCMAKE-BUILD: mycmake!" in c.out + assert "conanfile.py (pkg/0.1): MYUTILS-BUILD: myutils!" in c.out + + +@pytest.mark.tool_cmake +def test_exports_sources_common_code_layout(): + """ Equal to the previous test, but actually building and using cmake_layout + """ + c = TestClient() + conanfile = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import cmake_layout, CMake + from conan.tools.files import load, copy, save + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain" + + def layout(self): + self.folders.root = ".." + self.folders.subproject = "pkg" + cmake_layout(self) + + def export_sources(self): + source_folder = os.path.join(self.recipe_folder, "..") + copy(self, "*", source_folder, self.export_sources_folder) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + self.run(os.path.join(self.cpp.build.bindirs[0], "myapp")) + """) + cmake_include = "include(${CMAKE_CURRENT_LIST_DIR}/../common/myutils.cmake)" + c.save({"pkg/conanfile.py": conanfile, + "pkg/app.cpp": gen_function_cpp(name="main", includes=["../common/myheader"], + preprocessor=["MYDEFINE"]), + "pkg/CMakeLists.txt": gen_cmakelists(appsources=["app.cpp"], + custom_content=cmake_include), + "common/myutils.cmake": 'message(STATUS "MYUTILS.CMAKE!")', + "common/myheader.h": '#define MYDEFINE "MYDEFINEVALUE"'}) + c.run("create pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Release!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out + + # Local flow + c.run("install pkg") + c.run("build pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Release!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out + + c.run("install pkg -s build_type=Debug") + c.run("build pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Debug!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out diff --git a/conans/test/integration/layout/__init__.py b/conans/test/integration/layout/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/conans/test/integration/toolchains/gnu/test_basic_layout.py b/conans/test/integration/toolchains/gnu/test_basic_layout.py new file mode 100644 index 00000000000..19cd9ebb67e --- /dev/null +++ b/conans/test/integration/toolchains/gnu/test_basic_layout.py @@ -0,0 +1,25 @@ +import os +import platform +import textwrap + +from conans.test.utils.tools import TestClient + + +def test_basic_layout_subproject(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.layout import basic_layout + class Pkg(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "AutotoolsToolchain" + def layout(self): + self.folders.root = ".." + self.folders.subproject = "pkg" + basic_layout(self) + """) + c.save({"pkg/conanfile.py": conanfile}) + c.run("install pkg") + ext = "sh" if platform.system() != "Windows" else "bat" + assert os.path.isfile(os.path.join(c.current_folder, "pkg", "build-release", "conan", + "conanautotoolstoolchain.{}".format(ext))) diff --git a/conans/test/integration/toolchains/microsoft/test_vs_layout.py b/conans/test/integration/toolchains/microsoft/test_vs_layout.py new file mode 100644 index 00000000000..5a446da73a2 --- /dev/null +++ b/conans/test/integration/toolchains/microsoft/test_vs_layout.py @@ -0,0 +1,22 @@ +import os +import textwrap + +from conans.test.utils.tools import TestClient + + +def test_vs_layout_subproject(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.microsoft import vs_layout + class Pkg(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "MSBuildToolchain" + def layout(self): + self.folders.root = ".." + self.folders.subproject = "pkg" + vs_layout(self) + """) + c.save({"pkg/conanfile.py": conanfile}) + c.run("install pkg") + assert os.path.isfile(os.path.join(c.current_folder, "pkg", "conan", "conantoolchain.props"))