Create your first Conan package
In previous sections, we consumed Conan packages (like the Zlib one), first using a conanfile.txt and then with a conanfile.py. But a conanfile.py recipe file is not only meant to consume other packages, it can be used to create your own packages as well. In this section, we explain how to create a simple Conan package with a conanfile.py recipe and how to use Conan commands to build those packages from sources.
Important
This is a tutorial section. You are encouraged to execute these commands. For this concrete example, you will need CMake installed in your path. It is not strictly required by Conan to create packages, you can use other build systems (such as VS, Meson, Autotools, and even your own) to do that, without any dependency on CMake.
Use the conan new command to create a “Hello World” C++ library example project:
$ conan new cmake_lib -d name=hello -d version=1.0
This will create a Conan package project with the following structure.
.
├── CMakeLists.txt
├── conanfile.py
├── include
│ └── hello.h
├── src
│ └── hello.cpp
└── test_package
├── CMakeLists.txt
├── conanfile.py
└── src
└── example.cpp
The generated files are:
conanfile.py: On the root folder, there is a conanfile.py which is the main recipe file, responsible for defining how the package is built and consumed.
CMakeLists.txt: A simple generic CMakeLists.txt, with nothing specific about Conan in it.
src and include folders: the folders that contains the simple C++ “hello” library.
test_package folder: contains an example application that will require and link with the created package. It is not mandatory, but it is useful to check that our package is correctly created.
Let’s have a look at the package recipe conanfile.py:
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
class helloRecipe(ConanFile):
name = "hello"
version = "1.0"
# Optional metadata
license = "<Put the package license here>"
author = "<Put your name here> <And your email here>"
url = "<Package recipe repository url here, for issues about the package>"
description = "<Description of hello package here>"
topics = ("<Put some tag here>", "<here>", "<and here>")
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
# Sources are located in the same place as this recipe, copy them to the recipe
exports_sources = "CMakeLists.txt", "src/*", "include/*"
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def layout(self):
cmake_layout(self)
def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def package(self):
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.libs = ["hello"]
Let’s explain the different sections of the recipe briefly:
First, you can see the name and version of the Conan package defined:
name
: a string, with a minimum of 2 and a maximum of 100 lowercase characters that defines the package name. It should start with alphanumeric or underscore and can contain alphanumeric, underscore, +, ., - characters.version
: It is a string, and can take any value, matching the same constraints as thename
attribute. In case the version follows semantic versioning in the formX.Y.Z-pre1+build2
, that value might be used for requiring this package through version ranges instead of exact versions.
Then you can see, some attributes defining metadata. These are optional but recommended and define things like a short description
for the package, the author
of the packaged library, the license
, the url
for the package repository, and the topics
that the package is related to.
After that, there is a section related with the binary configuration. This section defines the valid settings and options for the package. As we explained in the consuming packages section:
settings
are project-wide configuration that cannot be defaulted in recipes. Things like the operating system, compiler or build configuration that will be common to several Conan packagesoptions
are package-specific configuration and can be defaulted in recipes, in this case, we have the option of creating the package as a shared or static library, being static the default.
After that, the exports_sources
attribute is set to define which sources are part of the Conan package. These are the sources for the library you want to package. In this case the sources for our “hello” library.
Then, several methods are declared:
The
config_options()
method (together with theconfigure()
one) allows fine-tuning the binary configuration model, for example, in Windows, there is nofPIC
option, so it can be removed.The
layout()
method declares the locations where we expect to find the source files and destinations for the files generated during the build process. Example destination folders are those for the generated binaries and all the files that the Conan generators create in thegenerate()
method. In this case, as our project uses CMake as the build system, we call tocmake_layout()
. Calling this function will set the expected locations for a CMake project.The
generate()
method prepares the build of the package from source. In this case, it could be simplified to an attributegenerators = "CMakeToolchain"
, but it is left to show this important method. In this case, the execution ofCMakeToolchain
generate()
method will create a conan_toolchain.cmake file that translates the Conansettings
andoptions
to CMake syntax. TheCMakeDeps
generator is added for completitude, but it is not strictly necessary untilrequires
are added to the recipe.The
build()
method uses theCMake
wrapper to call CMake commands, it is a thin layer that will manage to pass in this case the-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
argument. It will configure the project and build it from source.The
package()
method copies artifacts (headers, libs) from the build folder to the final package folder. It can be done with bare “copy” commands, but in this case, it is leveraging the already existing CMake install functionality (if the CMakeLists.txt didn’t implement it, it is easy to write an equivalent using the copy() tool in thepackage()
method.Finally, the
package_info()
method defines that consumers must link with a “hello” library when using this package. Other information as include or lib paths can be defined as well. This information is used for files created by generators (asCMakeDeps
) to be used by consumers. This is generic information about the current package, and is available to the consumers irrespective of the build system they are using and irrespective of the build system we have used in thebuild()
method
The test_package folder is not critical now for understanding how packages are created. The important bits are:
test_package folder is different from unit or integration tests. These tests are “package” tests, and validate that the package is properly created and that the package consumers will be able to link against it and reuse it.
It is a small Conan project itself, it contains its
conanfile.py
, and its source code including build scripts, that depends on the package being created, and builds and executes a small application that requires the library in the package.It doesn’t belong in the package. It only exists in the source repository, not in the package.
Let’s build the package from sources with the current default configuration, and then let the test_package
folder test the package:
$ conan create .
======== Exporting recipe to the cache ========
hello/1.0: Exporting package recipe
...
hello/1.0: Exported: hello/1.0#dcbfe21e5250264b26595d151796be70 (2024-03-04 17:52:39 UTC)
======== Installing packages ========
-------- Installing package hello/1.0 (1 of 1) --------
hello/1.0: Building from source
hello/1.0: Calling build()
...
hello/1.0: Package '9bdee485ef71c14ac5f8a657202632bdb8b4482b' built
======== Testing the package: Building ========
...
[ 50%] Building CXX object CMakeFiles/example.dir/src/example.cpp.o
[100%] Linking CXX executable example
[100%] Built target example
======== Testing the package: Executing test ========
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release!
hello/1.0: __aarch64__ defined
hello/1.0: __cplusplus201703
hello/1.0: __GNUC__4
hello/1.0: __GNUC_MINOR__2
hello/1.0: __clang_major__15
hello/1.0: __apple_build_version__15000309
...
If “Hello world Release!” is displayed, it worked. This is what has happened:
The conanfile.py together with the contents of the src folder have been copied (exported, in Conan terms) to the local Conan cache.
A new build from source for the
hello/1.0
package starts, calling thegenerate()
,build()
andpackage()
methods. This creates the binary package in the Conan cache.Conan then moves to the test_package folder and executes a conan install + conan build +
test()
method, to check if the package was correctly created.
We can now validate that the recipe and the package binary are in the cache:
$ conan list hello
Local Cache
hello
hello/1.0
The conan create command receives the same parameters as conan install, so you can pass to it the same settings and options. If we execute the following lines, we will create new package binaries for Debug configuration or to build the hello library as shared:
$ conan create . -s build_type=Debug
...
hello/1.0: Hello World Debug!
$ conan create . -o hello/1.0:shared=True
...
hello/1.0: Hello World Release!
These new package binaries will be also stored in the Conan cache, ready to be used by any project in this computer. We can see them with:
# list all the binaries built for the hello/1.0 package in the cache
$ conan list "hello/1.0:*"
Local Cache
hello
hello/1.0
revisions
dcbfe21e5250264b26595d151796be70 (2024-05-10 09:40:15 UTC)
packages
2505f7ebb5a4cca156b2d6b8534f415a4a48b5c9
info
settings
arch: armv8
build_type: Release
compiler: apple-clang
compiler.cppstd: gnu17
compiler.libcxx: libc++
compiler.version: 15
os: Macos
options
shared: True
39f48664f195e0847f59889d8a4cdfc6bca84bf1
info
settings
arch: armv8
build_type: Release
compiler: apple-clang
compiler.cppstd: gnu17
compiler.libcxx: libc++
compiler.version: 15
os: Macos
options
fPIC: True
shared: False
814ddaac84bc84f3595aa076660133b88e49fb11
info
settings
arch: armv8
build_type: Debug
compiler: apple-clang
compiler.cppstd: gnu17
compiler.libcxx: libc++
compiler.version: 15
os: Macos
options
fPIC: True
shared: False
Now that we have created a simple Conan package, we will explain each of the methods of the Conanfile in more detail. You will learn how to modify those methods to achieve things like retrieving the sources from an external repository, adding dependencies to our package, customising our toolchain and much more.
A note about the Conan cache
When you did the conan create command, the build of your package did not take place in your local folder but in other folder inside the Conan cache. This cache is located in the user home folder under the .conan2
folder. Conan will use the ~/.conan2
folder to store the built packages and also different configuration files. You already used the conan list command to list the recipes and binaries stored in the local cache.
An important note: the Conan cache is private to the Conan client - modifying, adding, removing or changing files inside the Conan cache is undefined behaviour likely to cause breakages.
See also