# Turning CMake Dependency Graphs Into a Reviewable Build Map

**URL:** https://discourse.cmake.org/t/turning-cmake-dependency-graphs-into-a-reviewable-build-map/15756
**Category:** Code
**Created:** [July 23, 2026, 9:29am UTC](https://discourse.cmake.org/t/turning-cmake-dependency-graphs-into-a-reviewable-build-map/15756 "2026-07-23T09:29:47Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![Martyn\_Foster](https://discourse.cmake.org/user_avatar/discourse.cmake.org/martyn_foster/32/6188_2.png) [@Martyn\_Foster](https://discourse.cmake.org/u/Martyn_Foster)
#### Post date: [July 23, 2026, 9:29am UTC](https://discourse.cmake.org/t/turning-cmake-dependency-graphs-into-a-reviewable-build-map/15756/1 "2026-07-23T09:29:47Z")

</div>

Anyone who has inherited a CMake project with a few dozen targets knows the moment: `add_subdirectory` calls nest three or four levels deep, `target_link_libraries` chains cross back on themselves, and a configure error about a missing target gives you a file and line number but no sense of _why_ two libraries ended up depending on each other in the first place. Scrolling through `CMakeLists.txt` files to reconstruct the dependency graph by hand works for small projects, but it stops scaling once you have interface libraries, private/public link keywords, and generator expressions mixed together.

CMake already ships with a way to externalize that graph instead of holding it in your head. Running configure with `cmake --graphviz=deps.dot` writes a Graphviz DOT file describing every target and its link dependencies as CMake understands them at that point in the build. It is a documented, built-in feature, not a plugin, so it works the same way across generators. Once you have the `.dot` file, `dot -Tpng deps.dot -o deps.png` (or `-Tsvg` for something you can zoom into) turns it into an actual picture you can drop into a pull request or a wiki page.

The raw output is usually too dense to be useful on its own, especially in projects that pull in several third-party dependencies through `find_package` or `FetchContent`. CMake lets you tune what gets drawn by placing a `CMakeGraphVizOptions.cmake` file next to your top-level `CMakeLists.txt`. Options like `GRAPHVIZ_IGNORE_TARGETS`, `GRAPHVIZ_EXECUTABLES`, `GRAPHVIZ_STATIC_LIBS`, and `GRAPHVIZ_MODULE_LIBS` let you exclude noise (test targets, imported system libraries) or restrict the graph to a specific target type. It is worth iterating on this file the same way you would iterate on a build target: generate, look at the result, decide what is irrelevant to the question you are actually trying to answer, and regenerate.

When the real issue is a configure-time error rather than a structural review, the graph alone will not explain it. `--trace-expand` and `--debug-output` give you the sequence of calls that led to the failure, and reading that trace alongside the dependency graph is often the fastest way to see whether an error came from a genuinely circular dependency, a missing `target_link_libraries` call, or a target that was referenced before it was defined. The graph tells you the shape of the problem; the trace tells you how CMake got there.

Once a graph is generated and pruned to something legible, some teams want a cleaner version for onboarding documentation or an architecture review slide, rather than the default Graphviz node-and-edge styling. That is a separate, optional step from the CMake work itself, and it is where a general-purpose image tool can be useful as a way to redraw a rough diagram into something more presentable, similar to how a sketch or screenshot might get restyled into a clean mockup. [Nano Banana 2 Lite](https://nanobanana2lite.tools/) is one such third-party, independent tool for that kind of visual cleanup — it is not affiliated with Google or DeepMind, and it has no integration with CMake or Graphviz; it simply takes an image or a prompt and produces a new one. Anyone using a tool like that for a build diagram should treat the output as illustrative only, since it will not preserve exact target names or edge relationships the way the original DOT file does.

The honest limitation of this whole workflow is that the Graphviz export reflects a single configure run, not the build over time. Targets added conditionally behind `if()` blocks, or dependencies that only appear under certain generator expressions, will only show up if that particular configuration path was taken when the graph was generated. For genuinely large monorepos, even a pruned graph can be too big to read as one image, and splitting it by subdirectory or target type becomes necessary. None of that makes the export less useful — it just means the graph is a snapshot for a specific question, not a permanent map, and it is worth regenerating whenever the dependency structure changes enough to matter.
