Skip to content

Adding C++ Header Include Directories With CMake

A good coding practice is splitting up your code into different reasonable and relatable pieces of code. In C++, this can be achieved by having multiple header and source files for different components. With CMake, adding header include directories to your C++ project is as easy as using your head in football!

include directions in C++ made easy with CMake
Heading those C++ include directories is easy with CMake.

As you are probably aware, you can include other source files in C++ with the #include pre-processor directive. Essentially, whatever file we include in that statement gets copied and pasted into the current source file by the compiler. However, the compiler needs to know how to find the file. So how do we tell CMake the location of our include files?

CMake Makes Working With The Compilers Easier

Telling the compiler where your include files are isn’t too difficult. For example, compiling the code in the source file program.cpp that includes the header files first_dir/first_include.h and second_dir/second_include.h needs the following command.

gcc -Ifirst_dir -Isecond_dir -o my_program program.cpp

Interestingly, the -I flag tells the compiler to look for files in the directory following the flag. More specifically, it tells GCC that our included files may live in those directories.

Obviously, the assumption here is that we’re working with the GCC compiler, but whether you’re using Clang or MSVC, the idea is the same: the compiler needs to know where your files are!

Luckily for us, CMake wraps all this functionality into easy-to-understand commands. In addition, CMake will work with any compiler you use, making the project’s build setup way more portable.

Using CMake To Add C++ Include Directories

Without further ado, the following lines of CMake will add include directories to a particular CMake target.

cmake_minimum_required(VERSION 3.16)
project(SomeProject LANGUAGES CXX)

add_executable(my_program program.cpp)

Precisely, lines 1-2 will set some required CMake project settings, which you can learn more about in my introduction to CMake post. In addition, line 4 creates the my_program executable from the file program.cpp. If you’re interested, learn how to create executables with CMake!

The star of the show is the target_include_directories(...) command. Unsurprisingly, this CMake command adds include directories to CMake target, and the syntax is the following:

target_include_directories(target_name {PUBLIC|PRIVATE|INTERFACE} directories...)

So we’re essentially saying that “target_name” should look for header files (or any included files) in the directories specified after the scope.

CMake Include Directories Scope? What Are They? Just Like Inheritance In C++

Firstly, to make things easier to explain, let’s assume we have a target for a static library called my_lib, and a target for an executable called my_program, that uses the library and its headers.

add_library(my_lib STATIC my_lib_source.cpp)
target_include_directories(my_lib PUBLIC some_directory)

add_executable(my_program my_program_source.cpp)
target_link_libraries(my_program PRIVATE my_lib)

For more information on adding libraries with CMake, check out how to use the add_library command in CMake.

Public Target Includes – Cascading Include Directories

Having PUBLIC target include directories for my_lib simply means that the include directories of our library will be used for the compilation of it, as well as the targets that use the library. In our example, some_directory will be searched for the compilation of my_lib and my_program, as it’s using (or linking) my_lib with the target_link_libraries(...) command.

In other words, using the PUBLIC scope specifier will cascade the include directories to the dependent targets of a target. Just like public inheritance!

Keeping Directories To Yourself With Private Include Directories

In the English language, the opposite of public is private. In the CMake world, the meaning isn’t so different!

Changing line 5 in the snippet above to the following code would make the “inheritance” of include directories private. This means that the include directories will not be searched for targets depending on our my_libtarget, so my_program cannot use the headers in the directory some_directory.

target_include_directories(my_lib PRIVATE some_directory)

Just like normal C++ inheritance, the PRIVATE scope means that my_lib will be able to see headers in some_directory, but targets linking against my_lib will not.

Interface Include Directories – The Most Uncommon Option

Unfortunately, the INTERFACE scope is a little bit difficult to understand. In short words, it means that the target adding the directory will not need the directory as a search path, but the dependent targets will. In other words, my_program would know about some_directory, but my_lib, who is declaring the interface directory, does not.

target_include_directories(my_lib INTERFACE some_directory)

As stated in the CMake documentation for INTERFACE_INCLUDE_DIRECTORIES, all the targets look at the INTERFACE_INCLUDE_DIRECTORIES property of linked targets, and use whatever directories are declared as search paths.

An example of this would be creating a CMake target for image manipulation. You may not use headers for libraries such as libpng, but you may want to declare libpng‘s include directories INTERFACE so that the users of your libraries can load images and use your manipulation functions. Essentially, we’re creating a “toolkit” target that contains useful include directories (and options) for dependencies. Although it’s not a good example, this will do – I actually hardly ever need to use the INTERFACE scope in the real world!

TL;DR General Notes On Including Header Directories With CMake

To include headers in CMake targets, use the command target_include_directories(...).

Depending on the purpose of the included directories, you will need to define the scope specifier – either PUBLIC, PRIVATE or INTERFACE.

PUBLIC and PRIVATE scopes mean exactly what you would expect in an inheritance context: PUBLIC will make the directories available in the current target and the dependent targets whilst PRIVATE will only make the directories available for the current target, not the dependents.

The INTERFACE keyword is the most difficult to understand, as its uses are rarely needed for basic projects: it makes the directories available for dependent targets but not the target declaring the directories!

As a general note: always use PRIVATE by default, this will keep scope small and simplify your CMake dependency graph. If you absolutely need to export header directories with the target, use PUBLIC.

If you still don’t understand the scopes, I’d recommend the CMake documentation and this article on CMake Public vs Private vs Interface by Lei Mao, he makes some good analogies there!

Published inCMakeCPP

Be First to Comment

Leave a Reply

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