Use Conan: An Open Source C/C++ Package Manager
Managing your C/C++ packages could be messy, but it can be significantly improved
In this article, I’ll go through the troubles of package management in C/C++ and how Conan can help you resolve these problems.
Start Simple
Let's start with a trivial task, taking values from the command line arguments and printing them to the standard output:
+root
- mainapp.c
- CMakeLists.txt
We use the getops function from the standard library to get the arguments -a
and -b
and their associated values, and then we print them.
Then we use CMake to compile the project into an executable called myproject
.
Let's configure this project:
cmake --build ./build --config Debug --target MyProject -j 10 --
And now let's run the application:
./build/MyProject -a foo -b bar
The output is:
a: foo
b: bar
It works, but ‘works’ isn’t always enough.
The Getops function uses global variables such as optarg
to store the arguments associated value, optopt
for the variables name, and optind
for the index.
This means that the function is not thread safe, and you can only parse a single input vector in a single thread.
If you have multiple input vectors, you need to parse these vectors sequentially, and to make things worse you have to reset the state of the parser every time.
Another well-known problem of getops is the error message that’s internally hidden which makes it difficult to redirect.
So, to fix this problem, I’ve decided to use parg, an open source C library to parse arguments with thread safety by allocating a struct and using it to monitor the state of the parser instead of global variables.
But How Am I Going To Consume This Code?
Option 1. Consume source code
We can use gits submodule tool to clone parg to our project directory and then to use cmake to compile and link it to our executable.
git submodules add https://github.com/jibsen/parg.git
This will create the file .gitsubmodules
with the following content:
[submodule "parg"]path = pargurl = https://github.com/jibsen/parg.git
Now we can fetch the repo with its source code:
git submodules init
We have all of parg
source code in the parg
directory.
Now we need to add this to our CMakeLists.txt
configurations:
We’ve added the directory and linked the library to our executable.
Now we can change our code to use the parg
library:
Now our code is thread safe, and we can easily parse multiple vectors of arguments if needed.
Let's compile:
cmake --build ./build --config Debug --target MyProject -j 10 --
Run:
./build/MyProject -a foo -b bar
The output is:
a: foo
b: bar
The main limitation of adding the source code is that we need to compile it every time we compile the executable and this can lead to prolonged compilation times.
Option 2 . Consume compiled library
In this option, we will compile parg
once, save the library, and then link the library directly without compiling it again.
Having our parg
project compiled will create the file libaprg.a
.
Now we can make some modifications to the CMakeLists and C code to make it work:
CMake:
And now the C code:
The only change to the C code is that now the header file is placed in the linked library.
In the CMakeLists file, we’ve removed the add_subdirectories
and instead, we’ve linked the compiled library from its path so that we are not going to compile it every time we compile the application.
Now let’s compile and run again.
cmake --build ./build --config Debug --target MyProject -j 10 --
Run:
./build/MyProject -a foo -b bar
The output is:
a: foo
b: bar
It works! it’s better, but it can get even better than that!
The problem with this approach is that it contaminates the working environment and when you have many dependencies, and your dependencies have second-tier or even third-tier dependencies, which becomes extremely cumbersome to manage.
Option 3. Use Conan for crying out loud!
Conan is an open source C\C++ package manager developed by JFrog.
It uses the JFrog artifactory to manage dependencies.
It is mostly used by its Software as a Service (SaaS) platform, but it can also be used as a local repository for internal company's needs.
As a prerequisite, you need Python installed.
First, we need to install Conan from pip using the following command:
pip install conan
Now we need to create a profile for our configurations:
conan profile new myprofile
The output will be:
Empty profile created: C:\Users\user\.conan\profiles\myprofile
Now we can go to the profile file and set the configurations as we need.
In my case, I used the following:
[env]CC=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/gcc.exeCXX=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/g++.exe[settings]os_build=Windowsarch_build=x86os=Windowsarch=x86compiler=gcccompiler.version=8compiler.libcxx=libstdc++11build_type=Release
This sets all the paths and variables needed to compile the project.
Having the profile set, now I’ll create conanfile.txt
in the project directory.
Looking at the parg repository in Conan center, I can find all the details I need.
[requires]
parg/1.0.2[generators]
cmake
Now my project is dependent on parg
and Conan knows this, so it should be provisioned for cmake.
Now I can run the Conan install command:
conan install . -pr=myprofile
Output:
Configuration:
[settings]
arch=x86
arch_build=x86
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=8
os=Windows
os_build=Windows
[options]
[build_requires]
[env]
CC=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/gcc.exe
CXX=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/g++.exe
conanfile.txt: Installing package
Requirements
parg/1.0.2 from 'conancenter' - Cache
Packages
parg/1.0.2:a955db98e980a5ab86ae50d6df8bfee361185c27 - CacheInstalling (downloading, building) binaries...
parg/1.0.2: Already installed!
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Aggregating env generators
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo
Cool!
Now let's add some changes to the CMakeLists.txt
file so it will take the library with these dependencies:
So we included the build info from the sources dir (it was created by the Conan install) and linked the library.
Now we can use the same C code we’ve used in the first example, as shown below:
The difference is that now we don’t pollute our code with git submodules and we won’t have to compile the code every time.
Likewise, we don’t need to manage dependencies and artifacts on our own anymore.
Now let's compile and run this code again:
cmake --build ./build --config Debug --target MyProject -j 10 --
Run:
./build/MyProject -a foo -b bar
The output is:
a: foo
b: bar
Sweet!