Lesson 9 · Targets: the core model
Headers and include directories
How the compiler finds your headers, and what PRIVATE, PUBLIC and INTERFACE mean in plain words, with a real example.
The most common “why does not my build work” in CMake is include directories. This lesson makes them mechanical.
The problem
Your library has two kinds of headers:
calc/
├── include/calc/calc.h # for the outside world
└── src/internal.h # for calc's own code
The compiler needs to find both. But the outside world should only ever see include/. One command handles both cases:
target_include_directories(calc PUBLIC include)
target_include_directories(calc PRIVATE src)
Two lines, and each folder goes exactly where it belongs. Now the keywords.
What PRIVATE, PUBLIC and INTERFACE mean
First, one word you need to know. The consumer is any other target that uses your library. In the examples below, the consumer is the executable app, which links calc. When we say “consumers get it”, we mean “any target that links calc gets it”.
Now the three keywords, in plain words:
-
PRIVATE. Only this target’s own files need it.
PRIVATE srcmeans “when compilingcalc.cpp, look insrc/”. Nobody else ever sees that folder. -
PUBLIC. This target needs it, and consumers need it too.
PUBLIC includemeans “when compilingcalc.cpp, look ininclude/. And whenapplinkscalc, addinclude/toappas well”. That is whyappcan write#include <calc/calc.h>. -
INTERFACE. Only consumers need it. The target itself never uses it. This happens with header-only libraries, which have no
.cppfiles to compile. Lesson 13 covers them.
A rule of thumb. If calc.h uses it, it is PUBLIC. If only calc.cpp uses it, it is PRIVATE. If only people using calc need it, it is INTERFACE.
What not to do
Don't do this# OLD. Global include dirs. include_directories(include) # leaks into every target, including third-party include_directories(src) # also exposes private headers to everyone
Do this# Attach the folders to the target that owns them. target_include_directories(calc PUBLIC include) target_include_directories(calc PRIVATE src)
Why
With
include_directories, anadd_subdirectory(third_party)after that line silently gives their targets your folders. If two libraries both have aversion.h, the first one wins and you get the wrong file with no error. When the folder is attached to the target, “which version.h am I including” always has an answer.
Rules that save you pain
- Public headers.
PUBLIC include/. One line, consumers inherit. - Private headers.
PRIVATE src/. Internal, invisible. - Header-only libraries.
INTERFACE include/. Nothing to compile, consumers only (lesson 13). - In consumer code, write includes with the folder prefix,
#include <calc/calc.h>. Never a bare#include "calc.h"with a flat include folder.
Tip
If a consumer still cannot find a header, look at the actual compiler flags. Run
cmake --build build --target app -vand check the-Ientries (lesson 27). Nine times out of ten, a missingPUBLICis the culprit.
Note
The full comparison of the three scopes, with examples, is on the PUBLIC vs PRIVATE vs INTERFACE page.
Read the docs