Understanding the Conan Package layout¶
In the previous section, we introduced the concept of editable packages and mentioned
that the reason they work out of the box when put in editable mode is due to the current
definition of the information in the
layout() method. Let’s examine this feature in
In this tutorial, we will continue working with the
say/1.0 package and the
hello/1.0 consumer used in the editable packages tutorial.
Please, first of all, clone the sources to recreate this project. You can find them in the examples2.0 repository in GitHub:
$ git clone https://github.com/conan-io/examples2.git $ cd examples2/tutorial/developing_packages/package_layout
We use CMake presets in this example. This requires CMake >= 3.23 because the
CMakePresets.json is only supported
since that version. If you prefer not to use presets you can use something like:
cmake <path> -G <CMake generator> -DCMAKE_TOOLCHAIN_FILE=<path to conan_toolchain.cmake> -DCMAKE_BUILD_TYPE=Release
Conan will show the exact CMake command everytime you run
conan install in case
you can’t use the presets feature.
As you can see, the main folder structure is the same:
. ├── hello │ ├── CMakeLists.txt │ ├── conanfile.py │ └── src │ └── hello.cpp └── say ├── CMakeLists.txt ├── conanfile.py ├── include │ └── say.h └── src └── say.cpp
The main difference here is that we are not using the predefined
cmake_layout() in the
say/1.0 ConanFile, but instead, we are
declaring our own custom layout. Let’s see how we describe the information in the
layout() method so that it works both when we create the package in the Conan local
cache and also when the package is in editable mode.
import os from conan import ConanFile from conan.tools.cmake import CMake class SayConan(ConanFile): name = "say" version = "1.0" exports_sources = "CMakeLists.txt", "src/*", "include/*" ... def layout(self): ## define project folder structure self.folders.source = "." self.folders.build = os.path.join("build", str(self.settings.build_type)) self.folders.generators = os.path.join(self.folders.build, "generators") ## cpp.package information is for consumers to find the package contents in the Conan cache self.cpp.package.libs = ["say"] self.cpp.package.includedirs = ["include"] # includedirs is already set to 'include' by # default, but declared for completion self.cpp.package.libdirs = ["lib"] # libdirs is already set to 'lib' by # default, but declared for completion ## cpp.source and cpp.build information is specifically designed for editable packages: # this information is relative to the source folder that is '.' self.cpp.source.includedirs = ["include"] # maps to ./include # this information is relative to the build folder that is './build/<build_type>', so it will self.cpp.build.libdirs = ["."] # map to ./build/<build_type> for libdirs def build(self): cmake = CMake(self) cmake.configure() cmake.build()
Let’s review the
layout() method. You can see that we are setting values for
self.cpp. Let’s explain what these values do.
Defines the structure of the
say project for the source code and the folders where the
files generated by Conan and the built artifacts will be located. This structure is
independent of whether the package is in editable mode or exported and built in the Conan
local cache. Let’s define the folder structure for the
say ├── CMakeLists.txt ├── conanfile.py ├── include │ └── say.h ├── src │ └── say.cpp └── build ├── Debug --> Built artifacts for Debug │ └── generators --> Conan generated files for Debug config └── Release --> Built artifacts for Release └── generators --> Conan generated files for Release config
- As we have our
self.folders.sourceis set to
- We set
self.folders.buildto be ./build/Release or ./build/Debug depending on the
build_typesetting. These are the folders where we want the built binaries to be located.
self.folders.generatorsfolder is the location we set for all the files created by the Conan generators. In this case, all the files generated by the
CMakeToolchaingenerator will be stored there.
Please note that the values above are for a single-configuration CMake generator. To support multi-configuration generators, such as Visual Studio, you should make some changes to this layout. For a complete layout that supports both single-config and multi-config, please check the cmake_layout() in the Conan documentation.
This attribute is used to define where consumers will find the package contents (headers files, libraries, etc.) depending on whether the package is in editable mode or not.
First, we set the information for cpp.package. This defines the contents of the package and its location relative to the folder where the package is stored in the local cache. Please note that defining this information is equivalent to defining self.cpp_info in the package_info() method. This is the information we defined:
self.cpp.package.libs: we add the
saylibrary so that consumers know that they should link with it. This is equivalent to declaring
self.cpp.package.libdirs: we add the
libfolder so that consumers know that they should search there for the libraries. This is equivalent to declaring
package_info()method. Note that the default value for
libdirsin both the
["lib"]so we could have omitted that declaration.
self.cpp.package.includedirs: we add the
includefolder so that consumers know that they should search there for the library headers. This is equivalent to declaring
package_info()method. Note that the default value for
includedirsin both the
["include"]so we could have omitted that declaration.
To check how this information affects consumers we are going to do first do a
conan create on
$ cd say $ conan create . -s build_type=Release
When we call
conan create, Conan moves the recipe and sources declared in the recipe
to be exported to the local Cache to a recipe folder and after that, it will create a
separate package folder to build the binaries and store the actual package contents. If
you check in the
[YOUR_CONAN_HOME]/p folder, you will find two new folders similar to
You could get the exact locations for this folders using the conan cache command or checking the output of the conan create command.
<YOUR_CONAN_HOME>/p ├── sayb3ea744527a91 --> folder for sources │ └── ... │ └── say830097e941e10 --> folder for building and storing the package binaries ├── b │ ├── build │ │ └── Release │ ├── include │ │ └── say.h │ └── src │ ├── hello.cpp │ └── say.cpp └── p ├── include --> defined in cpp.package.includedirs │ └── say.h └── lib --> defined in cpp.package.libdirs └── libsay.a --> defined in self.cpp.package.libs
You can identify there the structure we defined in the
layout() method. If you build
hello consumer project now, it will search for all the headers and libraries of
say in that folder inside the local Cache in the locations defined by
$ cd ../hello $ conan install . -s build_type=Release # Linux, MacOS $ cmake --preset conan-release --log-level=VERBOSE # Windows $ cmake --preset conan-default --log-level=VERBOSE ... -- Conan: Target declared 'say::say' -- Conan: Library say found <YOUR_CONAN_HOME>p/say8938ceae216fc/p/lib/libsay.a -- Created target CONAN_LIB::say_say_RELEASE STATIC IMPORTED -- Conan: Found: <YOUR_CONAN_HOME>p/p/say8938ceae216fc/p/lib/libsay.a -- Configuring done ... $ cmake --build --preset conan-release [ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o [100%] Linking CXX executable hello [100%] Built target hello
cpp.source and cpp.build¶
We also defined
cpp.build attributes in our recipe. These are only
used when the package is in editable mode and point to the locations that consumers will
use to find headers and binaries. We defined:
["include"]. This location is relative to the
self.folders.sourcethat we defined to
.. In the case of editable packages, this location will be the local folder where we have our project.
["."]. This location is relative to the
self.folders.buildthat we defined to ./build/<build_type>. In the case of editable packages, this location will point to <local_folder>/build/<build_type>.
To check how this information affects consumers, we are going to first put the
package in editable mode and build it locally.
$ cd ../say $ conan editable add . --name=say --version=1.0 $ conan install . -s build_type=Release $ cmake --preset conan-release $ cmake --build --preset conan-release
You can check the contents of the say project’s folder now, you can see that the output
folders match the ones we defined with
. ├── CMakeLists.txt ├── CMakeUserPresets.json ├── build │ └── Release --> defined in cpp.build.libdirs │ ├── ... │ ├── generators │ │ ├── CMakePresets.json │ │ ├── ... │ │ └── deactivate_conanrun.sh │ └── libsay.a --> no need to define ├── conanfile.py ├── include --> defined in cpp.source.includedirs │ └── say.h └── src ├── hello.cpp └── say.cpp
Now that we have the
say package in editable mode, if we build the
project, it will search for all the headers and libraries of
say in the folders
$ cd ../hello $ conan install . -s build_type=Release # Linux, MacOS $ cmake --preset conan-release --log-level=VERBOSE # Windows $ cmake --preset conan-default --log-level=VERBOSE ... -- Conan: Target declared 'say::say' -- Conan: Library say found <local_folder>/examples2/tutorial/developing_packages/package_layout/say/build/Release/libsay.a -- Conan: Found: <local_folder>/examples2/tutorial/developing_packages/package_layout/say/build/Release/libsay.a -- Configuring done ... $ cmake --build --preset conan-release [ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o [100%] Linking CXX executable hello [100%] Built target hello $ conan editable remove --refs=say/1.0
Please, note that we did not define
self.cpp.build.libs = ["say"]. This is because
the information set in
self.cpp.build will be merged with
the information set in
self.cpp.package so that you only have to define things
that change for the editable package. For the same reason, you could also omit setting
self.cpp.source.includedirs = ["include"] but we left it there to show the use of
- Define the layout() when you package third-party libraries
- Define the layout() when you have the conanfile in a subfolder
- Define the layout() when you want to handle multiple subprojects