5.3.8 · D5Build Systems & Toolchain
Question bank — CMake — CMakeLists.txt, add_executable, add_library, target_link_libraries
True or false — justify
True or false: CMake compiles your C++ code.
False. CMake is a meta build-system — it generates native build files (Makefiles, Ninja files, VS projects), and the generator then invokes the actual compiler. See Makefiles for what it produces.
True or false: You must list add_library before the target_link_libraries that uses it, matching link order.
False. CMake builds a dependency graph and computes link order itself; you only declare what depends on what. Raw
g++ is order-sensitive, but CMake is not.True or false: target_link_libraries(app PRIVATE core) on an executable is a mistake because nothing propagates from an app.
False — PRIVATE is exactly right. Nothing links against an executable, so propagation past it is meaningless; PRIVATE is the correct minimal choice, not an error.
True or false: A STATIC library and a SHARED library are interchangeable from the CMakeLists' point of view.
Mostly true for the commands, false for behaviour. You write the same TLL either way, but STATIC (
.a/.lib) is copied into the binary while SHARED (.so/.dll) loads at runtime — see Static vs Dynamic Linking.True or false: Setting include_directories() at the top of the file is equivalent to target_include_directories(... PUBLIC ...).
False.
include_directories() is directory-scoped and leaks into every target in that directory; the target_* form attaches include dirs to one target with a controlled propagation keyword.True or false: An INTERFACE library must have at least one .cpp source.
False. An INTERFACE library has no implementation by definition — its whole purpose is to carry usage requirements (flags, include dirs) to users, e.g. a header-only lib.
True or false: project() can be safely omitted if you only build C++.
False. Without
project() CMake auto-injects a default project(Project) enabling C and C++, so it warns you and — crucially — until it runs, variables like ${PROJECT_SOURCE_DIR}, ${PROJECT_NAME}, ${CMAKE_CXX_COMPILER} and the whole toolchain/ABI detection are not set up. Always declare it explicitly.True or false: Editing CMakeLists.txt requires you to manually re-run the configure step.
False.
cmake --build build detects the changed script and re-triggers the configure phase automatically before building.True or false: Adding a shared .cpp directly to two executables is fine because it compiles.
False in practice. It compiles, but the file is recompiled per target and there's no single place defining its interface — wrap it in
add_library once instead.Spot the error
target_link_libraries(calc mathlib) — what's missing?
The propagation keyword (
PUBLIC/PRIVATE/INTERFACE). This "plain" (keyword-less) signature is a legacy holdover that fills the old LINK_INTERFACE_LIBRARIES variable and can't express per-scope propagation; CMake kept it only for backward compatibility and forbids mixing plain and keyword forms on one target, so modern code must pick a keyword to get well-defined PUBLIC/PRIVATE/INTERFACE flow.add_library(hdronly INTERFACE header.h) — what's the real error here?
The API violation: an INTERFACE library is not allowed to list any sources at all (not even a header) in this signature — CMake rejects it. INTERFACE libraries have no build step, so you write just
add_library(hdronly INTERFACE) and attach the header directory with target_include_directories(hdronly INTERFACE include). The keyword you use to link it (INTERFACE, so users get the headers) is a separate, downstream question — don't let it distract from the fact this line won't even configure.add_executable(calc app/main.cpp)
add_library(mathlib STATIC math/add.cpp)
target_link_libraries(calc PRIVATE mathlib)main.cpp does #include "add.h" but the build fails to find it. Fix and explain?
mathlib never exported its include dir. Add target_include_directories(mathlib PUBLIC math). PUBLIC vs PRIVATE matters here: add.h is part of mathlib's public interface (users #include it), so the dir must ride the interface door out to calc. With PRIVATE, the dir would stay inside mathlib's own compilation and calc still couldn't find add.h. See Header files and Include Guards.cmake_minimum_required is placed on line 3, after project(). Why is that a bug?
cmake_minimum_required must be first — it sets policy behaviour that affects how every later command (including project()) is interpreted; placing it later triggers a hard error/warning.You run cmake --build build in a fresh clone with no build/ folder. What breaks?
There are no generated build files yet — you must configure first with
cmake -S . -B build, then build. The two phases are configure then build.target_link_libraries(mathlib PUBLIC fmt) where fmt only appears inside add.cpp, never in add.h. What's the mistake?
It should be PRIVATE. Since
fmt doesn't appear in mathlib's public header, marking it PUBLIC needlessly forces every user of mathlib to also see fmt — bloated, over-coupled builds.Why questions
Why does CMake insist you name a minimum version at all?
CMake's behaviour ("policies") changes between versions; declaring a minimum makes builds reproducible and lets CMake pick the right default behaviours instead of guessing.
Why prefer target_* commands over the global include_directories/link_libraries?
target_* commands scope settings to a single target with a propagation keyword, so flags don't leak into unrelated targets — the essence of "target-centric" modern CMake.Why does the PUBLIC/PRIVATE/INTERFACE choice matter if the code compiles anyway?
It controls transitive propagation. Wrong choice = downstream targets either miss required headers/flags (broken) or inherit ones they don't need (bloated, over-coupled).
Why do you build out-of-source (in a separate build/ folder)?
So generated junk (Makefiles, object files, cache) never pollutes your source tree, keeping the repo clean and letting you delete
build/ to start fresh.Why does CMake compute link order for you instead of trusting your declaration order?
Because it builds a full dependency graph and topologically orders the linker — this eliminates the fragile, order-sensitive
-lcore -lutil bugs you'd hit hand-writing linker commands.Why is find_package preferred over hard-coding a library's path?
find_package locates a dependency portably across machines and hands you targets with correct include dirs and flags already attached — see Package Management find_package. Hard-coded paths break on other systems.Why is CMake called a "meta" build-system rather than a build-system?
Because it doesn't run the compiler itself — it generates the input for a real build tool (Make, Ninja, MSBuild), one layer above the actual build.
Edge cases
If core links util as PUBLIC and app links core, does app also link util?
Yes. PUBLIC makes
util part of core's interface, so it flows transitively into app, including util's include dirs. (Trace the arrow in the propagation diagram above.)Same chain but core links util as PRIVATE — does app link util?
No, and correctly so. PRIVATE hides
util inside core's .cpp; app links core but never sees util's headers, which is right since it shouldn't need them.What does linking a target with zero sources but INTERFACE dependencies actually do?
It adds no compiled code but forwards usage requirements (flags, include dirs, transitive deps) to anyone who links it — the whole point of an INTERFACE/header-only target.
What happens if two libraries in the graph depend on each other (a cycle)?
CMake cannot topologically order a genuine cycle for STATIC libs and will error/require workarounds; the dependency graph must be acyclic (a DAG).
Can an executable be a dependency of another target via target_link_libraries?
Generally no — you link against libraries, not executables; an executable is a terminal node, so nothing propagates from it.
What if add_executable lists a source file that doesn't exist yet (generated later)?
CMake needs to know it's generated (e.g. via a custom command output) or configure fails; a plain missing file is treated as an error since nothing produces it.
Does removing the build/ directory lose any of your project setup?
No. All your configuration lives in
CMakeLists.txt; build/ is fully regenerable — deleting it just forces a clean re-configure.Active recall
Recall Fastest self-check
- CMake's job in one word? → generate (not compile).
- Keyword when a dependency appears in your header? → PUBLIC.
- Keyword when it appears only in your
.cpp? → PRIVATE. - Keyword for a header-only library's own requirements? → INTERFACE.
- Who decides link order? → CMake, from the dependency graph.