Package layout
Warning
This is an experimental feature subject to breaking changes in future releases.
Available since: 1.37.0
You can declare a layout()
method in the recipe to describe the package contents,
not only the final package in the cache but also the package while developing.
As the package will have the same structure in the cache and in our local directory, the recipe development becomes easier.
In the layout()
method you can adjust 3 different things:
self.folders
: Specify the location of several things, like the sources, the build folder or even the folder where the generators files (e.g the xx-config.cmake files from theCMakeDeps
) will be created.
self.patterns
: Describe the file patterns of your source and build folders. It will ease the process of packaging the files in thepackage()
method.
self.cpp
: The same you could adjust theself.cpp_info
in the package_info() for the package in the cache, you can do the same for the source and build folders while developing the package. This feature enables an easier way to use editable packages.
self.folders
self.folders.source: To specify a folder where your sources are.
self.folders.build: To specify a subfolder where the files from the build are (or will be).
self.folders.generators: To specify a subfolder where to write the files from the generators and the toolchains.
self.folders.imports: To specify a subfolder where to write the files copied when using the
imports(self)
method in aconanfile.py
.self.folders.package: To specify a subfolder where to write the package files when running the conan package command.
Check the complete reference of the folders attribute.
In the following example we are declaring a layout that follows the standard CLion one, where the build directory is cmake-build-release
or cmake-build-debug
depending on the declared build_type
setting. The sources of the project are in the src
folder.
Also the generators folders inside the build folder is quite convenient to include the conan_toolchain.cmake
file when using the CMakeDeps generator because it will be always in the same relative path to the build folder.
<my_project_folder>
| - conanfile.py
| - src
| - CMakeLists.txt
| - hello.cpp
| - hello.h
import os
from conans import ConanFile, CMake
class Pkg(ConanFile):
settings = "os", "build_type", "arch"
requires = "zlib/1.2.11"
generators = "CMakeDeps", "CMakeToolchain"
exports_sources = "src*"
def layout(self):
self.folders.build = "cmake-build-{}".format(str(self.settings.build_type).lower())
self.folders.generators = os.path.join(self.folders.build, "generators")
self.folders.imports = self.folders.build
self.folders.source = "src"
def source(self):
# In the source method, the current directory == self.source_folder
assert self.source_folder == os.getcwd()
def build(self):
# We are at a folder like "myproject/cmake-build-debug"
cmake = CMake(self)
cmake.configure()
cmake.build()
Given the previous example we can run the conan local methods without taking much care of the directories where the files are or the build files should be:
# This will write the toolchains and generator files from the dependencies to the ``cmake-build-debug/generators``
$ conan install . -if=my_install -s build_type=Debug
# In case we needed it, this will fetch the sources to the ./src folder.
$ conan source . -if=my_install
# This will build the project using the declared source folder and ``cmake-build-debug`` as the build folder
$ conan build . -if=my_install
# This will import, if declared imports(self) method, the files to the ``cmake-build-debug`` folder
$ conan imports . -if=my_install
Of course we could open the Clion IDE and build from there and the artifacts will be created at the same cmake-build-debug
folder.
Note
Maybe you are wondering why the install folder is not parametrized and has to be specified with the -if
argument.
Currently, Conan generates several files like the graph_info.json
and the conanbuildinfo.txt
that
are read to restore the configuration saved (settings, options, etc) to be applied in the local commands.
That configuration is needed before running the layout()
method because the folders might depend on the settings
like in the previous example. It is a kind of a chicken-egg issue. In Conan 2.0, likely, the
configuration won’t be stored, and the local methods like conan build . will compute the graph
from arguments (–profile, -s, -o…) and won’t need the --if
argument anymore, being always trivial to run.
self.cpp
The layout()
method allows to declare cpp_info
objects not only for the final package (like the classic approach with
the self.cpp_info
in the package_info(self)
method) but for the self.source_folder
and self.build_folder
.
This is useful when a package is in editable mode to automatically propagate to the consumers
all the needed information (library names, include directories…) but pointing to the local project directories while developing,
whether you are calling directly your build-system, using an IDE or executing the conan build command to build
your code.
Example:
from conans import ConanFile
class Pkg(ConanFile):
def layout(self):
self.cpp.source.includedirs = ["include"]
self.cpp.build.libdirs = ["."]
self.cpp.build.libs = ["mylib"]
self.cpp.build.includedirs = ["gen_include"]
self.cpp.package.libs = ["mylib"]
The fields of the cpp_info objects at self.info.build
and self.info.source
are the same described here.
Components are also supported.
Note
You can still use the package_info(self)
method. The received self.cpp_info object will be populated with the information explicitly declared
in the self.cpp.package
object, so you can complete it or modify it later.
Once you have your self.cpp.source
and self.cpp.build
objects declared you can put the package in
editable mode and keep working on the code development with your IDE. Other packages
depending on this one, will locate the libraries being developed instead of the Conan package in the cache.
$ conan editable add . hello/1.0
self.patterns
You can fill the self.patterns.source
and self.patterns.build
objects describing the patterns of the files that are at the self.folders.source
and self.folders.build
to automate the package(self)
method with the LayoutPackager() tool (see the example below).
The defaults are the following but you can customize anything based on the configuration (self.settings
, self.options
…):
self.patterns.source.include = ["*.h", "*.hpp", "*.hxx"]
self.patterns.source.lib = []
self.patterns.source.bin = []
self.patterns.build.include = ["*.h", "*.hpp", "*.hxx"]
self.patterns.build.lib = ["*.so", "*.so.*", "*.a", "*.lib", "*.dylib"]
self.patterns.build.bin = ["*.exe", "*.dll"]
These are all the fields that can be adjusted, both in self.patterns.source
and self.patterns.build
:
NAME |
DESCRIPTION (xxx can be either |
---|---|
include |
Patterns of the files from the folders: |
lib |
Patterns of the files from the folders: |
bin |
Patterns of the files from the folders: |
src |
Patterns of the files from the folders: |
build |
Patterns of the files from the folders: |
res |
Patterns of the files from the folders: |
framework |
Patterns of the files from the folders: |
Example: Everything together
Let’s see how we can use the layout()
method to both write simpler recipes, improve the local methods and the integration
with the IDE and develop the package as an editable package.
This is the project structure:
<project_folder>
| - CMakeLists.txt
| - hello.cpp
| - include
| - hello.h
| - res
| - myasset.jpg
We want to use CLion to build the project so we open the project (using both Release and Debug configurations). After building the project we have this layout:
<project_folder>
| - cmake-build-debug
| - CMakeFiles
| - ... other CMake stuff...
| - libhello.a
| - gen.h
| - cmake-build-release
| - CMakeFiles
| - ... other CMake stuff...
| - libhello.a
| - gen.h
| - CMakeLists.txt
| - hello.cpp
| - include
| - hello.h
| - res
| - myasset.jpeg
We can write a layout()
method describing it:
from conans import ConanFile
from conan.tools.layout import LayoutPackager
class Pkg(ConanFile):
def layout(self):
# ###### FOLDERS
# The sources can be found in the root dir
self.folders.source = "."
# The build folder is created with the CLion way
self.folders.build = "cmake-build-{}".format(str(self.settings.build_type).lower())
# We want to have the toolchains in the build folder so we can always pass
# `-DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake` to CMake
self.folders.generators = os.path.join(self.folders.build, "generators")
# In case we use "conan package" we declare an output directory
self.folders.package = "package-{}".format(str(self.settings.build_type).lower())
# ###### INFOS
self.cpp.source.includedirs = ["include"] # Relative to ["."] (self.folders.source)
self.cpp.build.libdirs = ["."] # Relative to (self.folders.build)
self.cpp.build.libs = ["hello"]
self.cpp.build.includedirs = ["."] # Relative to (self.folders.build)
self.cpp.package.libs = ["hello"]
# ###### PATTERNS
self.patterns.source.res = ["*.jpeg"] # To package automatically the myasset.jpeg
def package(self):
LayoutPackager(self).package()
There is no need to declare the
package_info(self)
method, we declared the needed information atself.cpp.package
.The
package(self)
method is quite simple using theLayoutPackager(self).package()
We can easily put the package in editable mode and keep using the CLion IDE to build the libraries:
$ conan editable add . hello/1.0
The packages requiring “hello/1.0” will find the headers and libraries in the right CLion output directories automatically.
If we want to verify the that the Conan recipe is totally correct we can use the Conan local methods always with the same syntax:
$ conan install . -if=my_install $ conan imports . -if=my_install $ conan build . -if=my_install $ conan package . -if=my_install
The conan commands will follow the same directory layout while building, and the
conan package
command will create an additionalpackage-release
folder with the packaged artifacts.