Cmake Add_executable Example

/ Comments off

So far our project is rather simple. A real project would be more complicated than the one we've created. Let's add subdirectories, libraries, and proper unit tests to make our project more realistic. In this chapter we will split up our project to have a library which we can put in a subdirectory. Then we Continue reading 'CMake Tutorial – Chapter 4: Libraries and Subdirectories'. Addexecutable tells CMake that we want to build an executable (so not a library) called helloworld as a target. The target should be built from C code (mainwindow.cpp, main.cpp), a Qt Designer file (mainwindow.ui), and a Qt Resource System file (resources.qrc). Note that you typically do not list header files here.

Abstract

I am sure that every C++ programmer has at one point struggled with CMake. There have been multiple times where when I have to start work on some C++ project, I’ve to spend a good couple of hours in thinking how should my project structure look like. It’s just such a huge hassle to think about all of your CMakeList.txt files and possible libraries and different modules and… the things that may go wrong with your build system. I’ve been meaning to find a good template of a CMake project for a long time and now I think I’ve foundcreated my long wanted gem. In this post, I first want to give a brief overview of my experience with CMake and the present the template project that I have finally settled with.

The horrible past experience

I have bad memories in my early days of dealing with CMake. At the start, I had the impression that a build system should make everything as easy as possible but it seems that often this is not the case. Don’t get me wrong. I know build systems do not solve an easy problem, but still. I quickly noticed how there are a lot of people on the internet talking about their way of doing CMake projects. It didn’t seem that there wasn’t the way of doing it. This is generally a good thing. Freedom and doing things your way! But everything comes with a cost. The cost of CMake – you have no idea what you are supposed to do at the beginning.

My first experience with a big CMake project was while dealing with legacy code. The structure was similar to the one of OpenCV. A big CMakeList.txt file at the top lever with general project settings, a separate file with all of the libraries needed for the project and then a source directory with different modules. Each module is in a folder of its own. The structure of a module: source, include, test, and data directory; a CMakeList.txt file a the root of the module to define all executables and libraries. In all fairness, I still like the structure of the individual modules. The strange thing with the module-system was the way it did dependency resolution. Everything was done “manually”. It essentially was a big CMake framework that first collected info about every module, looked at the dependencies between them and the needed libraries and then build targets manually for a module by linking explicitly everything that module needed. The whole thing was implemented in a collection of complicated macros. There maps and lists and algorithms and directory traversals and everything you can imagine. The whole thing is written in CMake of course. Legacy code for the win!

At one point I was considering building a similar system myself for my personal projects. Needless to say, that didn’t take off. I wanted to clear up the structure and tightened it around the edges but I didn’t have any form of success.

I browsed some more CMake projects, I watched some talks (see Effective CMake Talk by Daniel Pfeifer) and I also tried setting up some thins myself. Nothing adequately clicked with me. CMake was still getting in the way of my C++ programming and it was making it even more painful than it already is. This went on for some while.

The bright new world

Finally, a very fortunate thing happened. I watched Applied Best Practices and decided to check out the repository. The project is created with the idea of being a demonstration of “doing things properly” in C++. I can agree with this sentiment 100%! I still did some adjustments but I am still grateful that I found this project. Here I will go over the elements of the structure of the template project that I created.

Conan

Conan is one of the numerous attempts to bring the “package management world” to C++. Package management is one of those things that modern languages just “have by default” but somehow C++ hasn’t exactly caught up. For by CMake structure I wanted at least some attempt to make external dependency management a little bit easier. For this reason, I figured that Conan may be the utility to start my experimentation with “C++ package management”

From Conan’s website:

The open-source, decentralized and multi-platform package manager to create and share all your native binaries.

I won’t give (because I can’t) a comprehensive guide on Conan here. I believe this here is just enough to set you up and give you some basics of how Conan does its magic.

Conan can be installed through pip and there is an official get started guide. Once everything is set up properly, you can write your conanfile.txt file in the root directory of your project. This is where all of your dependencies are defined. An example file is:

It is a conf file with several sections. The dependencies go in the requires node. Usually what to put there is defined on the page of each Conan package. The generators section defines exactly what Conan does and how it builds the dependencies configuration files. Different generators have different effects and use cases. As we are dealing with a CMake project, the cmake and cmake_find_package are enough. With this configuration setup, you can later include several lines of CMake code in your top-level CMakeLists.txt and just call find_package(..) for your dependencies. In theory at least. Sometimes things are a little bit tricky to get them running. If everything is as it is supposed to be, you can execute conan install <path_to_source_directory> in the build directory of the project and all of the dependencies will be fetched and saved in some cache on your machine.

In my top-level CMakeLists.txt, I have the following lines:

This sets up everything and at some later point I can just use something like:

This includes all of the CMake targets defined for Clara. We can later effortlessly link agings them when defining our executable for example.

Top level CMakeList.txt

Cmake

With Conan out of the way, I can now proceed to the pure CMake part of my project structure. At the start, I want to layout my key core design goals.

Note: It is not officially available to download Sky Go on Amazon Firestick App. Until then, you can sideload Sky Go on Firestick to enjoy its offers. How to Install Sky Go on Firestick / Fire TV. Since Sky Go is not available on Amazon App Store, you would have to utilize an unconventional method to install Sky Go on Firestick. Jan 08, 2021 Step 4: Type in the Sky GO Apk download URL and name it as Sky Go and click the Download Now button to start downloading. Download Sky Go on FireStick Step 5: After the completion of download click on the open file and then click the Install button, this will open the Sky Go Apk file in FireStick installation window. Apr 13, 2020 Sky Go on Amazon Fire Stick gives you access to various types of content such as movies, sports events, breaking news, TV shows, and many others. In addition to movies and other shows, it also offers a wide range of content for children. Sky Go on Firestick lets you stream content for hours without any limit. The answer is Yes you can get the Sky Go app on Firestick 4K, FireTV Cube, and other devices instantly. Although the app is not officially available yet but to get this app we simply need to sideload it. Just like this app, there are various apps that are available on the different app stores but not on the Amazon Store. Sideload sky go firestick live

  • Follow modern CMake guidelines
  • Ease of use
  • Modular setup
  • Not worrying about dependencies between modules
  • Support for testing
  • Support for automatics documentation generation

Folder structure

A high-level overview of the my project structure is as follows:

Everything comes together at the top-level CMakeLists.txt. This is the main entry point when running cmake <path_to_source_directory>. In the cmake several utility CMake scripts solve several small problems like finding the git version of the host machine and preventing me to build the project inside the source file tree. The Doxyfile.in file contains the basic configuration setup for Doxygen which I use for generating documentation. My idea for the libs folder is to encapsulate the external dependencies that are not available through Conan. Of course, for some big dependencies (e.g. OpenCV) this is not really viable but it works like a charm for header-only libraries. The libs/CMakeLists.txt is responsible for loading the libraries in the libs folder. The src directory houses all of the individual sub-modules in separate sub-folders that are included through the src/CMakeLists.txx script. More on the individual modules in a minute. In templates/ I have template visions of the two modules that the project can have – a library or an executable. I want to add a new module to the project, I can use of the scrips at top-level – create_new_app_module.sh or create_new_lib_module.sh – to generate the module easily. The create_new_app_module.sh looks like this:

With this setup, I can simply execute ./create_new_app_module.sh executable_1 to add a new sub-module with the name “executable_1”.

Add_executable

The Makefile is there just so that I can automate some of the things I do regularly in the project folder. Things like rebuilding, creating debug or release builds or cleaning all build folders.

Options

With the general folder structure, we can now go through several parts of the top-level CMakeLists.txt script.

Near the top of the script, I have the options with which the project can be built. Those are just variables that can be true or false and enable certain conditions for the later parts of the scripts. The options are:

I think the help strings are pretty self-explanatory so I won’t go over each option individually. If you see any of these variables in later snippets, just know that it can be adjusted through the way the cmake is called when building the project. The options are passed as -D arguments to the cmake command. For example, to build with DEBUG_LOGGING enabled, we must call cmake like:

Targets

Modern CMake is all about targets! The general rule of thumb is not to touch any variable in CMake (like CMAKE_CXX_FLAGS) directly but rather impose some requirements on a certain target. For the most part, the top-level CMakeLists.txt follows this paradigm. Targets can be defines as INTERFACE. This means that they don’t produce any build output (neither library nor executable) but rather exist purely to be dependencies of other targets. Interface targets can be used, for example, to “contain” compile options. When an executable target is defined and it links against one such interface target, all of the compiler options imposed on the interface will also be imposed on the executable. This is my general idea that the CMakeLists.txt is structured around.

At the start, there are two INTERFACE targets defined – project_warnings and project_options

project_warnings is meant to keep track of the flags that instruct the compiler on what warning to report on. project_options is for every other flag that may be passed to the compiler.

Flags

After the definition of the targets, several checks decide on the compiler flags that are to be used. The significant parts of the “building” of both targets are given in the following snippets.

For starter, we make sure that we are programming in C++17. C++17 is as good as it gets and it’s the current year so, of course, we are going to use it for every personal project.

If coverage is enabled for the build, we set the appropriate flags for the compiler. Notice how we are defining the options only on the targets and not in “global scope” through the CMAKE_CXX_FLAGS flag.

We do something similar for the address sanitizers of the compiler:

While developing in C++, warnings are your friend. The more the better! Warnings can expose lots of tiny mistakes that you can make while writing C++ and in this sense, the compiler is your friend. As long as you tell it to report on the proper warning, of course.

A colorful output on the terminal is always useful.

Extra tools

Other than the compiler flags, several other external tools can help you in your C++ development. Many of them can be integrated with CMake. In my project template, I have three of them.

CCache is a compiler cache that speeds up recompilation. It is a separate program and you have to have it installed on your system. If this is the case, the following snippet will set up ccache in our build.

Cppcheck is a static analysis tool for C++ code. I can help you catch some common mistakes while programming and it even doesn’t require you to compile your code. If you have Cppcheck on your system and you’ve enabled it in your build, you can use it with CMake like this:

Clang-Tidy is yet another tool for static analysis of C++ code. It can catch different set ot errors than cppcheck. As with the warnings, the more things the tools can tell us about our code, the better. The CMake integration is, again, possible and trivial:

Configurable header

In some situations, in your C++ code, you’ll need information that is available only in the CMake build. This may include build information, version of the project, endianness of the host system, a compile-time configuration of some sort, etc. Because of this, I figured out that I may need some sort of a “global” header file where such things are saved by CMake and accessible in the C++ code. My approach is to have the file src/include/<project_name>/config.hpp.in that will be configured by CMake and every file in the project would be able to include it. On the CMake side of the things, the code looks like this:

Using a CMake function like include_directories is generally a bad practice but in this case, just for once, in a “global context”, I am okay with it. In the config.hpp.in the file we can use all of the variables in the CMake environment to define whatever we want in the C++ code. To access a CMake variable, we use the @<VARIABLE>@ syntax. For example, the file can look like something like:

Documentation building

One of my design goals for the project structure was to be able to handle building documentation. I achieve that through a custom target. In CMake we can specify a command to be executed (in the sense on a command on the terminal) and “bind” is to a make target in the final Makefile in the build directory. This means that at the end of the build process, I can execute something like make doc in the build directory to build the documentation for the project. Setting up Doxygen in CMake is not complicated. There is a package that can be included through find_package. This sets several CMake variables that are relevant to Doxygen. The whole setup can be done like:

Summery

At the end of CMakeLists.txt, I’ve created a bunch of messages that show exactly hot the project is currently being build. It just prints out the options of the build and whether or not they are active or not.

Modules

Let’s now look at the individual submodules and how are those organized. As said, the src/CMakeLists.txt script includes them all through several calls of the add_subdirectory function.

The folder structure of each module is the following:

The src/ directory is meant for the source files as well as the private headers of the module. “Private headers” means that those won’t be visible outside of the project. The public headers are meant to go in the include/ directory. There is, however, a small caveat to its subfolder structure. In order to keep everything in my project organize, so that I can keep my sanity, I prefer to include the headers files in the form of:

In my mind, this is the most “logical” way to include something that is somewhere in the project as there is a clear hierarchy. This can be achieved by having several subfolders in the include/ directory. The structure at the end is:

The CMakeLists.txt file for the modules is relatively simple. It just has to create a target (a library or executable), set up the include directories and then link it against the necessary other targets. In the top-level CMakeLists.txt, we’ve created the two INTERFACE targets project_options and project_warnings. Those are the “mandatory” ones to link against and every other target can be a one from the external library andor a different submodule of the project. The whole CMakeLists.txt file looks like:

The last call to the install function instructs CMake on how to construct the install target of the final Makefile. The function call basically makes all artifacts “go to the right place”. I think that the most relevant part is that the header files will be copied to the global include directory (e.g. /usr/include/) but the will be put inside a folder with the project’s name. In this case, another project can include headers from this project like:

which again helps to keep things organized.

Testing

The final aspect we have to look at is how to enable support for running C++ tests. Thankfully, CMake makes the integration incredibly easy. CMake uses ctest to discover and run tests I write the tests themselves with Catch – a header-only, test framework for unit-tests. There is a nice startup guide for Catch here. The general idea is to create test executables with the help of Catch and then let ctest run them. To enable ctest support in a project its enough to call the enable_testing function in the top CMakeLists.txt file. In there I have:

In the CMakeLists.txt files, I conditionally add the test/ directory of each submodule. In there, there is a different CMakaLists.txt file that sets up the testing for the corresponding module. The script is nothing special and it is very similar to the upper-level one.

In the CMakeLists.txt files I conditionally add the test/ directrory of each submodule. In therem, there is a different CMakaLists.txt file that sets up the testing for the corresponding module. The script is nothing special and it is very similar to the upper level one.

The last three lines turn this into a “test target”. The build will thus produce a binary that can be ran and all the tests defined in it will be executed. As ctest was enabled, the whole process boils down to executing make test in the build directrory of the project. submodule_1_test.cpp ./src/src_file_1.cpp ./src/src_file_2.cpp)

target_include_directories(${PROJECT_NAME}_alisp_test PUBLIC ./include PRIVATE ./src)

target_link_libraries(${PROJECT_NAME}_alisp_test PRIVATE project_options project_warnings PUBLIC Catch2::Catch2)

include(CTest) include(Catch) catch_discover_tests(submodule_1_test) #+END_SRC

Cmake Add_executable Example For Students

The last three lines turn this into a “test target”. The build will thus produce a binary that can be ran and all the tests defined in it will be executed. As ctest was enabled, the whole process boils down to executing make test in the build directory of the project.

Conclusion

So those are my two cents about CMake and project structure. I don’t claim to have a lot of experience but I’ve done a lot of research in the past year and a half. I’ve looked into different projects, read the best practices, read a lot of vague tutorials on the internet and watched the relevant talks. I have thought this several times, but this time I really think I’ve nailed it. I hope that I’ve created (mostly stolen) something scalable that will serve me well in my future small to mid-size projects. Whether of not scalability should be of my concern is a completely separate matter 🙂.

References

Cmake Add_executable Example Page

  • [1] CPP_BOX is a project by Jason Turner.
  • [2] Victoria Rudakova’s Post
  • [3] Effective CMake Talk by Daniel Pfeifer
  • [4] Applied Best Practices Talk by Jason Turner
  • [5] CMake’s documentation