CMake Prerequisite Knowledge for Developers Transitioning to C++

If you are like me and you come from coming to C++ from a background where you haven’t really used C++ before, the first thing you might notice is that dependency management is chaos.  In Python, you just run pip install and in JavaScript you can simply use npm, and then (hopefully) it just works.  If you start digging on how to deal with dependency management in C++, you’ll run into a lot of different concepts.  Some articles might say to use git submodule add to include github projects and others mention vcpkg or Conan to load packages.

The fact of the matter is that even if you added a dependency somehow, it isn’t guaranteed to work immediately like in a python project.  What needs to happen is that all the dependencies that you add to your project get (potentially compiled) and linked in your project.  There are several tools that can do this, but the one with the largest market share (somewhat controversially) is CMake.  If you are someone studying C++, it’s likely that you have either heard about it or used it at some point.  Since you are here reading this, I am going to assume that it has you a bit confused, which to reassure you, was my experience as well.

Resources for Learning About CMake

This article is about prerequisite knowledge for CMake so we won’t go over the actual syntax too much.  If you want some great guides on how to use it, check these pages out:

https://hsf-training.github.io/hsf-training-cmake-webpage/

https://cliutils.gitlab.io/modern-cmake/

Libraries, Directories and Linking, Oh My…

When coming to C++ from another language, we often tend to assume that, other than pointers and overloaders,  things can’t be so different from what we are used to using.  Unfortunately, this assumption tends to fail when we start running into the concept of compiling and linking files.

Fortunately, CMake handles this part for us, but to understand how to make CMake work, we need to tackle some vocabulary. The first two words I want to look at are linking and compiling.  Everyone in software knows what compiling is at least to some degree.  However, linking might not be so clear.  On top of that, it might not be perfectly clear what the actual responsibilities of both are.

Linking and Compiling

As most people probably know, compiling converts your code into something lower level for the computer to run.  This is usually binary object code of some sort, but it isn’t as simple as just taking a bunch of files and combining them together into one binary file.  This is where linking comes into play.

When your code is compiled, there will be binary files for the various libraries you might need for your project.  They may be in your project already or they may be in an include directory like where apt-get would install packages on a linux device.  The linker goes and connects all of those components to the other components that use them, including to the main file that you are working on.  The main purpose of CMake is to let the compiler know what files to compile and let the linker know how to connect all the created components together.

Libraries and Directories

Now we can move on to our next two vocabulary words: Libraries and Directories.  Again, you might be rolling your eyes at me because it should be obvious what the difference is, but when you start working with a CMakeLists.txt file that is throwing target_link_libraries and target_include_directories at you, it might confuse you a bit.  So let’s go ahead and make it as clear as possible.

Before we get started, let’s make a super simple project.  It will have a main.cpp file as the primary file in the project.  We will use CMake 3.8 and C++ 17.

CMake
cmake_minimum_required(VERSION 3.8)


project(main)


add_executable(main main.cpp)


set_property(TARGET main PROPERTY CXX_STANDARD 17)

When CMake refers to libraries (such as target_link_libraries) it usually means a header file that contains some specific functions and a binary to accompany it that contains the logic for those functions (LearnCPP has a good explanation).  In some cases, this can be a header only library as well, so you would not even need a binary.
To create a library in CMake you would use the add_library command to create a new library target.  For our purposes, let’s create a library using the code in a folder called writer and give it a writer.h and a writer.cpp file.  The code might look something like this:

CMake
cmake_minimum_required(VERSION 3.8)


project(main)


add_executable(main main.cpp)


set_property(TARGET main PROPERTY CXX_STANDARD 17)


add_library(writer STATIC writer/writer.h writer/writer.cpp)

Note:  If you didn’t read the LearnCPP link above, it explains the differences between static and dynamic libraries.

When CMake runs this code, it compiles the library from the files that you passed to it.  So great, now you have a compiled library assigned to the target writer, but if you were to include writer.h  in a project, it would give you a fatal error: writer.h: No such file or directory message.  This is because, although the library has been compiled, it hasn’t been linked to the project yet.

Fortunately, we already know exactly how to do this because we have talked about it so much!  We just use target_link_libraries to link our libraries to our project.  Here is very simple example of that:

CMake
cmake_minimum_required(VERSION 3.8)


project(main)


add_executable(main main.cpp)


set_property(TARGET main PROPERTY CXX_STANDARD 17)


add_library(writer STATIC writer/writer.h writer/writer.cpp)


target_link_libraries(main PUBLIC writer)

As you can see, the writer folder was included in the writer target.  If we now run CMake and build the project, it will compile correctly.

Generally, you can follow this pattern for libraries that you imported into an include directory (such as those included using git submodule add) as well.  Just make sure to follow the setup instructions provided by the project.

Adding Packages From A System Package Manager

Another way you may want to add a package is by installing it with a system package manager.  This is fairly straightforward as well.  When you add a package with the system package manager, generally you can use find_package to go find the installed package.  Then you just need to include it and its directories.

Here is a simple example using OpenCV (which we love because we are here to talk about Perception right?!):

CMake
find_package(OpenCV REQUIRED)
target_link_libraries(demo ${OpenCV_LIBS})
include_directories(${OpenCV_INCLUDE_DIRS})

Just be careful because in some circumstances, the variables needed are not always explicit and there might be additional setup needed.  Generally try to find examples in the documentation rather than just take my word for it.

Conclusión

For an example of the code that includes a runnable main.cpp file, please checkout the linking_and_compiling folder in this github repository.

If you are like me and you came from another language that doesn’t deal directly with compiling and linking, no one probably taught you these concepts and you likely couldn’t find answers in one location.  I know I had to dig around quite a bit to find this.  Hopefully this is useful to other people out there like me!


Publicado

en

por

Etiquetas:

Comentarios

Deja un comentario

Connect with

es_AREspañol de Argentina