“Also, we will explain how to transition from the conanfile.txt file we used in the first example to a more powerful conanfile.py.”
—Conan Documentation --Release 2.0.17, Chapter 4.1
Using a conanfile.txt to build your projects using Conan it’s enough for simple cases, but if you need more flexibility you should use a conanfile.py file where you can use Python code to make things such as adding requirements dynamically, changing options depending on other options or setting options for your requirements.
conanfile.txt
[requires]
zlib/1.2.11
[tool_requires]
cmake/3.22.6
[generators]
CMakeDeps
CMakeToolchain
conanfile.py
from conan import ConanFile
class CompressorRecipe(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/1.2.11")
def build_requirements(self):
self.tool_requires("cmake/3.22.6")
This file is what is typically called a “Conan recipe”.
It can be used for consuming packages, like in this case, and also to create packages.
For our current case, it will define our requirements (both libraries and build tools) and logic to modify options and set how we want to consume those packages.
In the case of using this file to create packages, it can define (among other things)
how to download the package’s source code,
how to build the binaries from those sources,
how to package the binaries,
and information for future consumers on how to consume the package.
To create the Conan recipe we declared a new class that inherits from the ConanFile class.
This class has different class attributes and methods:
• settings this class attribute defines the project-wide variables, like the compiler, its version, or the OS itself that may change when we build our project.
This is related to how Conan manages binary compatibility as these values will affect the value of the package ID for Conan packages.
We will explain how Conan uses this value to manage binary compatibility later.
• generators this class attribute specifies which Conan generators will be run when we call the conan install command.
In this case, we added CMakeToolchain and CMakeDeps as in the conanfile.txt.
• requirements() in this method we use the self.requires() method to declare the zlib/1.2.11 dependency.
• build_requirements() in this method we use the self.tool_requires() method to declare the cmake/3.22.6 dependency.
command lines
for windows:
> conan install . --output-folder=build --build=missing
> cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
> cmake --build . --config Release
for linux:
$ conan install . --output-folder build --build=missing
$ cd build
$ source conanbuild.sh
Capturing current environment in deactivate_conanbuildenv-release-x86_64.sh
Configuring environment variables
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
$ cmake --build .
...
Building with CMake version: 3.22.6
...
[100%] Built target compressor
$ ./compressor
Uncompressed size is: 233
Compressed size is: 147
ZLIB VERSION: 1.2.11
$ source deactivate_conanbuild.sh
Use the layout() method
In the previous examples, every time we executed a conan install command, we had to use the –output-folder argument to define where we wanted to create the files that Conan generates.
There’s a neater way to decide where we want Conan to generate the files for the build system that will allow us to decide, for example, if we want different output folders depending on the type of CMake generator we are using.
You can define this directly in the conanfile.py inside the layout() method and make it work for every platform without adding more changes.
conanfile.py
import os
from conan import ConanFile
class CompressorRecipe(ConanFile):
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/1.2.11")
# Add base64 dependency only for Windows
if self.settings.os == "Windows":
self.requires("base64/0.4.0")
def build_requirements(self):
if self.settings.os != "Windows": # we need cmake 3.22.6 in other platforms
self.tool_requires("cmake/3.22.6")
def layout(self):
# We make the assumption that if the compiler is msvc the
# CMake generator is multi-config
multi = True if self.settings.get_safe("compiler") == "msvc" else False
if multi:
self.folders.generators = os.path.join("build", "generators")
self.folders.build = "build"
else:
self.folders.generators = os.path.join("build", str(self.settings.build_type), "generators")
self.folders.build = os.path.join("build", str(self.settings.build_type))
As you can see, we defined the self.folders.generators attribute in the layout() method.
This is the folder where all the auxiliary files generated by Conan (CMake toolchain and cmake dependencies files) will be placed.
Note that the definitions of the folders is different if it is a multi-config generator (like Visual Studio), or a single-config generator (like Unix Makefiles).
In the first case, the folder is the same irrespective of the build type, and the build system will manage the different build types inside that folder.
But single-config generators like Unix Makefiles, must use a different folder for each different configuration (as a different build_type Release/Debug).
In this case we added a simple logic to consider multi-config if the compiler name is msvc.
The following command line will work in this case:
> conan install . --build=missing
There’s no need to always write this logic in the conanfile.py.
There are some pre-defined layouts you can import and directly use in your recipe.
For example, for the CMake case, there’s a cmake_layout() already defined in Conan:
conanfile.py
from conan import ConanFile
from conan.tools.cmake import cmake_layout
class CompressorRecipe(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/1.2.11")
def build_requirements(self):
self.tool_requires("cmake/3.22.6")
def layout(self):
cmake_layout(self)
Use the validate() method to raise an error for non-supported configurations
...
from conan.errors import ConanInvalidConfiguration
class CompressorRecipe(ConanFile):
...
def validate(self):
if self.settings.os == "Macos" and self.settings.arch == "armv8":
raise ConanInvalidConfiguration("ARM v8 not supported in Macos")
Conditional requirements using a conanfile.py
from conan import ConanFile
class CompressorRecipe(ConanFile):
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain", "CMakeDeps"
def requirements(self):
self.requires("zlib/1.2.11")
# Add base64 dependency for Windows
if self.settings.os == "Windows":
self.requires("base64/0.4.0")
def build_requirements(self):
# Use the system's CMake for Windows
if self.settings.os != "Windows":
self.tool_requires("cmake/3.22.6")
References
Conan Documentation --Release 2.0.17