about::code - my personal blog march 2018
Easy cross-platform C++ with Hunter
The process of using an external C++ project in IDEs like Visual Studio can be very frustrating, as Boris Kolpackov illustrates in his talk at CppCon. Luckily, a few things have changed since then such that this process becomes very easy when using the right tools. In this short writeup I'll introduce the solution that worked best for me in that context and tell you how to set it up. Note that all aspects mentioned here are basically cross-platform but since users with an unix-like OS are already blessed with an idiomatic and powerful eco-system and toolchain, I chose to include additional comments for users who are using Windows and Visual Studio. Be sure to check out the detailed step-by-step instructions to get comfortable with CMake! I'll try to provide a short explanation of what problems CMake is trying to solve in the next paragraph. Feel free to skip it if you are already familiar with those concepts!
Every platform might have a different idiomatic toolchain that is used to finally produce a binary. Roughly speaking there are two groups: the command-line users who write custom makefiles and the ones that prefer an IDE which basically keeps track of the project's configuration and dependencies for them. The goal is the same in both cases - the binary can be (re)produced with minimal effort, e.g. by pressing a single button or triggering a build-script.
But since those workflows might differ greatly, there is the need for an additonal layer on top. Introducing: CMake which is basically used to generate the files needed for the respective toolchain. On Windows this might include generating a solution (.sln) file whereas on unixoide systems a makefile might be generated.
In theory this is pretty neat: we simply need to write a CMakeLists.txt file that describes how to build the project and let CMake generate the respective files for the toolchain that is actually used. We stay cross-platform and can share the software with as many people as possible.
But reality might sometimes look a little different (as described in the cppcon talk above), especially for inexperienced IDE users:
Clone a repository - build the library - set the respective include and library paths for our current project and build the application - just to realize it's the wrong library config.
Try to build the library with another configuration - which simply won't work - finally give up in frustration!
I've been there initially - it sucks. Introducing Hunter which is made to solve those issues. Before I'll get into how to set it up though, let me roughly summarize how it works:
Hunter is basically a framework written in CMake that knows how to include external packages correctly into a project. The core module HunterGate.cmake (written in CMake) injects Hunter in our CMake code and enables neat extensions like the connection to an endpoint that provides information about packages and where to find them. Hunterized packages are basically stable snapshots of CMake based projects that use Hunter to manage their dependecies and which can be build reliably on all supported platforms with all their dependencies.
Many packages are actively monitored such that the latest stable release is available as soon as possible (which generally means it builds reliably on all supported platforms).
When you come across a stable project that is newer than the version which is available with Hunter, be sure to submit a pull request. It is also worth pointing out
that any package with correct CMake code can generally be distributed without any changes which enables the distribution via Hunters' eco-system without additional effort. That especially
includes all projects without dependencies which do not even need to be hunterized!
There is much more to say about Hunter and CMake which would blow the scope of this tutorial. Luckily there is a load of documentation and detailed tutorials for both projects and Hunter's community (e.g. on Gitter) is very helpful. Since this is a beginner tutorial let's keep it short and jump right into how to set it up:
First thing you need to do is to create a CMakeLists.txt file (preferably in your project's root directory). Note that in theory the minimum required version of CMake is actually 3.2 but you might still encounter some issues. The official recommended version is currently 3.7, so the first line in the CMakeLists.txt should be:
cmake_minimum_required(VERSION 3.7)
The next step is to include Hunter. This is generally done by creating a seperate folder called cmake which contains the latest HunterGate.cmake file. Back in our CMakeLists.txt we can then add the next line:
include("cmake/HunterGate.cmake")
which allows us to use all the neat features Hunter does offer. Let's tell Hunter where to find the description of packages. The best practice is to choose a stable
release which enables reproducable builds. Those releases with Version X.Y.Z can generally be found at "
https://github.com/ruslo/hunter/archive/vX.Y.Z.tar.gz"
. For the current version this might look like this:
HunterGate(
URL "https://github.com/ruslo/hunter/archive/v0.20.28.tar.gz"
SHA1 "8fee57e4232e35e28a88f0beafb5f2a739a3fa3c"
)
Afterwards we can describe our project to CMake as we would normally do:
project(MyProject)
# ... ...
To include a hunterized package, we use the hunter_add_package( ... ) command. If we were to include googles benchmarking framework benchmark, we would write:
hunter_add_package( benchmark )
To tell CMake that benchmark is required to build we would use the following command:
find_package( benchmark CONFIG REQUIRED )
Last thing we have left to do is to tell CMake to link the libraries. The specific commands can be looked up in the respective package documentation. For benchmark we would write:
target_link_libraries( MyProject benchmark::benchmark )
Now we can simply use it in our project by writing:
#include <benchmark/benchmark.h>
Hunter will take care of building everything in the correct configuration. Additional dependencies can now simply be added by another hunter_add_package( ... ) command. Check out the list of available packages and feel free to submit a pull request if you want to see your or one of your favourite projects included.
When using Visual Studio 2017 we do not even need a .sln file anymore: There is this neat option of opening a CMake based project directly. Once the project is loaded, CMake is run which will download all dependencies. This might take a while when executed for the first time but Hunter is smart enough to cache those dependecies. Wait for IntelliSense to finish and you are ready to code.
For more information about the scratched topics check out the official CMake documentation and the official Hunter
repository. Hope that helped!
Hazelbit is my
personal portfolio page. Contact me if you have any questions.