5.3.8 · D3Build Systems & Toolchain

Worked examples — CMake — CMakeLists.txt, add_executable, add_library, target_link_libraries

3,510 words16 min readBack to topic

Linked ideas you may want open in another tab: Static vs Dynamic Linking, Compilation Pipeline preprocess-compile-assemble-link, Header files and Include Guards, Package Management find_package.


The scenario matrix

Think of a CMake dependency question as having a few independent "dials". Each row below is a case class — a distinct kind of situation the topic can throw at you. The worked examples that follow each carry a tag like (C3) telling you which cell they cover. Together they cover every row.

# Case class The core question it tests Covered by
C1 PRIVATE leaf (executable) Does propagation past a program mean anything? Ex 1
C2 PRIVATE library link Dependency used only in .cpp — does it leak? Ex 2
C3 PUBLIC library link Dependency named in a header — does it flow? Ex 3
C4 Transitive PUBLIC chain 3+ levels: does the far dep reach the top? Ex 4
C5 PRIVATE breaks the chain Where exactly does propagation stop? Ex 5
C6 INTERFACE / header-only A target with zero sources Ex 6
C7 Diamond dependency Two paths to the same lib — double-link? Ex 7
C8 Degenerate / zero input Empty link, self-link, missing target Ex 8
C9 Real-world word problem Translate a requirement into keywords Ex 9
C10 Exam twist + non-include usage req The "looks PUBLIC but must be PRIVATE" trap, and compile-defs/flags propagation Ex 10
Figure — CMake — CMakeLists.txt, add_executable, add_library, target_link_libraries

Figure s01 — one link keyword answers two independent questions: does target T itself get the dependency D (arrow to "T's own .cpp"), and do the users of T get D (arrow to "users of T")? The three keywords are just the yes/no settings on those two arrows.


Ex 1 — The PRIVATE leaf (C1)


Figure — CMake — CMakeLists.txt, add_executable, add_library, target_link_libraries

Figure s02 — the same three targets under two keywords. Left (PRIVATE): the dashed orange line is a wall — util is used inside core but cannot be included by app. Right (PUBLIC): the teal curved arrow shows util's usage requirements flowing all the way up to app, which can now include util.h. Only one word changed between the two.



Ex 4 — Transitive PUBLIC chain, 3 levels (C4)


Ex 5 — Where PRIVATE breaks the chain (C5)

Figure — CMake — CMakeLists.txt, add_executable, add_library, target_link_libraries

Figure s03 — the four-box chain tests → app → core → util. The two lower edges are PUBLIC, so util's requirements climb up to app. But the top edge tests → app is PRIVATE — the dashed orange "WALL" — so nothing crosses into tests. It inherits zero deps.


Ex 6 — INTERFACE / header-only library (C6)


Ex 7 — Diamond dependency (C7)


Ex 8 — Degenerate & zero inputs (C8)


Ex 9 — Real-world word problem (C9)


Ex 10 — Exam twist + non-include usage requirements (C10)


Recap of the matrix

Recall One-line answer per case class
  • C1 leaf keyword ::: cosmetic — nobody links an executable, so PRIVATE.
  • C2 PRIVATE lib link ::: dep stays hidden; users can't include it (correct encapsulation).
  • C3 PUBLIC lib link ::: dep flows to users; needed when a header exposes it.
  • C4 PUBLIC chain ::: PUBLIC edges compose; far dep reaches the top.
  • C5 PRIVATE in middle ::: acts as a wall; nothing below it propagates upward.
  • C6 INTERFACE ::: no sources → only the "users need it" half exists.
  • C7 diamond ::: CMake dedupes the dep set → linked once, no duplicate symbols.
  • C8 degenerate ::: name-based graph; order among targets free; bad names fail at link, cycles at configure.
  • C9 word problem ::: internal→PRIVATE, exposed-in-header→PUBLIC, header-only→INTERFACE.
  • C10 exam twist ::: forward-declared→PRIVATE; and defs/flags propagate by the SAME keyword rule as includes.

See also: Makefiles, Ninja build system (the generators CMake feeds), Static vs Dynamic Linking (what STATIC/SHARED actually produce), and the parent CMake note for the base rules.