CMake Project Structure

As promised in the last post about CMake, today we’ll use a proper CMake project structure for our “Hello CMake” project.

This post is the third of a series about CMake:

  1. Hello CMake!
  2. Another Target and the Project
  3. CMake Project Structure
  4. Properties and Options

Currently, the files of our little CMake project are all in the same directory. Sources for the main program, sources for the tests, the Catch header, the CMakeLists.txt – everything. I’ve written about structuring our code into directories, and today we’ll move our project in that direction.

Moving the Catch header

Let’s move the catch.hpp into its own directory thirdparty/catch/include. We’ll put other third party stuff that may come under thirdparty as well, each in their own directory. The structure now looks like this:

hello_cmake
+-- thirdparty/
|   +-- catch/
|       +-- include/
|           +-- catch.hpp
+-- CMakeLists.txt
+-- hello.h
+-- hello.cpp
+-- main.cpp
+-- testmain.cpp

Of course, the tests won’t build now, since catch.hpp is not where it used to be and the compiler won’t find it without a proper include path. That is why we have to use the target_include_directories command to add an INCLUDE_DIRECTORY property to our tests target. The result will be a -I flag for the compiler on GCC or Clang, a /I flag for the MSVC compiler, and so on.

...
# The tests
add_executable(tests testmain.cpp ${PROJECT_SOURCES})
target_include_directories(tests PRIVATE thirdparty/catch/include)

The only thing we added is the last line. It adds the include directory to the tests target. The PRIVATE keyword means that other targets using the tests target won’t inherit it. Since that target is an executable that’s the right thing to do – we’ll get other keywords in that place when we come to libraries later.

We can now build our project in verbose mode to see the compiler commands. To do that, we can pass parameters for the native build tool after two additional hyphens to the build command. For a Makefile-based build that could be: cmake --build . -- VERBOSE=1 --no-print-directory

...
/usr/bin/c++.exe    -I/home/Arne/git/hello_cmake/thirdparty/catch/include   -o CMakeFiles/tests.dir/testmain.cpp.o -c /home/Arne/git/hello_cmake/testmain.cpp
...
/usr/bin/c++.exe      -o CMakeFiles/prog.dir/main.cpp.o -c /home/Arne/git/hello_cmake/main.cpp
...
/usr/bin/c++.exe      -o CMakeFiles/prog.dir/hello.cpp.o -c /home/Arne/git/hello_cmake/hello.cpp
...

As you see, the -I flag is used only for the test source, as it should be. You can see the current state of our sources on GitHub.

Add the CMake project structure

Currently, catch.hpp is the only external library we use, we use it only in one place, and we need only the INCLUDE_DIRECTORIES property for it. It won’t be always that easy, so we should give the library its own target. While we’re at it, we’ll want to reuse it, so it should have its own CMakeLists.txt file as well. The CMake project structure should reflect our actual project structure.

The location for Catch’s own CMakeLists.txt is in the catch directory:

hello_cmake
+-- thirdparty/
|   +-- catch/
|       +-- include/
|       |   +-- catch.hpp
|       +-- CMakeLists.txt
...

The content of the new file looks like this:

project (Catch)

# Header only library, therefore INTERFACE
add_library(catch INTERFACE)

# INTERFACE targets only have INTERFACE properties
target_include_directories(catch INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)

We see that it has its own project name coming first. Then we define the target, named catch, with the add_library command. The command defines a library target and is in many ways similar to the add_executable we already know.

However, it’s a header-only library, so there is nothing to compile. The whole target consists only of an interface that is passed to its users, therefore we have to add the INTERFACE keyword to the add_library command.

We see the keyword again in the next line: This time, the INCLUDE_DIRECTORIES property is not declared PRIVATE for the tests target, but as INTERFACE for catch. This INTERFACE means, that the property is not used for the target itself, but it’s passed to any user of the target. As there is nothing to compile that’s the way to go. The third of these keywords is PUBLIC: it means that a property is used to build the target itself and it is passed to users of the target.

The last new thing we see in this line is the use of the ${CMAKE_CURRENT_SOURCE_DIR} variable: it is set by CMake itself and points to the directory where the currently processed CMakeLists.txt is located.

Using the new CMakeLists.txt

We now have to use the new file from our main project file. To do that, we use the add_subdirectory command early in our main CMakeLists.txt, e.g. directly after the project command. add_subdirectory(thirdparty/catch) will tell CMake to go into that directory and process the CMakeLists.txt it finds there.

After that, the catch library target will be known to CMake, and we can use it for our tests target:

...
# The tests
add_executable(tests testmain.cpp ${PROJECT_SOURCES})
target_link_libraries(tests PRIVATE catch)

We have replaced the target_include_directories command with the target_link_libraries command. It tells CMake that in order to build the tests target, the catch library target has to be linked or added.

In this case, the name is misleading, since catch is not compiled and therefore there is nothing to link. The command will, however, add the PUBLIC and INTERFACE properties of the linked library to the target. In our case, that means that we get the INCLUDE_DIRECTORIES property, so the compiler knows where to find catch.hpp again. We used the keyword PRIVATE again, since there still is no other target using tests, and the properties we just inherited are an implementation detail.

See the current state of our sources on GitHub.

Putting it all together

We have now everything we need to create a clean directory and CMake project structure. Let’s move all those files into their proper places and add a CMakeLists.txt for each directory:

hello_cmake
+-- src/
|   +-- CMakeLists.txt
|   +-- hello.h
|   +-- hello.cpp
|   +-- main.cpp
+-- test/
|   +-- CMakeLists.txt
|   +-- testmain.cpp
+-- thirdparty/
|   +-- catch/
|       +-- include/
|       |   +-- catch.hpp
|       +-- CMakeLists.txt
+-- CMakeLists.txt

Let’s have a look at our main CMakeLists.txt:

cmake_minimum_required(VERSION 3.6)

# The project name
project(hello_cmake)

add_subdirectory(thirdparty/catch)
add_subdirectory(src)
add_subdirectory(test)

There’s nothing left but the required version, the overall project name and then we descend into all our directories. The order of those could be different, even though we define the targets in the first directories that we need later. CMake would figure those dependencies out. However, it is a good practice to sort things in order of their dependencies. If you can not do that, you likely have circular dependencies, which is not good.

Catch’s CMakeLists.txt is still the same, so let’s have a look at src/CMakeLists.txt:

project(Hello_prog)

# All sources that also need to be tested in unit tests go into a static library
add_library(hello_lib STATIC hello.cpp hello.h)
target_include_directories(hello_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# The main program
add_executable(prog main.cpp)
target_link_libraries(prog PRIVATE hello_lib)

After the project name, we see two targets: a static library and the main program. The library contains everything that is needed in the main program and in the tests. That way, we get around defining and reusing that ${PROJ_SOURCES} variable we had before. In addition, hello.cpp is only compiled once, and not for both the tests and the main program. This is a good thing, because not only does it cost less time; we also want to make sure that the code under test is compiled with the same flags properties as in production.

The library target again has the INCLUDE_DIRECTORIES property set, so the tests won’t have to add the directory manually. The main program simply has the main.cpp and links against the library containing the rest of the code, as we have seen before.

The last CMakeLists.txt to examine is the one in the test directory:

project(hello_tests)

# The test program
add_executable(tests testmain.cpp)
target_link_libraries(tests PRIVATE hello_lib catch)

No surprises here: we have the tests executable that links against the catch and hello_lib libraries. And, as always, you can see the working project in this state on GitHub.

Conclusion

With just a handful of commands we can already maintain a clean CMake project structure. In the next installment of this series, I’ll go into a few more useful properties. Please don’t hesitate to leave a comment below!

Previous Post
Next Post

15 Comments


  1. Hello Arne

    Your tutorial has been the MOST useful to get me a real-hands on starter with Modern CMake! Thank You very much for sharing such properly sorted out step-by-step tutorial introducing Modern CMake to beginners, like me!!

    I ran into problem when I tried to include the hello() function’s declaration (in hello.h), definition (in hello.cpp) and subsequent call (in main.cpp and testmain.cpp) using a custom namespace.
    I mean, the CMake did failed to build the project.

    May you please guide me as to how CMake can recognize custom namespaces?

    I look forward to your response!
    Thank You!

    Reply

    1. Hi Vivek, glad you liked the posts!
      I am not sure how namespaces would be a CMake issue. Are you sure it’s not a C++ problem? Without seeing the code and the error message it’s hard to tell. You could send me more details in an email or Twitter DM if you want: https://arne-mertz.de/contact-me/

      Reply

  2. First of all: great post!

    What struck my eye was that you are declaring a new project in each CMakeLists.txt. I never do this for my own projects and I’m wondering what’s the motivation for doing so?

    Reply

    1. To be honest, I simply have adopted that from others. It probably is not necessary. However, when your CMakeLists.txt files get larger, it gets harder to spot the targets and having a project name at the very top of the file tells you at once which of the many CMakeLists.txt in your project you are looking at.

      Reply

    2. This is very useful for MSVC organization

      Reply

  3. Hi!

    I prefer something like

    Prepare “Catch” library for other executables

    set(CATCH_INCLUDE_DIR {CMAKE_CURRENT_SOURCE_DIR}/external/catch)
    add_library(Catch INTERFACE)
    target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR})

    set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/mainTest.cc)
    add_executable(tests ${TEST_SOURCES})
    target_link_libraries(tests Catch …)

    Reply

    1. Except for the variables that looks similar to the post, if I’m not mistaken.

      Reply

  4. One piece that stands out as a potential “change waiting to happen” is the decision to do add_subdirectory(thirdparty/catch) as opposed to doing a strict layering with CMakeLists.txt in each directory.

    I won’t disagree that it feels over-the-top, but the next thirdparty added would either require layering or an additional one-off add_subdirectory(…).

    Reply

    1. I’m not sure I understand what you mean. I have the add_subdirectory(thirdparty/catch) and no strict layering, since there is no CMakeLists.txt in the thirdparty directory itself.

      Reply

      1. Wether you do it as Arne posted, or put an extra CMakelists.txt in the third-party lib directory is mostly a matter of personal taste. It becomes more interesting though, once git-submodule are used for adding external dependencies to the project tree

        Reply

  5. Thanks for the great post.
    I wonder if there is any way to unit test a standalone header file (e.g. with some utility functions) from “src” directory without making it a library?

    Reply

    1. Since you’d just have to include it, adding target_include_directories should be sufficient.

      Reply

      1. Thanks! In case there are two files .h and .cpp but they aren’t actually a library? I’ve seen projects, where such files just included to test target using relative paths or using {CMAKE_SOURCE_DIR} variable. May be you can advise which way is better?

        Reply

        1. To be honest, I haven’t found too much about what is the best practice in that case while I was researching for this post. I’m still learning new things about CMake every day 😉
          That being said, a static library is nothing more than a bunch of compiled object files put together. Therefore you can group translation units to static libraries as you like. I did the same in the post with the hello_lib. The benefit of doing this is that you are guaranteed that they are compiled with the same flags and defines, whether you link them against the test code or against the actual executable.

          Reply

Leave a Reply

Your email address will not be published. Required fields are marked *