Conan Package Manager
What is it
Conan is a package and dependency manager for C and C++ that is designed to be build system agnostic. It can work with various build systems such as CMake, Meson, and Autotools, among others. Conan does not act as a build system itself but rather as a tool that manages dependencies and integrates with existing build systems.
It also allows for the creation of packages that can be used across different projects, supporting multiple architectures, build settings, options, and versions of dependencies. It orchestrates the build system's on your behalf so that the code and all of its third-party libraries are built with matching compilers, flags and options.
Does it replace Yocto?
Conan and Yocto tackle different layers of the stack. Conan is a dependency manager that fetches or builds individual C/C++ libraries, wires them into your build system, and stops once the compiler starts. Yocto, by contrast, it assembles an entire distribution—from cross-toolchain and kernel to root-filesystem image and package feeds. In practice you’d use Yocto to create the OS that boots on your board, then let Conan live inside that OS to keep your application-level libraries consistent and reproducible. One curates the operating system, the other curates the code you run on top of it.
Installing and building a package
Installation is pretty straightforward and done through pip:
pip install conan
Then we need to create the tool-chain:
conan profile detect
This creates a default profile that captures your host compiler, architecture, and standard library settings, and will be used by all packages meant to run natively.
A dummy package creation and installation looks as follows:
conan new hello/0.1 --template=cmake_lib cd hello conan create . --profile=default
Conan pulls any dependencies, builds the sample library with CMake, and stores the resulting package in the local cache.
How to create a package
All Conan packages are created through a conanfile.py such as the following:
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout
class MyLibConan(ConanFile):
name = "mylib"
version = "1.0"
settings = "os", "arch", "compiler", "build_type"
# 1-line way to publish the exact sources that live beside this recipe
exports_sources = "CMakeLists.txt", "src/*", "include/*"
def requirements(self):
self.requires("fmt/10.2.1")
self.requires("spdlog/1.13.0")
def layout(self):
cmake_layout(self)
def generate(self):
pass # CMakeToolchain + dependencies auto-generated
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 = ["mylib"]
This package tells Conan how to build the project, CMake in this case. Each dependency needs its own conanfile.py and must be either in your local cache or on a remote such as Conan Center (or a private server). Those remotes can host ready-made binaries or source code that Conan will rebuild if your build flags change, for example during cross-compilation. Note that a dependency’s build system doesn’t have to match yours: your CMake-based project can link just fine against libraries built with Autotools, Meson, or anything else, it will create the necessary files for it to link.
The build looks as follows:
conan install . \
--profile=default \
--output-folder=build \
--build=missing
conan build . --output-folder=build
How to cross-compile
The default build in your machine will be performed against your system's settings. But for cross compilation we want to build using the target system's flags.
For Jetson it can look as follows, the profile declares the new arch and other flags and should be located in ~/.conan2/profiles/:
[settings] os=Linux arch=armv8 compiler=gcc compiler.version=10 compiler.libcxx=libstdc++11 compiler.cppstd=17 build_type=Debug [tool_requires] l4t-toolchain/1.0
It also has a tool requirement, in this case this is a custom package that will take charge of downloading the Jetson toolchain and declare the necessary flags:
import os
import tarfile
from conan import ConanFile
from conan.tools.files import download, mkdir, copy
class L4TToolchain(ConanFile):
name = "l4t-toolchain"
version = "1.0"
package_type = "application"
settings = "os", "arch"
def build(self):
url = "https://developer.nvidia.com/embedded/jetson-linux/bootlin-toolchain-gcc-93"
archive_name = "bootlin-toolchain-gcc-93"
archive_path = os.path.join(self.build_folder, archive_name)
toolchain_dir = os.path.join(self.build_folder, "toolchain")
if not os.path.exists(toolchain_dir):
mkdir(self, toolchain_dir)
self.output.info(f"Downloading toolchain from {url}")
download(self, url, archive_path)
self.output.info(f"Extracting to {toolchain_dir}")
with tarfile.open(archive_path) as tf:
tf.extractall(path=toolchain_dir)
def package(self):
toolchain_src = os.path.join(self.build_folder, "toolchain")
copy(self, "*", src=toolchain_src, dst=self.package_folder, keep_path=True)
def package_info(self):
bin_dir = os.path.join(self.package_folder, "bin")
cc = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-cc")
cxx = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-g++")
ld = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-ld")
self.env_info.PATH.append(bin_dir)
self.conf_info.define("tools.build:compiler_executables", {
"c": cc,
"cpp": cxx,
"ld": ld
})
def generate(self):
bin_dir = os.path.join(self.package_folder, "bin")
cc = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-cc")
cxx = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-g++")
ld = os.path.join(bin_dir, "aarch64-buildroot-linux-gnu-ld")
self.buildenv_info.define("CC", cc)
self.buildenv_info.define("CXX", cxx)
self.buildenv_info.define("LD", ld)
self.buildenv_info.append_path("PATH", bin_dir)
The build (run on the package we want to cross-compile not the toolchain) is similar to the native build but we use the new profile:
conan install . \
--profile=l4t_toolchain \ # This should match how we name the profile
--output-folder=build \
--build=missing
conan build . --output-folder=build