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 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.