Differencebetween Include_Directories and Target_Include_Directories in Cmake

What is the difference between include_directories and target_include_directories in CMake?

include_directories(x/y) affects directory scope. All targets in this CMakeList, as well as those in all subdirectories added after the point of its call, will have the path x/y added to their include path.

target_include_directories(t x/y) has target scope—it adds x/y to the include path for target t.

You want the former one if all of your targets use the include directories in question. You want the latter one if the path is specific to a target, or if you want finer control of the path's visibility. The latter comes from the fact that target_include_directories() supports the PRIVATE, PUBLIC, and INTERFACE qualifiers.

CMAKE: target_include_directories and include_directories

include_directories applies to all the targets in a particular CMakeLists.txt file. For example, let's say you have

include_directories( ../include
${SOME_OTHER_PATH}/include
)

add_library(math ${MATH_SOURCES})

target_include_directories(math
math_include
)

add_executable(calculator ${MYCALCULATOR_SOURCES})

target_include_directories(calculator
calc_include
)

calculator is an executable target and math is a library target defined in the same CMakeLists.txt. The folders ../include and ${SOME_OTHER_PATH}/include are visible to both. That means cmake will add the option -I../include -I<expanded-some-other-path>/include to both these targets when their sources are compiled.

For target_include_directories, the include path calc_include applies only to the calculator target and math_include applies only to the math target. As specified, math_include and calc_include would be (usually) subfolders present in the same folder that contains the CMakeLists.txt file.

CMake: What is the difference between `include_directories` versus `target_link_libraries`

Yes, you need both.

include_directories will tell to the compiler where to look for the header files, in this case, the header files for the boost library.

target_link_libraries will tell to the linker which libraries you want to link against your executable.

While headers will provide (most of the time) just the interface to access the library, the library itself is precompiled and linked to your application.

Difference between (target_)link_libraries and (target_)include_directories

*include_directories is used to supply a list of include directories to the compiler. When a file is included using the pre-processor, these directories will be searched for the file.

*link_libraries is used to supply a list of libraries (object archives) to the linker. If the linked item is a cmake target, with specified include directories, they don't need to be specified separately with *include_directories.

The target_* versions apply only to the target that is given as an operand. The non-target versions apply to all targets in the directory. The target_* versions should be used whenever possible (i.e. pretty much always).

find_package is used to search for cmake settings from external sources i.e. outside of the project. If you want to link with a library without including the source of the library in a sub directory of your project, then you use find_package. From a lower level point of view, find_package(Foo) looks for a cmake module FindFoo.cmake and executes the module. The purpose of the module is to generate cmake variables or targets that can be used to include the corresponding dependency.

add_library is similar to add_executable, except it adds a target for a library, rather rather than an executable. Library targets can be used as items in link_libraries, and their dependencies are transitive by default.

I know it has something to do with header-only libraries and so on,

All of these have to do with libraries in general. Except *include_directories are also used for specifying the include directory of the project's own header files, not only those of the libraries.



The included library requires for example Boost, but the core project not. How can I avoid bloating up the entire CMakeLists.txt file and the binaries, and still use the precompiled library?

If the find_package module has created a cmake target for the library (using add_library(... IMPORTED)), which itself specifies the dependencies of the dependency, then simply link with it using link_libraries, and cmake takes care of linking with the dependencies. Same goes for the include directory of the target.

Old cmake modules don't necessarily provide targets, in which case you may need to write your own module in order to avoid bloating the project configuration.

CMake target_include_directories meaning of scope

These keywords are used to tell when the list of include directories you're passing to the target are needed. By when, it means if those include directories are needed:

  • To compile that target itself.
  • To compile other targets that depend on that target (like using its public headers).
  • In both of the above situations.

When CMake is compiling a target, it uses the targets INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS, and COMPILE_OPTIONS properties. When you use the PRIVATE keyword in target_include_directories() and alike, you tell CMake to populate those target properties.

When CMake detects a dependency between a target A and another target B (like when you use the target_link_libraries(A B) command), it transitively propagates B usage requirements to the A target. Those target usage requirements are the include directories, compile definitions, etc. that any target that depends on B must meet. They are specified by the INTERFACE_* version of the properties listed above (like INTERFACE_INCLUDE_DIRECTORIES), and are populated by using the INTERFACE keyword when calling the target_*() commands.

The PUBLIC keyword means roughly PRIVATE + INTERFACE.

Therefore, suppose you are creating a library A that uses some Boost headers. You would do:

  • target_include_directories(A PRIVATE ${Boost_INCLUDE_DIRS}) if you only use those Boost headers inside your source files (.cpp) or private header files (.h).
  • target_include_directories(A INTERFACE ${Boost_INCLUDE_DIRS}) if you don't use those Boost headers inside your source files (therefore, not needing them to compile A). I can't actually think of a real-world example for this.
  • target_include_directories(A PUBLIC ${Boost_INCLUDE_DIRS}) if you use those Boost headers in your public header files, which are included BOTH in some of A's source files and might also be included in any other client of your A library.

CMake 3.0 documentation has more details on this build specification and usage requirements properties.

Why not use include_directories in CMake?

There are 3 main reasons

  • Limiting the scope of include directories to only those targets that need them
  • Allow for modifications of one target without requiring an update of the cmake logic for other projects
  • Allow for easier reuse of a project as part of a larger project

Limiting the scope of include dirs

The goal is to reduce the visibility of include directories to a minimum. If you use add_subdirectory after doing include_directories, all the targets created from the subdir or descendants of the subdir see the include directory added with include_directories.

subdir/CMakeLists.txt

add_library(subdir_lib ...)
...

CMakeLists.txt

add_subdirectory(subdir)
...
include_directories(some_include_dir)

vs

include_directories(some_include_dir)
...
add_subdirectory(subdir)

The difference between the 2 CMakeLists.txt files may be difficult to see, especially if there are is a lot of logic between the 2 commands; this makes this kind of logic hard to maintain.

"Inheritance"/Ease of recombination

Using target_include_directories also makes it easier to provide targets that link a library with the include dirs required. Include directories added via include_directories are not available to linking targets automatically, but with target_include_directories(... PUBLIC ...) this is quite easy to achieve. include_directories won't work on targets created from directories that are not created in subdirectories containing the command.

Example project

  • Library A
  • Library B requires library A; the public headers of B use public includes from A
  • Library C does not use A or B

File system:

.
|---A
| |---include
| | |---- A
| | |---- a.hpp
| |
| |--- a.cpp
|
|---B
| |---include
| | |---- B
| | |---- b.hpp
| |
| |--- b.cpp
|
|---C
| |---include
| | |---- C
| | |---- c.hpp
| |
| |--- c.cpp
|
|---CMakeLists.txt

Now you need to give B, but not C access to the headers of A. Using target_link_libraries this is trivial, but with include_directories you'd need to be careful about the order of add_subdirectorys in CMakeLists.txt

CMakeList.txt

add_subdirectory(A)
add_subdirectory(B)
add_subdirectory(C)

A/CMakeLists.txt

add_library(A ...)
target_include_directories(A PUBLIC include)

B/CMakeLists.txt

add_library(B ...)
target_include_directories(B PUBLIC include)
target_link_libraries(B PUBLIC A) # quite simple to provide access to A's include dir to linking libs using PUBLIC here, if required

C/CMakeLists.txt

add_library(C ...)
target_include_directories(C PUBLIC include)

Now let's assume you want to rename the directory A to AB. All you need to do is rename the dir on the file system and replace add_subdirectory(A) with add_subdirectory(AB) in CMakeLists.txt.

Alternatively assume you want to use libraries B and C as part of a larger project: All you need to do, if you use target_include_directories instead of include_directories, is add the directory containing the toplevel CMakeLists.txt using add_subdirectory and all you need to do is use target_link_libraries(NewTarget PRIVATE B C) to gain access to all the required include directories instead of doing

add_subdirectory(OriginalProject)

include_directories(OriginalProject/B/include)
include_directories(OriginalProject/C/include)
include_directories(OriginalProject/A/include) # did you remember this one is needed by B?

add_executable(NewTarget ...)

distinguish include_directories with and without the 'SYSTEM' option in cmake

There is target property INTERFACE_SYSTEM_INCLUDE_DIRECTORIES, which contains those of include directories, which has been marked as SYSTEM.


As the property's name suggests (and as noted in docs), INTERFACE_SYSTEM_INCLUDE_DIRECTORIES contains only INTERFACE directories, which are propagated to the targets linked with given one. It is unclear from CMake docs, how PRIVATE include directories should be distinguished.

Why my INCLUDE_DIRECTORIES and INTERFACE_INCLUDE_DIRECTORIES properties are empty in Cmake?

You are getting the DIRECTORY properties. You want the TARGET properties of your executable target.

target_include_directories is TARGET based.

get_target_property(inc_dirs Tutorial INCLUDE_DIRECTORIES)
message("INCLUDE_DIRECTORIES = ${inc_dirs}")

get_target_property(interface_inc_dirs Tutorial INTERFACE_INCLUDE_DIRECTORIES)
message("INTERFACE_INCLUDE_DIRECTORIES = ${interface_inc_dirs}")


Related Topics



Leave a reply



Submit