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 more detail.
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 repository in GitHub:
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/developing_packages/package_layout
Note
We use CMake presets in this example. This requires CMake >= 3.23 because the “include” from CMakeUserPresets.json
to 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.
say/conanfile.py
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.folders
and self.cpp
. Let’s explain what these values do.
self.folders
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
package:
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
CMakeLists.txt
in the.
folder,self.folders.source
is set to.
.We set
self.folders.build
to be ./build/Release or ./build/Debug depending on thebuild_type
setting. These are the folders where we want the built binaries to be located.The
self.folders.generators
folder is the location we set for all the files created by the Conan generators. In this case, all the files generated by theCMakeToolchain
generator will be stored there.
Note
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.
self.cpp
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.
cpp.package
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 thesay
library so that consumers know that they should link with it. This is equivalent to declaringself.cpp_info.libs
in thepackage_info()
method.self.cpp.package.libdirs
: we add thelib
folder so that consumers know that they should search there for the libraries. This is equivalent to declaringself.cpp_info.libdirs
in thepackage_info()
method. Note that the default value forlibdirs
in both thecpp_info
andcpp.package
is["lib"]
so we could have omitted that declaration.self.cpp.package.includedirs
: we add theinclude
folder so that consumers know that they should search there for the library headers. This is equivalent to declaringself.cpp_info.includedirs
in thepackage_info()
method. Note that the default value forincludedirs
in both thecpp_info
andcpp.package
is["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 the say
package:
$ 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 these:
Tip
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 the 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 cpp.package
:
$ 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.source
and 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:
self.cpp.source.includedirs
set to["include"]
. This location is relative to theself.folders.source
that we defined to.
. In the case of editable packages, this location will be the local folder where we have our project.self.cpp.build.libdirs
set to["."]
. This location is relative to theself.folders.build
that we defined to ./build/<build_type>. In the case of editable packages, this location will point to <local_folder>/build/<build_type>.
Note that other cpp.source
and cpp.build
definitions are also possible, with different meanings and purposes, for example:
self.cpp.source.libdirs
andself.cpp.source.libs
could be used if we had pre-compiled libraries in the source repo, committed to git, for example. They are not a product of the build, but rather part of the sources.self.cpp.build.includedirs
could be use for folders containing headers generated at build time, as it usually happens by some code generators that are fired by the build before starting to compile the project.
To check how this information affects consumers, we are going to first put the say
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 self.folders
:
.
├── 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 hello
consumer project, it will search for all the headers and libraries of say
in the folders defined by cpp.source
and cpp.build
:
$ 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
Note
Please, note that we did not define self.cpp.build.libs = ["say"]
. This is because the information set in self.cpp.source
and 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 cpp.source
.
See also
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