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!
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)
target_include_directories(my_program
PRIVATE
first_dir/
second_dir/)
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_lib
target, 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!
Be First to Comment