PUBLIC, PRIVATE and INTERFACE

CMake · Essentials · 12 min

Getting these keywords right is what makes libraries easy to consume and builds correct.

Modern CMake attaches every setting to a target with target_* commands, instead of setting global variables:

  • target_include_directories(core PUBLIC include PRIVATE src)
  • target_compile_definitions(core PRIVATE CORE_INTERNAL=1)
  • target_compile_options(core PRIVATE -Wall)
  • target_compile_features(core PUBLIC cxx_std_20)
  • target_link_libraries(core PUBLIC fmt::fmt PRIVATE zlib)

The keyword decides who gets the setting:

  • PRIVATE: only for building this target. Implementation details: src/ headers, warnings, libraries used only in .cpp files.
  • INTERFACE: only for targets that link to this one, not for itself. Typical for header-only libraries.
  • PUBLIC: both. Use it when the setting appears in your public headers: the public include directory, a dependency whose types appear in your API, a required C++ standard.

The rule of thumb: if a user of your library needs it to compile against your headers, it's PUBLIC; otherwise PRIVATE. Propagation is transitive: if app links core and core links fmt PUBLIC, app gets fmt's include path too.

Avoid the old directory-wide commands include_directories(), add_definitions() and link_libraries(); they apply to every target in the directory and hide which target needs what.

Example

# header-only utility: nothing to compile, only requirements for users
add_library(mathx INTERFACE)
target_include_directories(mathx INTERFACE mathx/include)
target_compile_features(mathx INTERFACE cxx_std_20)

add_library(core STATIC src/engine.cpp)
target_include_directories(core
  PUBLIC  include          # users include <core/engine.h>
  PRIVATE src)             # internal headers
target_compile_definitions(core PRIVATE CORE_BUILDING=1)
target_link_libraries(core
  PUBLIC  mathx            # mathx types appear in core's public headers
  PRIVATE Threads::Threads)

add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE core)
# app gets: include/, mathx/include, C++20
# app does not get: src/, CORE_BUILDING, (Threads only at link time for static core)

Remember

  • PRIVATE: only this target; INTERFACE: only users; PUBLIC: both
  • If it's needed to compile against your headers, make it PUBLIC
  • Use target_* commands, not directory-wide include_directories

Watch out for

  • Making everything PUBLIC, which leaks internal include paths and flags into every dependent target.

Check yourself

  1. A library uses zlib only inside its .cpp files. How should it link zlib?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#target-usage-requirements