Cmake Subdirectory Example


  1. Cmake Subdirectory Example Paper
  2. Cmake Subdirectory Examples
  3. Cmake Subdirectory Set Option

CMake is a powerful and robust build system. You specify what you want done, not how to do it. CMake then takes that information and generates the files needed to build the system. For example, CMake can generate solution (.sln) and project files (.vcxproj) that Visual Studio and Visual Studio Code use on Windows. Example # set (myvariable 'the value is a string') By default, a local variable is only defined in the current directory and any subdirectories added through the addsubdirectory command. To extend the scope of a variable there are two possibilities.

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 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

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.

  • 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 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 – or – to generate the module easily. The looks like this:

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

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.


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:


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.


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>/ 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 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:



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.


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:

Cmake Subdirectory Example Paper

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.

Cmake Subdirectory Examples


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

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.


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 🙂.


  • [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

CMake is an excellent cross-platform build tool for automatically generating Unix Makefiles, Windows NMake Makefiles, Microsoft Visual Studio® Solution projects or Apple Xcode® projects for MacOS. It has its own domain specific language and various modules for most commonly used libraries and software frameworks. The most common use of CMake is to build projects that are written in C, C++ or both. As a longtime user of CMake we have written build systems for large and complex projects in it that also build Java and C# wrappers, or use it for auto-generating cross-platform C/C++ code using Perl.

In this blog post we demonstrate how to use CMake to build a large toolkit like Intel® Threading Building Blocks (TBB).

Although TBB might be available in your Linux operating system's package manager, sometimes you may want to compile the latest version from source using a different compiler like Intel's C Compiler (icc) instead of GNU C Compiler (gcc), or you're building software that runs on both Linux and Windows, and you don't want to use a pre-built version of TBB from Intel.

CMake has a module called ExternalProject that can do this for you. Below we demonstrate how to download the latest source from the TBB website, and how to use features present in CMake to make sure that the project gets compiled and ready to use in your project. TBB is a C++ library, hence our example will be with C++ source.

Let's say your source code directory structure looks like below. For brevity we are not displaying too many C++ files. We will use this example to show how to use TBB as an external dependency.

Here the myproject.h, myproject.cpp are the source code for your application that will use TBB and loadtbb.cpp is a unit test to check that you have loaded TBB correctly. The CMakeLists.txt files in each directory are for CMake to know how to handle the files in each directory. Sample files are given at the end of this post.

Note that the below file tbb.cmake doesn't exist yet and we will be creating it in the following section.

Create an empty file using your favorite text editor called tbb.cmake in the thirdparty directory. Your directory structure should look like this:

The tbb.cmake file can be downloaded here.

Downloading the TBB Source Code

There are two ways to download the source: manually and using CMake. We explain the manual method first, as the CMake method follows from that.

Cmake Subdirectory Set Option

Using curl or wget or any browser download the source from the TBB website.

For example, the latest source code version at the time of writing of this blog post is 4.4. Place this downloaded file in the thirdparty directory of your project, which in our case is the myproject directory. Our tree structure now looks like this.

Let's add the following lines to the tbb.cmake file now.

If you want to use the direct URL from the TBB website, the TBB_URL variable line will look like below. Be aware that this causes TBB source to be downloaded each time you build the project which may not be what you want. Hence, we recommend the manual download method above.

The rest of the tbb.cmake file will be described assuming the manual download method since that is expedient.

To build the TBB source, we have to use the CMake functions provided by the ExternalProject module, viz., ExternalProject_Add, ExternalProject_Get_Property and ExternalProject_Add_Step. The details of each of these functions can be viewed in the CMake manual or using the command man cmake on your terminal.

NOTE: To build TBB on Windows requires GNU Make or gmake installed and in the PATH or set it in the TBB_MAKE variable.

The best way to verify this works is to first test it on Linux or MacOS.

We add the TBB project using the ExternalProject_Add command to the
tbb.cmake file like below. We also add the sub-projects in TBB that are required in the file using ExternalProject_Step.

The ExternalProject_Add will uncompress the TBB source file we downloaded earlier and compile it using as many CPU cores as available in your system. The number of CPUs can be modified by editing the NCPU variable in the file.


It will then compile the source and all its dependencies that are specified. Any following targets that need to be built usign ExternalProject_Step are also built.

NOTE: Building the examples is optional. It can take very long and isn't recommended for regular use. It is only useful if you want to see how stuff works.


We then use CMake's module CheckIncludeFileCXX to have CMake test whether it can include the tbb/tbb.h header file in code and compile it. If it can, then we have succeeded in adding TBB as a dependency in our project.

The complete tbb.cmake file is below:

As you see above this file looks complex but in reality, that's how CMake build files look. You have to instruct CMake in detail to avoid it making mistakes, especially in such cases. However, once you have a template like the above, it is easy to make it work for other libraries too.

The commands to build the project are as follows:

This has worked well for us on Linux and MacOS. We have not had a need to test on Windows, but if you find any problems let us know.

NOTE: If you have errors in your run of cmake for any reason and they don't go away, remember to delete the CMakeCache.txt file and then retry.

The complete source code for the sample project can be downloaded here.