Lesson 13 · Targets: the core model
Interface libraries
add_library(... INTERFACE), a target with no sources. The clean way to bundle headers, flags and requirements and hand them to consumers.
A target without any source files sounds useless. It is actually one of the most useful things in CMake. Interface libraries exist purely to carry requirements to other targets.
The header-only library
The simplest use is a library that is just headers. There is nothing to compile, so it is an INTERFACE library:
mystr/
├── CMakeLists.txt
└── include/mystr/
└── mystr.hpp
CMakeLists.txtcmake_minimum_required(VERSION 4.4) project(mystr LANGUAGES CXX) # INTERFACE. No sources, only requirements for consumers. add_library(mystr INTERFACE) target_include_directories(mystr INTERFACE include) target_compile_features(mystr INTERFACE cxx_std_20)
Consumers get everything with one line:
target_link_libraries(app PRIVATE mystr) # include dir + C++20 requirement, done
The “warnings” bundle
The same idea for flags. One target that is a set of warnings:
CMakeLists.txt# A target named "warnings". It compiles nothing, it carries flags. add_library(warnings INTERFACE) if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") target_compile_options(warnings INTERFACE /W4) else() target_compile_options(warnings INTERFACE -Wall -Wextra -Wpedantic) endif()
target_link_libraries(app PRIVATE warnings)
target_link_libraries(mylib PRIVATE warnings)
target_link_libraries(test_app PRIVATE warnings)
One definition of “warnings”, applied everywhere. Changing it once changes the whole project.
Why
Interface libraries turn repeated boilerplate into a name. Instead of copying an
if(MSVC)block into ten targets, you define it once and link it ten times. You can also compose them.add_library(best_practices INTERFACE)that linkswarningsand sets the C++ standard, thentarget_link_libraries(app PRIVATE best_practices)gives the app everything.
INTERFACE means “consumers only”
compiles itself? consumers inherit?
PRIVATE ✓ ✗
PUBLIC ✓ ✓
INTERFACE ✗ ✓
An INTERFACE target has nothing to compile, so everything on it must be INTERFACE. Include dirs, features, options, links. Since 3.19 an INTERFACE library can carry sources with target_sources(... INTERFACE), but that is a niche “headers as sources for IDEs” trick. Ignore it until you need it.
ALIAS, a stable name
When you rename a library, consumers should not care. An alias gives a target a second, stable name:
add_library(calc STATIC src/calc.cpp)
add_library(calc::calc ALIAS calc) # consumers link calc::calc
The name::name pattern is the convention used by imported targets (lesson 14). Aliases let your project follow the same convention. Note that an alias to an INTERFACE library works too, but aliases cannot be installed or exported. The export story uses a different mechanism (lesson 22).
Pitfall
Do not use an INTERFACE library where a real one is needed. If it has
.cppfiles, make itSTATICorSHARED. An INTERFACE library withPRIVATEcompile flags is a logic error. There is nothing to compile, so the flags vanish.
Note
The full comparison of PRIVATE, PUBLIC and INTERFACE is on the PUBLIC vs PRIVATE vs INTERFACE page.
Read the docs