Declaring the layout when we have multiple subprojects

Please, first clone the sources to recreate this project. You can find them in the examples2 repository in GitHub:

  1. $ git clone https://github.com/conan-io/examples2.git
  2. $ cd examples2/examples/conanfile/layout/multiple_subprojects

Let’s say that we have a project that contains two subprojects: hello and bye, that need to access some information that is at their same level (sibling folders). Each subproject would be a Conan package. The structure could be something similar to this:

  1. .
  2. ├── bye
  3. ├── CMakeLists.txt
  4. ├── bye.cpp # contains an #include "../common/myheader.h"
  5. └── conanfile.py # contains include(../common/myutils.cmake)
  6. ├── common
  7. ├── myheader.h
  8. └── myutils.cmake
  9. └── hello
  10. ├── CMakeLists.txt # contains include(../common/myutils.cmake)
  11. ├── conanfile.py
  12. └── hello.cpp # contains an #include "../common/myheader.h"

Both hello and bye subprojects needs to use some of the files located inside the common folder (that might be used and shared by other subprojects too), and it references them by their relative location. Note that common is not intended to be a Conan package. It is just some common code that will be copied into the different subproject packages.

We can use the self.folders.root = ".." layout specifier to locate the root of the project, then use the self.folders.subproject = "subprojectfolder" to relocate back most of the layout to the current subproject folder, as it would be the one containing the build scripts, sources code, etc., so other helpers like cmake_layout() keep working. Let’s see how the conanfile.py of hello could look like:

./hello/conanfile.py

  1. import os
  2. from conan import ConanFile
  3. from conan.tools.cmake import cmake_layout, CMake
  4. from conan.tools.files import copy
  5. class hello(ConanFile):
  6. name = "hello"
  7. version = "1.0"
  8. settings = "os", "compiler", "build_type", "arch"
  9. generators = "CMakeToolchain"
  10. def layout(self):
  11. self.folders.root = ".."
  12. self.folders.subproject = "hello"
  13. cmake_layout(self)
  14. def export_sources(self):
  15. source_folder = os.path.join(self.recipe_folder, "..")
  16. copy(self, "hello/conanfile.py", source_folder, self.export_sources_folder)
  17. copy(self, "hello/CMakeLists.txt", source_folder, self.export_sources_folder)
  18. copy(self, "hello/hello.cpp", source_folder, self.export_sources_folder)
  19. copy(self, "common*", source_folder, self.export_sources_folder)
  20. def build(self):
  21. cmake = CMake(self)
  22. cmake.configure()
  23. cmake.build()
  24. self.run(os.path.join(self.cpp.build.bindirs[0], "hello"))

Let’s build hello and check that it’s building correctly, using the contents of the common folder.

  1. $ conan install hello
  2. $ conan build hello
  3. ...
  4. [100%] Built target hello
  5. conanfile.py (hello/1.0): RUN: ./hello
  6. hello WORLD

You can also run a conan create and check that it works fine too:

  1. $ conan create hello
  2. ...
  3. [100%] Built target hello
  4. conanfile.py (hello/1.0): RUN: ./hello
  5. hello WORLD

Note

Note the importance of the export_sources() method, which is able to maintain the same relative layout of the hello and common folders, both in the local developer flow in the current folder, but also when those sources are copied to the Conan cache, to be built there with conan create or conan install --build=hello. This is one of the design principles of the layout(), the relative location of things must be consistent in the user folder and in the cache.

See also