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:

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!