summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Modules/CMakeGraphVizOptions.cmake146
-rw-r--r--Source/CMakeLists.txt2
-rw-r--r--Source/cmGraphVizWriter.cxx723
-rw-r--r--Source/cmGraphVizWriter.h93
-rw-r--r--Source/cmLinkItemGraphVisitor.cxx142
-rw-r--r--Source/cmLinkItemGraphVisitor.h75
-rw-r--r--Source/cmake.cxx7
-rw-r--r--Tests/RunCMake/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/Graphviz/CMakeGraphVizOptions.cmake.in1
-rw-r--r--Tests/RunCMake/Graphviz/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/Graphviz/GraphvizTestProject.cmake58
-rw-r--r--Tests/RunCMake/Graphviz/RunCMakeTest.cmake82
-rw-r--r--Tests/RunCMake/Graphviz/default_options-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_custom_targets.dot52
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_default_options.dot50
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_dependers_files.dot50
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_executables.dot44
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_external_libs.dot46
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_graphic_libs.dot35
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_interface_libs.dot43
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_module_libs.dot44
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_object_libs.dot48
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_per_target_files.dot50
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_shared_libs.dot44
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_static_libs.dot42
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_unknown_libs.dot48
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_header.dot50
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_name.dot50
-rw-r--r--Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_node_prefix.dot50
-rw-r--r--Tests/RunCMake/Graphviz/no_dependers_files-check.cmake4
-rw-r--r--Tests/RunCMake/Graphviz/no_executables-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_external_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_graphic_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_interface_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_module_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_object_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_per_target_files-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_shared_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_static_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/no_unknown_libs-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/set_graph_header-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/set_graph_name-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/set_node_prefix-check.cmake5
-rw-r--r--Tests/RunCMake/Graphviz/test_project/core_library.c3
-rw-r--r--Tests/RunCMake/Graphviz/test_project/graphic_library.c3
-rw-r--r--Tests/RunCMake/Graphviz/test_project/main.c4
-rw-r--r--Tests/RunCMake/Graphviz/test_project/module.c3
-rw-r--r--Tests/RunCMake/Graphviz/test_project/third_party_project/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/RunCMake.cmake23
-rwxr-xr-xbootstrap1
50 files changed, 1692 insertions, 501 deletions
diff --git a/Modules/CMakeGraphVizOptions.cmake b/Modules/CMakeGraphVizOptions.cmake
index 1911e7392e..be4a3be365 100644
--- a/Modules/CMakeGraphVizOptions.cmake
+++ b/Modules/CMakeGraphVizOptions.cmake
@@ -5,119 +5,145 @@
CMakeGraphVizOptions
--------------------
-The builtin graphviz support of CMake.
+The builtin Graphviz support of CMake.
-Variables specific to the graphviz support
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Generating Graphviz files
+^^^^^^^^^^^^^^^^^^^^^^^^^
-CMake
-can generate `graphviz <http://www.graphviz.org/>`_ files, showing the dependencies between the
-targets in a project and also external libraries which are linked
-against. When CMake is run with the ``--graphviz=foo.dot`` option, it will
-produce:
+CMake can generate `Graphviz <https://www.graphviz.org/>`_ files showing the
+dependencies between the targets in a project, as well as external libraries
+which are linked against.
-* a ``foo.dot`` file showing all dependencies in the project
-* a ``foo.dot.<target>`` file for each target, file showing on which other targets the respective target depends
-* a ``foo.dot.<target>.dependers`` file, showing which other targets depend on the respective target
+When running CMake with the ``--graphviz=foo.dot`` option, it produces:
-The different dependency types ``PUBLIC``, ``PRIVATE`` and ``INTERFACE``
-are represented as solid, dashed and dotted edges.
+* a ``foo.dot`` file, showing all dependencies in the project
+* a ``foo.dot.<target>`` file for each target, showing on which other targets
+ it depends
+* a ``foo.dot.<target>.dependers`` file for each target, showing which other
+ targets depend on it
-This can result in huge graphs. Using the file
-``CMakeGraphVizOptions.cmake`` the look and content of the generated
-graphs can be influenced. This file is searched first in
-:variable:`CMAKE_BINARY_DIR` and then in :variable:`CMAKE_SOURCE_DIR`. If found, it is
-read and the variables set in it are used to adjust options for the
-generated graphviz files.
+Those .dot files can be converted to images using the *dot* command from the
+Graphviz package:
-.. variable:: GRAPHVIZ_GRAPH_TYPE
+.. code-block:: shell
- The graph type.
+ dot -Tpng -o foo.png foo.dot
- * Mandatory : NO
- * Default : "digraph"
+The different dependency types ``PUBLIC``, ``INTERFACE`` and ``PRIVATE``
+are represented as solid, dashed and dotted edges.
- Valid graph types are:
+Variables specific to the Graphviz support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- * "graph" : Nodes are joined with lines
- * "digraph" : Nodes are joined with arrows showing direction
- * "strict graph" : Like "graph" but max one line between each node
- * "strict digraph" : Like "graph" but max one line between each node in each direction
+The resulting graphs can be huge. The look and content of the generated graphs
+can be controlled using the file ``CMakeGraphVizOptions.cmake``. This file is
+first searched in :variable:`CMAKE_BINARY_DIR`, and then in
+:variable:`CMAKE_SOURCE_DIR`. If found, the variables set in it are used to
+adjust options for the generated Graphviz files.
.. variable:: GRAPHVIZ_GRAPH_NAME
The graph name.
- * Mandatory : NO
- * Default : "GG"
+ * Mandatory: NO
+ * Default: value of :variable:`CMAKE_PROJECT_NAME`
.. variable:: GRAPHVIZ_GRAPH_HEADER
- The header written at the top of the graphviz file.
+ The header written at the top of the Graphviz files.
- * Mandatory : NO
- * Default : "node [n fontsize = "12"];"
+ * Mandatory: NO
+ * Default: "node [ fontsize = "12" ];"
.. variable:: GRAPHVIZ_NODE_PREFIX
- The prefix for each node in the graphviz file.
+ The prefix for each node in the Graphviz files.
- * Mandatory : NO
- * Default : "node"
+ * Mandatory: NO
+ * Default: "node"
.. variable:: GRAPHVIZ_EXECUTABLES
- Set this to FALSE to exclude executables from the generated graphs.
+ Set to FALSE to exclude executables from the generated graphs.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: TRUE
.. variable:: GRAPHVIZ_STATIC_LIBS
- Set this to FALSE to exclude static libraries from the generated graphs.
+ Set to FALSE to exclude static libraries from the generated graphs.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: TRUE
.. variable:: GRAPHVIZ_SHARED_LIBS
- Set this to FALSE to exclude shared libraries from the generated graphs.
+ Set to FALSE to exclude shared libraries from the generated graphs.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: TRUE
.. variable:: GRAPHVIZ_MODULE_LIBS
- Set this to FALSE to exclude module libraries from the generated graphs.
+ Set to FALSE to exclude module libraries from the generated graphs.
+
+ * Mandatory: NO
+ * Default: TRUE
+
+.. variable:: GRAPHVIZ_INTERFACE_LIBS
+
+ Set to FALSE to exclude interface libraries from the generated graphs.
+
+ * Mandatory: NO
+ * Default: TRUE
- * Mandatory : NO
- * Default : TRUE
+.. variable:: GRAPHVIZ_OBJECT_LIBS
+
+ Set to FALSE to exclude object libraries from the generated graphs.
+
+ * Mandatory: NO
+ * Default: TRUE
+
+.. variable:: GRAPHVIZ_UNKNOWN_LIBS
+
+ Set to FALSE to exclude unknown libraries from the generated graphs.
+
+ * Mandatory: NO
+ * Default: TRUE
.. variable:: GRAPHVIZ_EXTERNAL_LIBS
- Set this to FALSE to exclude external libraries from the generated graphs.
+ Set to FALSE to exclude external libraries from the generated graphs.
+
+ * Mandatory: NO
+ * Default: TRUE
+
+.. variable:: GRAPHVIZ_CUSTOM_TARGETS
+
+ Set to TRUE to include custom targets in the generated graphs.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: FALSE
.. variable:: GRAPHVIZ_IGNORE_TARGETS
- A list of regular expressions for ignoring targets.
+ A list of regular expressions for names of targets to exclude from the
+ generated graphs.
- * Mandatory : NO
- * Default : empty
+ * Mandatory: NO
+ * Default: empty
.. variable:: GRAPHVIZ_GENERATE_PER_TARGET
- Set this to FALSE to exclude per target graphs ``foo.dot.<target>``.
+ Set to FALSE to not generate per-target graphs ``foo.dot.<target>``.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: TRUE
.. variable:: GRAPHVIZ_GENERATE_DEPENDERS
- Set this to FALSE to exclude depender graphs ``foo.dot.<target>.dependers``.
+ Set to FALSE to not generate depender graphs ``foo.dot.<target>.dependers``.
- * Mandatory : NO
- * Default : TRUE
+ * Mandatory: NO
+ * Default: TRUE
#]=======================================================================]
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 63e08de0f5..6087e53a29 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -289,6 +289,8 @@ set(SRCS
cmGeneratorExpression.h
cmGeneratorTarget.cxx
cmGeneratorTarget.h
+ cmLinkItemGraphVisitor.cxx
+ cmLinkItemGraphVisitor.h
cmGetPipes.cxx
cmGetPipes.h
cmGlobalCommonGenerator.cxx
diff --git a/Source/cmGraphVizWriter.cxx b/Source/cmGraphVizWriter.cxx
index e0d545d86d..7759c5f9d3 100644
--- a/Source/cmGraphVizWriter.cxx
+++ b/Source/cmGraphVizWriter.cxx
@@ -2,174 +2,190 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmGraphVizWriter.h"
-#include <cstddef>
+#include <cctype>
#include <iostream>
#include <memory>
-#include <sstream>
+#include <set>
#include <utility>
+#include <cm/memory>
+
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
+#include "cmLinkItem.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmState.h"
#include "cmStateSnapshot.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
-#include "cmTarget.h"
#include "cmake.h"
namespace {
-enum LinkLibraryScopeType
-{
- LLT_SCOPE_PUBLIC,
- LLT_SCOPE_PRIVATE,
- LLT_SCOPE_INTERFACE
-};
-const char* const GRAPHVIZ_PRIVATE_EDEGE_STYLE = "dashed";
-const char* const GRAPHVIZ_INTERFACE_EDEGE_STYLE = "dotted";
+char const* const GRAPHVIZ_EDGE_STYLE_PUBLIC = "solid";
+char const* const GRAPHVIZ_EDGE_STYLE_INTERFACE = "dashed";
+char const* const GRAPHVIZ_EDGE_STYLE_PRIVATE = "dotted";
-std::string getLinkLibraryStyle(const LinkLibraryScopeType& type)
-{
- std::string style;
- switch (type) {
- case LLT_SCOPE_PRIVATE:
- style = "[style = " + std::string(GRAPHVIZ_PRIVATE_EDEGE_STYLE) + "]";
- break;
- case LLT_SCOPE_INTERFACE:
- style = "[style = " + std::string(GRAPHVIZ_INTERFACE_EDEGE_STYLE) + "]";
- break;
- default:
- break;
- }
- return style;
-}
+char const* const GRAPHVIZ_NODE_SHAPE_EXECUTABLE = "egg"; // egg-xecutable
+
+// Normal libraries.
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC = "octagon";
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED = "doubleoctagon";
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE = "tripleoctagon";
-const char* getShapeForTarget(const cmGeneratorTarget* target)
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE = "pentagon";
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT = "hexagon";
+char const* const GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN = "septagon";
+
+char const* const GRAPHVIZ_NODE_SHAPE_UTILITY = "box";
+
+const char* getShapeForTarget(const cmLinkItem& item)
{
- if (!target) {
- return "ellipse";
+ if (item.Target == nullptr) {
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN;
}
- switch (target->GetType()) {
+ switch (item.Target->GetType()) {
case cmStateEnums::EXECUTABLE:
- return "house";
+ return GRAPHVIZ_NODE_SHAPE_EXECUTABLE;
case cmStateEnums::STATIC_LIBRARY:
- return "diamond";
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC;
case cmStateEnums::SHARED_LIBRARY:
- return "polygon";
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED;
case cmStateEnums::MODULE_LIBRARY:
- return "octagon";
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE;
+ case cmStateEnums::OBJECT_LIBRARY:
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT;
+ case cmStateEnums::UTILITY:
+ return GRAPHVIZ_NODE_SHAPE_UTILITY;
+ case cmStateEnums::INTERFACE_LIBRARY:
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE;
+ case cmStateEnums::UNKNOWN_LIBRARY:
default:
- break;
+ return GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN;
}
+}
+}
- return "box";
+cmGraphVizWriter::cmGraphVizWriter(std::string const& fileName,
+ const cmGlobalGenerator* globalGenerator)
+ : FileName(fileName)
+ , GlobalFileStream(fileName)
+ , GraphName(globalGenerator->GetSafeGlobalSetting("CMAKE_PROJECT_NAME"))
+ , GraphHeader("node [\n fontsize = \"12\"\n];")
+ , GraphNodePrefix("node")
+ , GlobalGenerator(globalGenerator)
+ , NextNodeId(0)
+ , GenerateForExecutables(true)
+ , GenerateForStaticLibs(true)
+ , GenerateForSharedLibs(true)
+ , GenerateForModuleLibs(true)
+ , GenerateForInterfaceLibs(true)
+ , GenerateForObjectLibs(true)
+ , GenerateForUnknownLibs(true)
+ , GenerateForCustomTargets(false)
+ , GenerateForExternals(true)
+ , GeneratePerTarget(true)
+ , GenerateDependers(true)
+{
}
-std::map<std::string, LinkLibraryScopeType> getScopedLinkLibrariesFromTarget(
- cmTarget* Target, const cmGlobalGenerator* globalGenerator)
+cmGraphVizWriter::~cmGraphVizWriter()
{
- char sep = ';';
- std::map<std::string, LinkLibraryScopeType> tokens;
- size_t start = 0;
- size_t end = 0;
+ this->WriteFooter(this->GlobalFileStream);
- const char* pInterfaceLinkLibraries =
- Target->GetProperty("INTERFACE_LINK_LIBRARIES");
- const char* pLinkLibraries = Target->GetProperty("LINK_LIBRARIES");
+ for (auto& fileStream : this->PerTargetFileStreams) {
+ this->WriteFooter(*fileStream.second);
+ }
- if (!pInterfaceLinkLibraries && !pLinkLibraries) {
- return tokens; // target is not linked against any other libraries
+ for (auto& fileStream : this->TargetDependersFileStreams) {
+ this->WriteFooter(*fileStream.second);
}
+}
- // make sure we don't touch a null-ptr
- auto interfaceLinkLibraries =
- std::string(pInterfaceLinkLibraries ? pInterfaceLinkLibraries : "");
- auto linkLibraries = std::string(pLinkLibraries ? pLinkLibraries : "");
+void cmGraphVizWriter::VisitGraph(std::string const&)
+{
+ this->WriteHeader(GlobalFileStream, this->GraphName);
+ this->WriteLegend(GlobalFileStream);
+}
- // first extract interfaceLinkLibraries
- while (start < interfaceLinkLibraries.length()) {
+void cmGraphVizWriter::OnItem(cmLinkItem const& item)
+{
+ if (this->ItemExcluded(item)) {
+ return;
+ }
- if ((end = interfaceLinkLibraries.find(sep, start)) == std::string::npos) {
- end = interfaceLinkLibraries.length();
- }
+ NodeNames[item.AsStr()] = cmStrCat(GraphNodePrefix, NextNodeId);
+ ++NextNodeId;
- std::string element = interfaceLinkLibraries.substr(start, end - start);
- if (globalGenerator->IsAlias(element)) {
- const auto tgt = globalGenerator->FindTarget(element);
- if (tgt) {
- element = tgt->GetName();
- }
- }
+ this->WriteNode(this->GlobalFileStream, item);
- if (std::string::npos == element.find("$<LINK_ONLY:", 0)) {
- // we assume first, that this library is an interface library.
- // if we find it again in the linklibraries property, we promote it to an
- // public library.
- tokens[element] = LLT_SCOPE_INTERFACE;
- } else {
- // this is an private linked static library.
- // we take care of this case in the second iterator.
- }
- start = end + 1;
+ if (this->GeneratePerTarget) {
+ this->CreateTargetFile(this->PerTargetFileStreams, item);
}
- // second extract linkLibraries
- start = 0;
- while (start < linkLibraries.length()) {
-
- if ((end = linkLibraries.find(sep, start)) == std::string::npos) {
- end = linkLibraries.length();
- }
+ if (this->GenerateDependers) {
+ this->CreateTargetFile(this->TargetDependersFileStreams, item,
+ ".dependers");
+ }
+}
- std::string element = linkLibraries.substr(start, end - start);
- if (globalGenerator->IsAlias(element)) {
- const auto tgt = globalGenerator->FindTarget(element);
- if (tgt) {
- element = tgt->GetName();
- }
- }
+void cmGraphVizWriter::CreateTargetFile(FileStreamMap& fileStreamMap,
+ cmLinkItem const& item,
+ std::string const& fileNameSuffix)
+{
+ auto const pathSafeItemName = PathSafeString(item.AsStr());
+ auto const perTargetFileName =
+ cmStrCat(this->FileName, '.', pathSafeItemName, fileNameSuffix);
+ auto perTargetFileStream =
+ cm::make_unique<cmGeneratedFileStream>(perTargetFileName);
- if (tokens.find(element) == tokens.end()) {
- // this library is not found in interfaceLinkLibraries but in
- // linkLibraries.
- // this results in a private linked library.
- tokens[element] = LLT_SCOPE_PRIVATE;
- } else if (LLT_SCOPE_INTERFACE == tokens[element]) {
- // this library is found in interfaceLinkLibraries and linkLibraries.
- // this results in a public linked library.
- tokens[element] = LLT_SCOPE_PUBLIC;
- } else {
- // private and public linked libraries should not be changed anymore.
- }
+ this->WriteHeader(*perTargetFileStream, item.AsStr());
+ this->WriteNode(*perTargetFileStream, item);
- start = end + 1;
- }
+ fileStreamMap.emplace(item.AsStr(), std::move(perTargetFileStream));
+}
- return tokens;
+void cmGraphVizWriter::OnDirectLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee,
+ DependencyType dt)
+{
+ this->VisitLink(depender, dependee, true, GetEdgeStyle(dt));
}
+
+void cmGraphVizWriter::OnIndirectLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee)
+{
+ this->VisitLink(depender, dependee, false);
}
-cmGraphVizWriter::cmGraphVizWriter(const cmGlobalGenerator* globalGenerator)
- : GraphType("digraph")
- , GraphName("GG")
- , GraphHeader("node [\n fontsize = \"12\"\n];")
- , GraphNodePrefix("node")
- , GlobalGenerator(globalGenerator)
- , LocalGenerators(globalGenerator->GetLocalGenerators())
- , GenerateForExecutables(true)
- , GenerateForStaticLibs(true)
- , GenerateForSharedLibs(true)
- , GenerateForModuleLibs(true)
- , GenerateForInterface(true)
- , GenerateForExternals(true)
- , GeneratePerTarget(true)
- , GenerateDependers(true)
- , HaveTargetsAndLibs(false)
+void cmGraphVizWriter::VisitLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee, bool isDirectLink,
+ std::string const& scopeType)
{
+ if (this->ItemExcluded(depender) || this->ItemExcluded(dependee)) {
+ return;
+ }
+
+ if (!isDirectLink) {
+ return;
+ }
+
+ this->WriteConnection(this->GlobalFileStream, depender, dependee, scopeType);
+
+ if (this->GeneratePerTarget) {
+ auto fileStream = PerTargetFileStreams[depender.AsStr()].get();
+ this->WriteNode(*fileStream, dependee);
+ this->WriteConnection(*fileStream, depender, dependee, scopeType);
+ }
+
+ if (this->GenerateDependers) {
+ auto fileStream = TargetDependersFileStreams[dependee.AsStr()].get();
+ this->WriteNode(*fileStream, depender);
+ this->WriteConnection(*fileStream, depender, dependee, scopeType);
+ }
}
void cmGraphVizWriter::ReadSettings(
@@ -208,7 +224,6 @@ void cmGraphVizWriter::ReadSettings(
} \
} while (false)
- __set_if_set(this->GraphType, "GRAPHVIZ_GRAPH_TYPE");
__set_if_set(this->GraphName, "GRAPHVIZ_GRAPH_NAME");
__set_if_set(this->GraphHeader, "GRAPHVIZ_GRAPH_HEADER");
__set_if_set(this->GraphNodePrefix, "GRAPHVIZ_NODE_PREFIX");
@@ -225,7 +240,10 @@ void cmGraphVizWriter::ReadSettings(
__set_bool_if_set(this->GenerateForStaticLibs, "GRAPHVIZ_STATIC_LIBS");
__set_bool_if_set(this->GenerateForSharedLibs, "GRAPHVIZ_SHARED_LIBS");
__set_bool_if_set(this->GenerateForModuleLibs, "GRAPHVIZ_MODULE_LIBS");
- __set_bool_if_set(this->GenerateForInterface, "GRAPHVIZ_INTERFACE");
+ __set_bool_if_set(this->GenerateForInterfaceLibs, "GRAPHVIZ_INTERFACE_LIBS");
+ __set_bool_if_set(this->GenerateForObjectLibs, "GRAPHVIZ_OBJECT_LIBS");
+ __set_bool_if_set(this->GenerateForUnknownLibs, "GRAPHVIZ_UNKNOWN_LIBS");
+ __set_bool_if_set(this->GenerateForCustomTargets, "GRAPHVIZ_CUSTOM_TARGETS");
__set_bool_if_set(this->GenerateForExternals, "GRAPHVIZ_EXTERNAL_LIBS");
__set_bool_if_set(this->GeneratePerTarget, "GRAPHVIZ_GENERATE_PER_TARGET");
__set_bool_if_set(this->GenerateDependers, "GRAPHVIZ_GENERATE_DEPENDERS");
@@ -248,329 +266,170 @@ void cmGraphVizWriter::ReadSettings(
}
}
-// Iterate over all targets and write for each one a graph which shows
-// which other targets depend on it.
-void cmGraphVizWriter::WriteTargetDependersFiles(const std::string& fileName)
+void cmGraphVizWriter::Write()
{
- if (!this->GenerateDependers) {
- return;
- }
-
- this->CollectTargetsAndLibs();
-
- for (auto const& ptr : this->TargetPtrs) {
- if (ptr.second == nullptr) {
- continue;
- }
-
- if (!this->GenerateForTargetType(ptr.second->GetType())) {
- continue;
- }
-
- std::string currentFilename =
- cmStrCat(fileName, '.', ptr.first, ".dependers");
-
- cmGeneratedFileStream str(currentFilename);
- if (!str) {
- return;
+ auto gg = this->GlobalGenerator;
+
+ this->VisitGraph(gg->GetName());
+
+ // We want to traverse in a determined order, such that the output is always
+ // the same for a given project (this makes tests reproducible, etc.)
+ std::set<cmGeneratorTarget const*, cmGeneratorTarget::StrictTargetComparison>
+ sortedGeneratorTargets;
+
+ for (cmLocalGenerator const* lg : gg->GetLocalGenerators()) {
+ for (cmGeneratorTarget const* gt : lg->GetGeneratorTargets()) {
+ // Reserved targets have inconsistent names across platforms (e.g. 'all'
+ // vs. 'ALL_BUILD'), which can disrupt the traversal ordering.
+ // We don't need or want them anyway.
+ if (!cmGlobalGenerator::IsReservedTarget(gt->GetName())) {
+ sortedGeneratorTargets.insert(gt);
+ }
}
-
- std::set<std::string> insertedConnections;
- std::set<std::string> insertedNodes;
-
- std::cout << "Writing " << currentFilename << "..." << std::endl;
- this->WriteHeader(str);
-
- this->WriteDependerConnections(ptr.first, insertedNodes,
- insertedConnections, str);
-
- this->WriteFooter(str);
- }
-}
-
-// Iterate over all targets and write for each one a graph which shows
-// on which targets it depends.
-void cmGraphVizWriter::WritePerTargetFiles(const std::string& fileName)
-{
- if (!this->GeneratePerTarget) {
- return;
}
- this->CollectTargetsAndLibs();
-
- for (auto const& ptr : this->TargetPtrs) {
- if (ptr.second == nullptr) {
- continue;
- }
-
- if (!this->GenerateForTargetType(ptr.second->GetType())) {
- continue;
- }
-
- std::set<std::string> insertedConnections;
- std::set<std::string> insertedNodes;
-
- std::string currentFilename = cmStrCat(fileName, '.', ptr.first);
- cmGeneratedFileStream str(currentFilename);
- if (!str) {
- return;
- }
-
- std::cout << "Writing " << currentFilename << "..." << std::endl;
- this->WriteHeader(str);
-
- this->WriteConnections(ptr.first, insertedNodes, insertedConnections, str);
- this->WriteFooter(str);
+ for (auto const gt : sortedGeneratorTargets) {
+ auto item = cmLinkItem(gt, gt->GetBacktrace());
+ this->VisitItem(item);
}
}
-void cmGraphVizWriter::WriteGlobalFile(const std::string& fileName)
+void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& fs,
+ const std::string& name)
{
- this->CollectTargetsAndLibs();
-
- cmGeneratedFileStream str(fileName);
- if (!str) {
- return;
- }
- this->WriteHeader(str);
-
- std::cout << "Writing " << fileName << "..." << std::endl;
-
- std::set<std::string> insertedConnections;
- std::set<std::string> insertedNodes;
-
- for (auto const& ptr : this->TargetPtrs) {
- if (ptr.second == nullptr) {
- continue;
- }
-
- if (!this->GenerateForTargetType(ptr.second->GetType())) {
- continue;
- }
-
- this->WriteConnections(ptr.first, insertedNodes, insertedConnections, str);
- }
- this->WriteFooter(str);
+ auto const escapedGraphName = EscapeForDotFile(name);
+ fs << "digraph \"" << escapedGraphName << "\" {" << std::endl;
+ fs << this->GraphHeader << std::endl;
}
-void cmGraphVizWriter::WriteHeader(cmGeneratedFileStream& str) const
+void cmGraphVizWriter::WriteFooter(cmGeneratedFileStream& fs)
{
- str << this->GraphType << " \"" << this->GraphName << "\" {" << std::endl;
- str << this->GraphHeader << std::endl;
+ fs << "}" << std::endl;
}
-void cmGraphVizWriter::WriteFooter(cmGeneratedFileStream& str) const
+void cmGraphVizWriter::WriteLegend(cmGeneratedFileStream& fs)
{
- str << "}" << std::endl;
+ // Note that the subgraph name must start with "cluster", as done here, to
+ // make Graphviz layout engines do the right thing and keep the nodes
+ // together.
+ fs << "subgraph clusterLegend {" << std::endl;
+ fs << " label = \"Legend\";" << std::endl;
+ // Set the color of the box surrounding the legend.
+ fs << " color = black;" << std::endl;
+ // We use invisible edges just to enforce the layout.
+ fs << " edge [ style = invis ];" << std::endl;
+
+ // Nodes.
+ fs << " legendNode0 [ label = \"Executable\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_EXECUTABLE << " ];" << std::endl;
+
+ fs << " legendNode1 [ label = \"Static Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_STATIC << " ];" << std::endl;
+ fs << " legendNode2 [ label = \"Shared Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_SHARED << " ];" << std::endl;
+ fs << " legendNode3 [ label = \"Module Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_MODULE << " ];" << std::endl;
+
+ fs << " legendNode4 [ label = \"Interface Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_INTERFACE << " ];" << std::endl;
+ fs << " legendNode5 [ label = \"Object Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_OBJECT << " ];" << std::endl;
+ fs << " legendNode6 [ label = \"Unknown Library\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_LIBRARY_UNKNOWN << " ];" << std::endl;
+
+ fs << " legendNode7 [ label = \"Custom Target\", shape = "
+ << GRAPHVIZ_NODE_SHAPE_UTILITY << " ];" << std::endl;
+
+ // Edges.
+ // Some of those are dummy (invisible) edges to enforce a layout.
+ fs << " legendNode0 -> legendNode1 [ style = " << GRAPHVIZ_EDGE_STYLE_PUBLIC
+ << " ];" << std::endl;
+ fs << " legendNode0 -> legendNode2 [ style = " << GRAPHVIZ_EDGE_STYLE_PUBLIC
+ << " ];" << std::endl;
+ fs << " legendNode0 -> legendNode3;" << std::endl;
+
+ fs << " legendNode1 -> legendNode4 [ label = \"Interface\", style = "
+ << GRAPHVIZ_EDGE_STYLE_INTERFACE << " ];" << std::endl;
+ fs << " legendNode2 -> legendNode5 [ label = \"Private\", style = "
+ << GRAPHVIZ_EDGE_STYLE_PRIVATE << " ];" << std::endl;
+ fs << " legendNode3 -> legendNode6 [ style = " << GRAPHVIZ_EDGE_STYLE_PUBLIC
+ << " ];" << std::endl;
+
+ fs << " legendNode0 -> legendNode7;" << std::endl;
+
+ fs << "}" << std::endl;
}
-void cmGraphVizWriter::WriteConnections(
- const std::string& targetName, std::set<std::string>& insertedNodes,
- std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const
+void cmGraphVizWriter::WriteNode(cmGeneratedFileStream& fs,
+ cmLinkItem const& item)
{
- auto targetPtrIt = this->TargetPtrs.find(targetName);
+ auto const& itemName = item.AsStr();
+ auto const& nodeName = this->NodeNames[itemName];
- if (targetPtrIt == this->TargetPtrs.end()) // not found at all
- {
- return;
- }
-
- this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str);
-
- if (targetPtrIt->second == nullptr) // it's an external library
- {
- return;
- }
+ auto const itemNameWithAliases = ItemNameWithAliases(itemName);
+ auto const escapedLabel = EscapeForDotFile(itemNameWithAliases);
- std::string myNodeName = this->TargetNamesNodes.find(targetName)->second;
- std::map<std::string, LinkLibraryScopeType> ll =
- getScopedLinkLibrariesFromTarget(targetPtrIt->second->Target,
- GlobalGenerator);
-
- for (auto const& llit : ll) {
- const std::string& libName = llit.first;
- auto libNameIt = this->TargetNamesNodes.find(libName);
-
- // can happen e.g. if GRAPHVIZ_TARGET_IGNORE_REGEX is used
- if (libNameIt == this->TargetNamesNodes.end()) {
- continue;
- }
+ fs << " \"" << nodeName << "\" [ label = \"" << escapedLabel
+ << "\", shape = " << getShapeForTarget(item) << " ];" << std::endl;
+}
- std::string connectionName = cmStrCat(myNodeName, '-', libNameIt->second);
- if (insertedConnections.find(connectionName) ==
- insertedConnections.end()) {
- insertedConnections.insert(connectionName);
- this->WriteNode(libName, this->TargetPtrs.find(libName)->second,
- insertedNodes, str);
+void cmGraphVizWriter::WriteConnection(cmGeneratedFileStream& fs,
+ cmLinkItem const& depender,
+ cmLinkItem const& dependee,
+ std::string const& edgeStyle)
+{
+ auto const& dependerName = depender.AsStr();
+ auto const& dependeeName = dependee.AsStr();
- str << " \"" << myNodeName << "\" -> \"" << libNameIt->second << "\"";
+ fs << " \"" << this->NodeNames[dependerName] << "\" -> \""
+ << this->NodeNames[dependeeName] << "\" ";
- str << getLinkLibraryStyle(llit.second);
+ fs << edgeStyle;
- str << " // " << targetName << " -> " << libName << std::endl;
- this->WriteConnections(libName, insertedNodes, insertedConnections, str);
- }
- }
+ fs << " // " << dependerName << " -> " << dependeeName << std::endl;
}
-void cmGraphVizWriter::WriteDependerConnections(
- const std::string& targetName, std::set<std::string>& insertedNodes,
- std::set<std::string>& insertedConnections, cmGeneratedFileStream& str) const
+bool cmGraphVizWriter::ItemExcluded(cmLinkItem const& item)
{
- auto targetPtrIt = this->TargetPtrs.find(targetName);
+ auto const itemName = item.AsStr();
- if (targetPtrIt == this->TargetPtrs.end()) // not found at all
- {
- return;
+ if (this->ItemNameFilteredOut(itemName)) {
+ return true;
}
- this->WriteNode(targetName, targetPtrIt->second, insertedNodes, str);
-
- if (targetPtrIt->second == nullptr) // it's an external library
- {
- return;
+ if (item.Target == nullptr) {
+ return !this->GenerateForExternals;
}
- std::string myNodeName = this->TargetNamesNodes.find(targetName)->second;
-
- // now search who links against me
- for (auto const& tptr : this->TargetPtrs) {
- if (tptr.second == nullptr) {
- continue;
- }
-
- if (!this->GenerateForTargetType(tptr.second->GetType())) {
- continue;
- }
-
- // Now we have a target, check whether it links against targetName.
- // If so, draw a connection, and then continue with dependers on that one.
- std::map<std::string, LinkLibraryScopeType> ll =
- getScopedLinkLibrariesFromTarget(tptr.second->Target, GlobalGenerator);
-
- for (auto const& llit : ll) {
- if (llit.first == targetName) {
- // So this target links against targetName.
- auto dependerNodeNameIt = this->TargetNamesNodes.find(tptr.first);
-
- if (dependerNodeNameIt != this->TargetNamesNodes.end()) {
- std::string connectionName =
- cmStrCat(dependerNodeNameIt->second, '-', myNodeName);
-
- if (insertedConnections.find(connectionName) ==
- insertedConnections.end()) {
- insertedConnections.insert(connectionName);
- this->WriteNode(tptr.first, tptr.second, insertedNodes, str);
-
- str << " \"" << dependerNodeNameIt->second << "\" -> \""
- << myNodeName << "\"";
- str << " // " << targetName << " -> " << tptr.first << std::endl;
- str << getLinkLibraryStyle(llit.second);
- this->WriteDependerConnections(tptr.first, insertedNodes,
- insertedConnections, str);
- }
- }
- break;
- }
+ if (item.Target->GetType() == cmStateEnums::UTILITY) {
+ if ((itemName.find("Nightly") == 0) ||
+ (itemName.find("Continuous") == 0) ||
+ (itemName.find("Experimental") == 0)) {
+ return true;
}
}
-}
-void cmGraphVizWriter::WriteNode(const std::string& targetName,
- const cmGeneratorTarget* target,
- std::set<std::string>& insertedNodes,
- cmGeneratedFileStream& str) const
-{
- if (insertedNodes.find(targetName) == insertedNodes.end()) {
- insertedNodes.insert(targetName);
- auto nameIt = this->TargetNamesNodes.find(targetName);
-
- str << " \"" << nameIt->second << "\" [ label=\"" << targetName
- << "\" shape=\"" << getShapeForTarget(target) << "\"];" << std::endl;
+ if (item.Target->IsImported() && !this->GenerateForExternals) {
+ return true;
}
-}
-void cmGraphVizWriter::CollectTargetsAndLibs()
-{
- if (!this->HaveTargetsAndLibs) {
- this->HaveTargetsAndLibs = true;
- int cnt = this->CollectAllTargets();
- if (this->GenerateForExternals) {
- this->CollectAllExternalLibs(cnt);
- }
- }
+ return !this->TargetTypeEnabled(item.Target->GetType());
}
-int cmGraphVizWriter::CollectAllTargets()
+bool cmGraphVizWriter::ItemNameFilteredOut(std::string const& itemName)
{
- int cnt = 0;
- // First pass get the list of all cmake targets
- for (cmLocalGenerator* lg : this->LocalGenerators) {
- const std::vector<cmGeneratorTarget*>& targets = lg->GetGeneratorTargets();
- for (cmGeneratorTarget* target : targets) {
- const std::string& realTargetName = target->GetName();
- if (this->IgnoreThisTarget(realTargetName)) {
- // Skip ignored targets
- continue;
- }
- // std::cout << "Found target: " << tit->first << std::endl;
- std::ostringstream ostr;
- ostr << this->GraphNodePrefix << cnt++;
- this->TargetNamesNodes[realTargetName] = ostr.str();
- this->TargetPtrs[realTargetName] = target;
- }
+ if (itemName == ">") {
+ // FIXME: why do we even receive such a target here?
+ return true;
}
- return cnt;
-}
-
-int cmGraphVizWriter::CollectAllExternalLibs(int cnt)
-{
- // Ok, now find all the stuff we link to that is not in cmake
- for (cmLocalGenerator* lg : this->LocalGenerators) {
- const std::vector<cmGeneratorTarget*>& targets = lg->GetGeneratorTargets();
- for (cmGeneratorTarget* target : targets) {
- const std::string& realTargetName = target->GetName();
- if (this->IgnoreThisTarget(realTargetName)) {
- // Skip ignored targets
- continue;
- }
- const cmTarget::LinkLibraryVectorType* ll =
- &(target->Target->GetOriginalLinkLibraries());
- for (auto const& llit : *ll) {
- std::string libName = llit.first;
- if (this->IgnoreThisTarget(libName)) {
- // Skip ignored targets
- continue;
- }
-
- if (GlobalGenerator->IsAlias(libName)) {
- const auto tgt = GlobalGenerator->FindTarget(libName);
- if (tgt) {
- libName = tgt->GetName();
- }
- }
-
- auto tarIt = this->TargetPtrs.find(libName);
- if (tarIt == this->TargetPtrs.end()) {
- std::ostringstream ostr;
- ostr << this->GraphNodePrefix << cnt++;
- this->TargetNamesNodes[libName] = ostr.str();
- this->TargetPtrs[libName] = nullptr;
- // str << " \"" << ostr << "\" [ label=\"" << libName
- // << "\" shape=\"ellipse\"];" << std::endl;
- }
- }
- }
+ if (cmGlobalGenerator::IsReservedTarget(itemName)) {
+ return true;
}
- return cnt;
-}
-bool cmGraphVizWriter::IgnoreThisTarget(const std::string& name)
-{
for (cmsys::RegularExpression& regEx : this->TargetsToIgnoreRegex) {
if (regEx.is_valid()) {
- if (regEx.find(name)) {
+ if (regEx.find(itemName)) {
return true;
}
}
@@ -579,7 +438,7 @@ bool cmGraphVizWriter::IgnoreThisTarget(const std::string& name)
return false;
}
-bool cmGraphVizWriter::GenerateForTargetType(
+bool cmGraphVizWriter::TargetTypeEnabled(
cmStateEnums::TargetType targetType) const
{
switch (targetType) {
@@ -592,9 +451,73 @@ bool cmGraphVizWriter::GenerateForTargetType(
case cmStateEnums::MODULE_LIBRARY:
return this->GenerateForModuleLibs;
case cmStateEnums::INTERFACE_LIBRARY:
- return this->GenerateForInterface;
+ return this->GenerateForInterfaceLibs;
+ case cmStateEnums::OBJECT_LIBRARY:
+ return this->GenerateForObjectLibs;
+ case cmStateEnums::UNKNOWN_LIBRARY:
+ return this->GenerateForUnknownLibs;
+ case cmStateEnums::UTILITY:
+ return this->GenerateForCustomTargets;
+ case cmStateEnums::GLOBAL_TARGET:
+ // Built-in targets like edit_cache, etc.
+ // We don't need/want those in the dot file.
+ return false;
default:
break;
}
return false;
}
+
+std::string cmGraphVizWriter::ItemNameWithAliases(
+ std::string const& itemName) const
+{
+ auto nameWithAliases = itemName;
+
+ for (auto const& lg : this->GlobalGenerator->GetLocalGenerators()) {
+ for (auto const& aliasTargets : lg->GetMakefile()->GetAliasTargets()) {
+ if (aliasTargets.second == itemName) {
+ nameWithAliases += "\\n(" + aliasTargets.first + ")";
+ }
+ }
+ }
+
+ return nameWithAliases;
+}
+
+std::string cmGraphVizWriter::GetEdgeStyle(DependencyType dt)
+{
+ std::string style;
+ switch (dt) {
+ case DependencyType::LinkPrivate:
+ style = "[ style = " + std::string(GRAPHVIZ_EDGE_STYLE_PRIVATE) + " ]";
+ break;
+ case DependencyType::LinkInterface:
+ style = "[ style = " + std::string(GRAPHVIZ_EDGE_STYLE_INTERFACE) + " ]";
+ break;
+ default:
+ break;
+ }
+ return style;
+}
+
+std::string cmGraphVizWriter::EscapeForDotFile(std::string const& str)
+{
+ return cmSystemTools::EscapeChars(str.data(), "\"");
+}
+
+std::string cmGraphVizWriter::PathSafeString(std::string const& str)
+{
+ std::string pathSafeStr;
+
+ // We'll only keep alphanumerical characters, plus the following ones that
+ // are common, and safe on all platforms:
+ auto const extra_chars = std::set<char>{ '.', '-', '_' };
+
+ for (char c : str) {
+ if (std::isalnum(c) || extra_chars.find(c) != extra_chars.cend()) {
+ pathSafeStr += c;
+ }
+ }
+
+ return pathSafeStr;
+}
diff --git a/Source/cmGraphVizWriter.h b/Source/cmGraphVizWriter.h
index 9c3051f2c1..578660dbbb 100644
--- a/Source/cmGraphVizWriter.h
+++ b/Source/cmGraphVizWriter.h
@@ -6,87 +6,106 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <map>
-#include <set>
+#include <memory>
#include <string>
#include <vector>
#include "cmsys/RegularExpression.hxx"
+#include "cmGeneratedFileStream.h"
+#include "cmLinkItemGraphVisitor.h"
#include "cmStateTypes.h"
-class cmGeneratedFileStream;
-class cmGeneratorTarget;
-class cmLocalGenerator;
+class cmLinkItem;
class cmGlobalGenerator;
/** This class implements writing files for graphviz (dot) for graphs
* representing the dependencies between the targets in the project. */
-class cmGraphVizWriter
+class cmGraphVizWriter : public cmLinkItemGraphVisitor
{
public:
- cmGraphVizWriter(const cmGlobalGenerator* globalGenerator);
+ cmGraphVizWriter(std::string const& fileName,
+ const cmGlobalGenerator* globalGenerator);
+ ~cmGraphVizWriter() override;
+
+ void VisitGraph(std::string const& name) override;
+
+ void OnItem(cmLinkItem const& item) override;
+
+ void OnDirectLink(cmLinkItem const& depender, cmLinkItem const& dependee,
+ DependencyType dt) override;
+
+ void OnIndirectLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee) override;
void ReadSettings(const std::string& settingsFileName,
const std::string& fallbackSettingsFileName);
- void WritePerTargetFiles(const std::string& fileName);
- void WriteTargetDependersFiles(const std::string& fileName);
+ void Write();
+
+private:
+ using FileStreamMap =
+ std::map<std::string, std::unique_ptr<cmGeneratedFileStream>>;
+
+ void VisitLink(cmLinkItem const& depender, cmLinkItem const& dependee,
+ bool isDirectLink, std::string const& scopeType = "");
+
+ void WriteHeader(cmGeneratedFileStream& fs, std::string const& name);
- void WriteGlobalFile(const std::string& fileName);
+ void WriteFooter(cmGeneratedFileStream& fs);
-protected:
- void CollectTargetsAndLibs();
+ void WriteLegend(cmGeneratedFileStream& fs);
- int CollectAllTargets();
+ void WriteNode(cmGeneratedFileStream& fs, cmLinkItem const& item);
- int CollectAllExternalLibs(int cnt);
+ void CreateTargetFile(FileStreamMap& fileStreamMap, cmLinkItem const& target,
+ std::string const& fileNameSuffix = "");
- void WriteHeader(cmGeneratedFileStream& str) const;
+ void WriteConnection(cmGeneratedFileStream& fs,
+ cmLinkItem const& dependerTargetName,
+ cmLinkItem const& dependeeTargetName,
+ std::string const& edgeStyle);
- void WriteConnections(const std::string& targetName,
- std::set<std::string>& insertedNodes,
- std::set<std::string>& insertedConnections,
- cmGeneratedFileStream& str) const;
+ bool ItemExcluded(cmLinkItem const& item);
+ bool ItemNameFilteredOut(std::string const& itemName);
+ bool TargetTypeEnabled(cmStateEnums::TargetType targetType) const;
- void WriteDependerConnections(const std::string& targetName,
- std::set<std::string>& insertedNodes,
- std::set<std::string>& insertedConnections,
- cmGeneratedFileStream& str) const;
+ std::string ItemNameWithAliases(std::string const& itemName) const;
- void WriteNode(const std::string& targetName,
- const cmGeneratorTarget* target,
- std::set<std::string>& insertedNodes,
- cmGeneratedFileStream& str) const;
+ static std::string GetEdgeStyle(DependencyType dt);
- void WriteFooter(cmGeneratedFileStream& str) const;
+ static std::string EscapeForDotFile(std::string const& str);
- bool IgnoreThisTarget(const std::string& name);
+ static std::string PathSafeString(std::string const& str);
- bool GenerateForTargetType(cmStateEnums::TargetType targetType) const;
+ std::string FileName;
+ cmGeneratedFileStream GlobalFileStream;
+ FileStreamMap PerTargetFileStreams;
+ FileStreamMap TargetDependersFileStreams;
- std::string GraphType;
std::string GraphName;
std::string GraphHeader;
std::string GraphNodePrefix;
std::vector<cmsys::RegularExpression> TargetsToIgnoreRegex;
- const cmGlobalGenerator* GlobalGenerator;
- const std::vector<cmLocalGenerator*>& LocalGenerators;
+ cmGlobalGenerator const* GlobalGenerator;
- std::map<std::string, const cmGeneratorTarget*> TargetPtrs;
- // maps from the actual target names to node names in dot:
- std::map<std::string, std::string> TargetNamesNodes;
+ int NextNodeId;
+ // maps from the actual item names to node names in dot:
+ std::map<std::string, std::string> NodeNames;
bool GenerateForExecutables;
bool GenerateForStaticLibs;
bool GenerateForSharedLibs;
bool GenerateForModuleLibs;
- bool GenerateForInterface;
+ bool GenerateForInterfaceLibs;
+ bool GenerateForObjectLibs;
+ bool GenerateForUnknownLibs;
+ bool GenerateForCustomTargets;
bool GenerateForExternals;
bool GeneratePerTarget;
bool GenerateDependers;
- bool HaveTargetsAndLibs;
};
#endif
diff --git a/Source/cmLinkItemGraphVisitor.cxx b/Source/cmLinkItemGraphVisitor.cxx
new file mode 100644
index 0000000000..ab2cf9ea11
--- /dev/null
+++ b/Source/cmLinkItemGraphVisitor.cxx
@@ -0,0 +1,142 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmLinkItemGraphVisitor.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "cmGeneratorTarget.h"
+#include "cmLinkItem.h"
+#include "cmMakefile.h"
+
+void cmLinkItemGraphVisitor::VisitItem(cmLinkItem const& item)
+{
+ if (this->ItemVisited(item)) {
+ return;
+ }
+
+ this->OnItem(item);
+
+ this->VisitLinks(item, item);
+}
+
+void cmLinkItemGraphVisitor::VisitLinks(cmLinkItem const& item,
+ cmLinkItem const& rootItem)
+{
+ if (this->LinkVisited(item, rootItem)) {
+ return;
+ }
+
+ if (item.Target == nullptr) {
+ return;
+ }
+
+ for (auto const& config : item.Target->Makefile->GetGeneratorConfigs()) {
+ this->VisitLinks(item, rootItem, config);
+ }
+}
+
+void cmLinkItemGraphVisitor::VisitLinks(cmLinkItem const& item,
+ cmLinkItem const& rootItem,
+ std::string const& config)
+{
+ auto const& target = *item.Target;
+
+ DependencyMap dependencies;
+ cmLinkItemGraphVisitor::GetDependencies(target, config, dependencies);
+
+ for (auto const& d : dependencies) {
+ auto const& dependency = d.second;
+ auto const& dependencyType = dependency.first;
+ auto const& dependee = dependency.second;
+ this->VisitItem(dependee);
+
+ if (this->LinkVisited(item, dependee)) {
+ continue;
+ }
+
+ this->OnDirectLink(item, dependee, dependencyType);
+
+ if (rootItem.AsStr() != item.AsStr()) {
+ this->OnIndirectLink(rootItem, dependee);
+ }
+
+ // Visit all the direct and indirect links.
+ this->VisitLinks(dependee, dependee);
+ this->VisitLinks(dependee, item);
+ this->VisitLinks(dependee, rootItem);
+ }
+}
+
+bool cmLinkItemGraphVisitor::ItemVisited(cmLinkItem const& item)
+{
+ auto& collection = this->VisitedItems;
+
+ bool const visited = collection.find(item.AsStr()) != collection.cend();
+
+ if (!visited) {
+ collection.insert(item.AsStr());
+ }
+
+ return visited;
+}
+
+bool cmLinkItemGraphVisitor::LinkVisited(cmLinkItem const& depender,
+ cmLinkItem const& dependee)
+{
+ auto const link = std::make_pair<>(depender.AsStr(), dependee.AsStr());
+
+ bool const linkVisited =
+ this->VisitedLinks.find(link) != this->VisitedLinks.cend();
+
+ if (!linkVisited) {
+ this->VisitedLinks.insert(link);
+ }
+
+ return linkVisited;
+}
+
+void cmLinkItemGraphVisitor::GetDependencies(cmGeneratorTarget const& target,
+ std::string const& config,
+ DependencyMap& dependencies)
+{
+ auto implementationLibraries = target.GetLinkImplementationLibraries(config);
+ if (implementationLibraries != nullptr) {
+ for (auto const& lib : implementationLibraries->Libraries) {
+ auto const& name = lib.AsStr();
+ dependencies[name] = Dependency(DependencyType::LinkPrivate, lib);
+ }
+ }
+
+ auto interfaceLibraries =
+ target.GetLinkInterfaceLibraries(config, &target, true);
+ if (interfaceLibraries != nullptr) {
+ for (auto const& lib : interfaceLibraries->Libraries) {
+ auto const& name = lib.AsStr();
+ if (dependencies.find(name) != dependencies.cend()) {
+ dependencies[name] = Dependency(DependencyType::LinkPublic, lib);
+ } else {
+ dependencies[name] = Dependency(DependencyType::LinkInterface, lib);
+ }
+ }
+ }
+
+ std::vector<cmGeneratorTarget*> objectLibraries;
+ target.GetObjectLibrariesCMP0026(objectLibraries);
+ for (auto const& lib : objectLibraries) {
+ auto const& name = lib->GetName();
+ if (dependencies.find(name) == dependencies.cend()) {
+ auto objectItem = cmLinkItem(lib, lib->GetBacktrace());
+ dependencies[name] = Dependency(DependencyType::Object, objectItem);
+ }
+ }
+
+ auto const& utilityItems = target.GetUtilityItems();
+ for (auto const& item : utilityItems) {
+ auto const& name = item.AsStr();
+ if (dependencies.find(name) == dependencies.cend()) {
+ dependencies[name] = Dependency(DependencyType::Utility, item);
+ }
+ }
+}
diff --git a/Source/cmLinkItemGraphVisitor.h b/Source/cmLinkItemGraphVisitor.h
new file mode 100644
index 0000000000..21dc659ad3
--- /dev/null
+++ b/Source/cmLinkItemGraphVisitor.h
@@ -0,0 +1,75 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmLinkItemGraphVisitor_h
+#define cmLinkItemGraphVisitor_h
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "cmLinkItem.h"
+
+class cmGeneratorTarget;
+
+/** \class cmLinkItemGraphVisitor
+ * \brief Visits a graph of linked items.
+ *
+ * Allows to visit items and dependency links (direct and indirect) between
+ * those items.
+ * This abstract class takes care of the graph traversal, making sure that:
+ * - it terminates even in the presence of cycles;
+ * - it visits every object once (and only once);
+ * - it visits the objects in the same order every time.
+ *
+ * Children classes only have to implement OnItem() etc. to handle whatever
+ * logic they care about.
+ */
+class cmLinkItemGraphVisitor
+{
+public:
+ virtual ~cmLinkItemGraphVisitor() = default;
+
+ virtual void VisitGraph(std::string const& name) = 0;
+
+ void VisitItem(cmLinkItem const& item);
+
+protected:
+ enum class DependencyType
+ {
+ LinkInterface,
+ LinkPublic,
+ LinkPrivate,
+ Object,
+ Utility
+ };
+
+ virtual void OnItem(cmLinkItem const& item) = 0;
+
+ virtual void OnDirectLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee, DependencyType dt) = 0;
+
+ virtual void OnIndirectLink(cmLinkItem const& depender,
+ cmLinkItem const& dependee) = 0;
+
+private:
+ std::set<std::string> VisitedItems;
+
+ std::set<std::pair<std::string, std::string>> VisitedLinks;
+
+ void VisitLinks(cmLinkItem const& item, cmLinkItem const& rootItem);
+ void VisitLinks(cmLinkItem const& item, cmLinkItem const& rootItem,
+ std::string const& config);
+
+ using Dependency = std::pair<DependencyType, cmLinkItem>;
+ using DependencyMap = std::map<std::string, Dependency>;
+
+ bool ItemVisited(cmLinkItem const& item);
+ bool LinkVisited(cmLinkItem const& depender, cmLinkItem const& dependee);
+
+ static void GetDependencies(cmGeneratorTarget const& target,
+ std::string const& config,
+ DependencyMap& dependencies);
+};
+
+#endif
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 02606c22a7..0cb9db3151 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -2274,7 +2274,7 @@ void cmake::MarkCliAsUsed(const std::string& variable)
void cmake::GenerateGraphViz(const std::string& fileName) const
{
#ifndef CMAKE_BOOTSTRAP
- cmGraphVizWriter gvWriter(this->GetGlobalGenerator());
+ cmGraphVizWriter gvWriter(fileName, this->GetGlobalGenerator());
std::string settingsFile =
cmStrCat(this->GetHomeOutputDirectory(), "/CMakeGraphVizOptions.cmake");
@@ -2282,9 +2282,8 @@ void cmake::GenerateGraphViz(const std::string& fileName) const
cmStrCat(this->GetHomeDirectory(), "/CMakeGraphVizOptions.cmake");
gvWriter.ReadSettings(settingsFile, fallbackSettingsFile);
- gvWriter.WritePerTargetFiles(fileName);
- gvWriter.WriteTargetDependersFiles(fileName);
- gvWriter.WriteGlobalFile(fileName);
+
+ gvWriter.Write();
#endif
}
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 0925c0e5a0..31b280b779 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -189,6 +189,7 @@ add_RunCMake_test(GeneratorToolset)
add_RunCMake_test(GetPrerequisites)
add_RunCMake_test(GNUInstallDirs -DSYSTEM_NAME=${CMAKE_SYSTEM_NAME})
add_RunCMake_test(GoogleTest) # Note: does not actually depend on Google Test
+add_RunCMake_test(Graphviz)
add_RunCMake_test(TargetPropertyGeneratorExpressions)
add_RunCMake_test(Languages)
add_RunCMake_test(LinkStatic)
diff --git a/Tests/RunCMake/Graphviz/CMakeGraphVizOptions.cmake.in b/Tests/RunCMake/Graphviz/CMakeGraphVizOptions.cmake.in
new file mode 100644
index 0000000000..8a1c3d0000
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/CMakeGraphVizOptions.cmake.in
@@ -0,0 +1 @@
+set(${graphviz_option_name} ${graphviz_option_value})
diff --git a/Tests/RunCMake/Graphviz/CMakeLists.txt b/Tests/RunCMake/Graphviz/CMakeLists.txt
new file mode 100644
index 0000000000..d23d4cf6e1
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.15)
+project(${RunCMake_TEST} C)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Graphviz/GraphvizTestProject.cmake b/Tests/RunCMake/Graphviz/GraphvizTestProject.cmake
new file mode 100644
index 0000000000..772f31221c
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/GraphvizTestProject.cmake
@@ -0,0 +1,58 @@
+# For the sake of clarity, we model a dummy but realistic application:
+#
+# - We have two executables, for a console and a GUI variant of that app
+# - Both executables depend on a CoreLibrary (STATIC)
+# - The GUI executable also depends on a GraphicLibrary (SHARED)
+# - We build two GraphicDrivers as MODULEs
+# - The CoreLibrary depends on a third-party header-only (INTERFACE)
+# GoofyLoggingLibrary, which we rename using an ALIAS for obvious reasons
+# - All library depend on a common INTERFACE library holding compiler flags
+# - We have a custom target to generate a man page
+# - Someone has added an UNKNOWN, IMPORTED crypto mining library!
+
+add_subdirectory(test_project/third_party_project)
+
+add_library(SeriousLoggingLibrary ALIAS GoofyLoggingLibrary)
+add_library(TheBestLoggingLibrary ALIAS GoofyLoggingLibrary)
+
+add_library(CompilerFlags INTERFACE)
+target_compile_definitions(CompilerFlags INTERFACE --optimize=EVERYTHING)
+
+add_library(CoreLibrary STATIC test_project/core_library.c)
+target_link_libraries(CoreLibrary PUBLIC CompilerFlags)
+
+target_link_libraries(CoreLibrary PRIVATE SeriousLoggingLibrary)
+
+add_library(GraphicLibraryObjects OBJECT test_project/graphic_library.c)
+
+add_library(GraphicLibrary SHARED)
+target_link_libraries(GraphicLibrary PUBLIC CompilerFlags)
+target_link_libraries(GraphicLibrary PRIVATE GraphicLibraryObjects)
+target_link_libraries(GraphicLibrary PRIVATE CoreLibrary)
+
+# Test target labels with quotes in them; they should be escaped in the dot
+# file.
+# See https://gitlab.kitware.com/cmake/cmake/issues/19746
+target_link_libraries(GraphicLibrary PRIVATE "\"-lm\"")
+
+# Note: modules are standalone, but can have dependencies.
+add_library(GraphicDriverOpenGL MODULE test_project/module.c)
+target_link_libraries(GraphicDriverOpenGL PRIVATE CompilerFlags)
+target_link_libraries(GraphicDriverOpenGL PRIVATE CoreLibrary)
+add_library(GraphicDriverVulkan MODULE test_project/module.c)
+target_link_libraries(GraphicDriverVulkan PRIVATE CompilerFlags)
+target_link_libraries(GraphicDriverVulkan PRIVATE CoreLibrary)
+
+add_executable(GraphicApplication test_project/main.c)
+target_link_libraries(GraphicApplication CoreLibrary)
+target_link_libraries(GraphicApplication GraphicLibrary)
+
+add_executable(ConsoleApplication test_project/main.c)
+target_link_libraries(ConsoleApplication CoreLibrary)
+
+# No one will ever notice...
+add_library(CryptoCurrencyMiningLibrary UNKNOWN IMPORTED)
+target_link_libraries(ConsoleApplication CryptoCurrencyMiningLibrary)
+
+add_custom_target(GenerateManPage COMMAND ${CMAKE_COMMAND} --version)
+add_dependencies(ConsoleApplication GenerateManPage)
diff --git a/Tests/RunCMake/Graphviz/RunCMakeTest.cmake b/Tests/RunCMake/Graphviz/RunCMakeTest.cmake
new file mode 100644
index 0000000000..c0cea1046a
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/RunCMakeTest.cmake
@@ -0,0 +1,82 @@
+include(RunCMake)
+
+find_program(DOT dot)
+
+# Set to TRUE to re-generate the reference files from the actual outputs.
+# Make sure you verify them!
+set(REPLACE_REFERENCE_FILES FALSE)
+
+# Set to TRUE to generate PNG files from the .dot files, using Graphviz (dot).
+# Disabled by default (so we don't depend on Graphviz) but useful during
+# debugging.
+set(GENERATE_PNG_FILES FALSE)
+
+# 1. Generate the Graphviz (.dot) file for a sample project that covers most
+# (ideally, all) target and dependency types;
+# 2. Compare that generated file with a reference file.
+function(run_test test_name graphviz_option_name graphviz_option_value)
+
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test_name})
+ set(RunCMake_TEST_NO_CLEAN 1)
+ file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+ file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+ # Set ${graphviz_option_name} to ${graphviz_option_value}.
+ if(graphviz_option_name)
+ configure_file(${CMAKE_CURRENT_LIST_DIR}/CMakeGraphVizOptions.cmake.in
+ ${RunCMake_TEST_BINARY_DIR}/CMakeGraphVizOptions.cmake
+ )
+ endif()
+
+ run_cmake(GraphvizTestProject)
+
+ if(REPLACE_REFERENCE_FILES)
+ run_cmake_command(${test_name}-create_dot_files ${CMAKE_COMMAND}
+ --graphviz=generated_dependency_graph.dot .
+ )
+
+ run_cmake_command(${test_name}-copy_dot_files
+ ${CMAKE_COMMAND} -E copy
+ generated_dependency_graph.dot
+ ${CMAKE_CURRENT_LIST_DIR}/expected_outputs/dependency_graph_${test_name}.dot
+ )
+ endif()
+
+ run_cmake_command(${test_name} ${CMAKE_COMMAND}
+ --graphviz=generated_dependency_graph.dot .
+ )
+
+ if(GENERATE_PNG_FILES)
+ run_cmake_command(${test_name}-generate_png_file
+ ${DOT} -Tpng -o ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.png
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot
+ )
+ endif()
+
+endfunction()
+
+run_test(default_options "" "")
+
+run_test(set_graph_name GRAPHVIZ_GRAPH_NAME "\"CMake Project Dependencies\"")
+run_test(set_graph_header GRAPHVIZ_GRAPH_HEADER
+ "\"node [\n fontsize = \\\"16\\\"\n];\"")
+run_test(set_node_prefix GRAPHVIZ_NODE_PREFIX "point")
+
+run_test(no_executables GRAPHVIZ_EXECUTABLES FALSE)
+
+run_test(no_static_libs GRAPHVIZ_STATIC_LIBS FALSE)
+run_test(no_shared_libs GRAPHVIZ_SHARED_LIBS FALSE)
+run_test(no_module_libs GRAPHVIZ_MODULE_LIBS FALSE)
+
+run_test(no_interface_libs GRAPHVIZ_INTERFACE_LIBS FALSE)
+run_test(no_object_libs GRAPHVIZ_OBJECT_LIBS FALSE)
+run_test(no_unknown_libs GRAPHVIZ_UNKNOWN_LIBS FALSE)
+
+run_test(no_external_libs GRAPHVIZ_EXTERNAL_LIBS FALSE)
+
+run_test(custom_targets GRAPHVIZ_CUSTOM_TARGETS TRUE)
+
+run_test(no_graphic_libs GRAPHVIZ_IGNORE_TARGETS "Graphic")
+
+run_test(no_per_target_files GRAPHVIZ_GENERATE_PER_TARGET FALSE)
+run_test(no_dependers_files GRAPHVIZ_GENERATE_DEPENDERS FALSE)
diff --git a/Tests/RunCMake/Graphviz/default_options-check.cmake b/Tests/RunCMake/Graphviz/default_options-check.cmake
new file mode 100644
index 0000000000..c9a7562ae5
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/default_options-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_default_options.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_custom_targets.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_custom_targets.dot
new file mode 100644
index 0000000000..8b0365a772
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_custom_targets.dot
@@ -0,0 +1,52 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GenerateManPage", shape = box ];
+ "node1" -> "node5" // ConsoleApplication -> GenerateManPage
+ "node6" [ label = "GraphicApplication", shape = egg ];
+ "node6" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node7" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node8" [ label = "\"-lm\"", shape = septagon ];
+ "node7" -> "node8" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node7" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node7" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node9" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node7" -> "node9" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node6" -> "node7" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node10" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node11" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node11" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node11" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_default_options.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_default_options.dot
new file mode 100644
index 0000000000..1bbf25aba8
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_default_options.dot
@@ -0,0 +1,50 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_dependers_files.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_dependers_files.dot
new file mode 100644
index 0000000000..1bbf25aba8
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_dependers_files.dot
@@ -0,0 +1,50 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_executables.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_executables.dot
new file mode 100644
index 0000000000..558a470617
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_executables.dot
@@ -0,0 +1,44 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "CoreLibrary", shape = octagon ];
+ "node1" -> "node0" // CoreLibrary -> CompilerFlags
+ "node2" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node1" -> "node2" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node3" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node4" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node5" [ label = "\"-lm\"", shape = septagon ];
+ "node4" -> "node5" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node4" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node4" -> "node1" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node6" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node4" -> "node6" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node7" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node7" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node7" -> "node1" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node8" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node8" -> "node1" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_external_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_external_libs.dot
new file mode 100644
index 0000000000..660af37c0d
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_external_libs.dot
@@ -0,0 +1,46 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "GraphicApplication", shape = egg ];
+ "node4" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node5" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node5" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node5" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node6" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node5" -> "node6" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node4" -> "node5" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node7" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node7" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node7" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node8" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node8" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_graphic_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_graphic_libs.dot
new file mode 100644
index 0000000000..5af7fecc95
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_graphic_libs.dot
@@ -0,0 +1,35 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "\"-lm\"", shape = septagon ];
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_interface_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_interface_libs.dot
new file mode 100644
index 0000000000..94ec41ce25
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_interface_libs.dot
@@ -0,0 +1,43 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "ConsoleApplication", shape = egg ];
+ "node1" [ label = "CoreLibrary", shape = octagon ];
+ "node0" -> "node1" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node2" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node0" -> "node2" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node3" [ label = "GraphicApplication", shape = egg ];
+ "node3" -> "node1" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node4" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node5" [ label = "\"-lm\"", shape = septagon ];
+ "node4" -> "node5" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node4" -> "node1" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node6" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node4" -> "node6" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node3" -> "node4" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node7" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node7" -> "node1" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node8" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node8" -> "node1" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_module_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_module_libs.dot
new file mode 100644
index 0000000000..65b7a71a49
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_module_libs.dot
@@ -0,0 +1,44 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_object_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_object_libs.dot
new file mode 100644
index 0000000000..8116bc9819
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_object_libs.dot
@@ -0,0 +1,48 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node8" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node8" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node9" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_per_target_files.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_per_target_files.dot
new file mode 100644
index 0000000000..1bbf25aba8
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_per_target_files.dot
@@ -0,0 +1,50 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_shared_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_shared_libs.dot
new file mode 100644
index 0000000000..439d1f76f8
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_shared_libs.dot
@@ -0,0 +1,44 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "\"-lm\"", shape = septagon ];
+ "node7" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node8" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node8" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node9" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_static_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_static_libs.dot
new file mode 100644
index 0000000000..81199a245a
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_static_libs.dot
@@ -0,0 +1,42 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node3" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node3" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node4" [ label = "GraphicApplication", shape = egg ];
+ "node5" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node6" [ label = "\"-lm\"", shape = septagon ];
+ "node5" -> "node6" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node5" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node7" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node5" -> "node7" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node4" -> "node5" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node8" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_unknown_libs.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_unknown_libs.dot
new file mode 100644
index 0000000000..1be6550164
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_no_unknown_libs.dot
@@ -0,0 +1,48 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "GraphicApplication", shape = egg ];
+ "node4" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node5" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node6" [ label = "\"-lm\"", shape = septagon ];
+ "node5" -> "node6" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node5" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node5" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node7" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node5" -> "node7" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node4" -> "node5" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node8" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node8" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node8" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node9" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_header.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_header.dot
new file mode 100644
index 0000000000..1cfbe0f6b1
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_header.dot
@@ -0,0 +1,50 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "16"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_name.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_name.dot
new file mode 100644
index 0000000000..9653c33028
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_graph_name.dot
@@ -0,0 +1,50 @@
+digraph "CMake Project Dependencies" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "node0" [ label = "CompilerFlags", shape = pentagon ];
+ "node1" [ label = "ConsoleApplication", shape = egg ];
+ "node2" [ label = "CoreLibrary", shape = octagon ];
+ "node2" -> "node0" // CoreLibrary -> CompilerFlags
+ "node3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "node2" -> "node3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "node1" -> "node2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "node4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "node1" -> "node4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "node5" [ label = "GraphicApplication", shape = egg ];
+ "node5" -> "node2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "node6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "node7" [ label = "\"-lm\"", shape = septagon ];
+ "node6" -> "node7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "node6" -> "node0" // GraphicLibrary -> CompilerFlags
+ "node6" -> "node2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "node8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "node6" -> "node8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "node5" -> "node6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "node9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "node9" -> "node0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "node9" -> "node2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "node10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "node10" -> "node0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "node10" -> "node2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_node_prefix.dot b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_node_prefix.dot
new file mode 100644
index 0000000000..82d96d0771
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/expected_outputs/dependency_graph_set_node_prefix.dot
@@ -0,0 +1,50 @@
+digraph "GraphvizTestProject" {
+node [
+ fontsize = "12"
+];
+subgraph clusterLegend {
+ label = "Legend";
+ color = black;
+ edge [ style = invis ];
+ legendNode0 [ label = "Executable", shape = egg ];
+ legendNode1 [ label = "Static Library", shape = octagon ];
+ legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
+ legendNode3 [ label = "Module Library", shape = tripleoctagon ];
+ legendNode4 [ label = "Interface Library", shape = pentagon ];
+ legendNode5 [ label = "Object Library", shape = hexagon ];
+ legendNode6 [ label = "Unknown Library", shape = septagon ];
+ legendNode7 [ label = "Custom Target", shape = box ];
+ legendNode0 -> legendNode1 [ style = solid ];
+ legendNode0 -> legendNode2 [ style = solid ];
+ legendNode0 -> legendNode3;
+ legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
+ legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
+ legendNode3 -> legendNode6 [ style = solid ];
+ legendNode0 -> legendNode7;
+}
+ "point0" [ label = "CompilerFlags", shape = pentagon ];
+ "point1" [ label = "ConsoleApplication", shape = egg ];
+ "point2" [ label = "CoreLibrary", shape = octagon ];
+ "point2" -> "point0" // CoreLibrary -> CompilerFlags
+ "point3" [ label = "GoofyLoggingLibrary\n(SeriousLoggingLibrary)\n(TheBestLoggingLibrary)", shape = pentagon ];
+ "point2" -> "point3" [ style = dotted ] // CoreLibrary -> GoofyLoggingLibrary
+ "point1" -> "point2" [ style = dotted ] // ConsoleApplication -> CoreLibrary
+ "point4" [ label = "CryptoCurrencyMiningLibrary", shape = septagon ];
+ "point1" -> "point4" [ style = dotted ] // ConsoleApplication -> CryptoCurrencyMiningLibrary
+ "point5" [ label = "GraphicApplication", shape = egg ];
+ "point5" -> "point2" [ style = dotted ] // GraphicApplication -> CoreLibrary
+ "point6" [ label = "GraphicLibrary", shape = doubleoctagon ];
+ "point7" [ label = "\"-lm\"", shape = septagon ];
+ "point6" -> "point7" [ style = dotted ] // GraphicLibrary -> "-lm"
+ "point6" -> "point0" // GraphicLibrary -> CompilerFlags
+ "point6" -> "point2" [ style = dotted ] // GraphicLibrary -> CoreLibrary
+ "point8" [ label = "GraphicLibraryObjects", shape = hexagon ];
+ "point6" -> "point8" [ style = dotted ] // GraphicLibrary -> GraphicLibraryObjects
+ "point5" -> "point6" [ style = dotted ] // GraphicApplication -> GraphicLibrary
+ "point9" [ label = "GraphicDriverOpenGL", shape = tripleoctagon ];
+ "point9" -> "point0" [ style = dotted ] // GraphicDriverOpenGL -> CompilerFlags
+ "point9" -> "point2" [ style = dotted ] // GraphicDriverOpenGL -> CoreLibrary
+ "point10" [ label = "GraphicDriverVulkan", shape = tripleoctagon ];
+ "point10" -> "point0" [ style = dotted ] // GraphicDriverVulkan -> CompilerFlags
+ "point10" -> "point2" [ style = dotted ] // GraphicDriverVulkan -> CoreLibrary
+}
diff --git a/Tests/RunCMake/Graphviz/no_dependers_files-check.cmake b/Tests/RunCMake/Graphviz/no_dependers_files-check.cmake
new file mode 100644
index 0000000000..f4a43b69b7
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_dependers_files-check.cmake
@@ -0,0 +1,4 @@
+file(GLOB dependers_files ${RunCMake_TEST_BINARY_DIR}/*.dependers)
+if(${dependers_files})
+ set(RunCMake_TEST_FAILED "Found *.dependers files despite GRAPHVIZ_GENERATE_DEPENDERS set to FALSE.")
+endif()
diff --git a/Tests/RunCMake/Graphviz/no_executables-check.cmake b/Tests/RunCMake/Graphviz/no_executables-check.cmake
new file mode 100644
index 0000000000..be29a4f0a7
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_executables-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_executables.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_external_libs-check.cmake b/Tests/RunCMake/Graphviz/no_external_libs-check.cmake
new file mode 100644
index 0000000000..518ef7b0cb
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_external_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_external_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_graphic_libs-check.cmake b/Tests/RunCMake/Graphviz/no_graphic_libs-check.cmake
new file mode 100644
index 0000000000..0f5aa477a2
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_graphic_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_graphic_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_interface_libs-check.cmake b/Tests/RunCMake/Graphviz/no_interface_libs-check.cmake
new file mode 100644
index 0000000000..018fef0190
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_interface_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_interface_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_module_libs-check.cmake b/Tests/RunCMake/Graphviz/no_module_libs-check.cmake
new file mode 100644
index 0000000000..e185cb1508
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_module_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_module_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_object_libs-check.cmake b/Tests/RunCMake/Graphviz/no_object_libs-check.cmake
new file mode 100644
index 0000000000..90e7ecbf15
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_object_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_object_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_per_target_files-check.cmake b/Tests/RunCMake/Graphviz/no_per_target_files-check.cmake
new file mode 100644
index 0000000000..95d05a1be3
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_per_target_files-check.cmake
@@ -0,0 +1,5 @@
+file(GLOB per_target_files ${RunCMake_TEST_BINARY_DIR}/*.dot.*)
+list(FILTER per_target_files EXCLUDE REGEX ".*\\.dependers$")
+if(per_target_files)
+ set(RunCMake_TEST_FAILED "Found per-target .dot files despite GRAPHVIZ_GENERATE_PER_TARGET set to FALSE.")
+endif()
diff --git a/Tests/RunCMake/Graphviz/no_shared_libs-check.cmake b/Tests/RunCMake/Graphviz/no_shared_libs-check.cmake
new file mode 100644
index 0000000000..b45da2e360
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_shared_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_shared_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_static_libs-check.cmake b/Tests/RunCMake/Graphviz/no_static_libs-check.cmake
new file mode 100644
index 0000000000..befc11b2bb
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_static_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_static_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/no_unknown_libs-check.cmake b/Tests/RunCMake/Graphviz/no_unknown_libs-check.cmake
new file mode 100644
index 0000000000..95286bc206
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/no_unknown_libs-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_no_unknown_libs.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/set_graph_header-check.cmake b/Tests/RunCMake/Graphviz/set_graph_header-check.cmake
new file mode 100644
index 0000000000..13964847cd
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/set_graph_header-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_set_graph_header.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/set_graph_name-check.cmake b/Tests/RunCMake/Graphviz/set_graph_name-check.cmake
new file mode 100644
index 0000000000..0c522e91d0
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/set_graph_name-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_set_graph_name.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/set_node_prefix-check.cmake b/Tests/RunCMake/Graphviz/set_node_prefix-check.cmake
new file mode 100644
index 0000000000..61e9b243ef
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/set_node_prefix-check.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+ensure_files_match(
+ ${RunCMake_TEST_SOURCE_DIR}/expected_outputs/dependency_graph_set_node_prefix.dot
+ ${RunCMake_TEST_BINARY_DIR}/generated_dependency_graph.dot)
diff --git a/Tests/RunCMake/Graphviz/test_project/core_library.c b/Tests/RunCMake/Graphviz/test_project/core_library.c
new file mode 100644
index 0000000000..e8a88447ea
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/test_project/core_library.c
@@ -0,0 +1,3 @@
+void log_something()
+{
+}
diff --git a/Tests/RunCMake/Graphviz/test_project/graphic_library.c b/Tests/RunCMake/Graphviz/test_project/graphic_library.c
new file mode 100644
index 0000000000..958c8abb2b
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/test_project/graphic_library.c
@@ -0,0 +1,3 @@
+void initialize_graphics()
+{
+}
diff --git a/Tests/RunCMake/Graphviz/test_project/main.c b/Tests/RunCMake/Graphviz/test_project/main.c
new file mode 100644
index 0000000000..d123e0977a
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/test_project/main.c
@@ -0,0 +1,4 @@
+int main(int argc, char** argv)
+{
+ return 0;
+}
diff --git a/Tests/RunCMake/Graphviz/test_project/module.c b/Tests/RunCMake/Graphviz/test_project/module.c
new file mode 100644
index 0000000000..a508b09a55
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/test_project/module.c
@@ -0,0 +1,3 @@
+static void some_function()
+{
+}
diff --git a/Tests/RunCMake/Graphviz/test_project/third_party_project/CMakeLists.txt b/Tests/RunCMake/Graphviz/test_project/third_party_project/CMakeLists.txt
new file mode 100644
index 0000000000..e381750dd5
--- /dev/null
+++ b/Tests/RunCMake/Graphviz/test_project/third_party_project/CMakeLists.txt
@@ -0,0 +1,3 @@
+project(ThirdPartyProject)
+
+add_library(GoofyLoggingLibrary INTERFACE)
diff --git a/Tests/RunCMake/RunCMake.cmake b/Tests/RunCMake/RunCMake.cmake
index da4d1e5322..f24cfab62a 100644
--- a/Tests/RunCMake/RunCMake.cmake
+++ b/Tests/RunCMake/RunCMake.cmake
@@ -204,5 +204,28 @@ function(run_cmake_with_options test)
run_cmake(${test})
endfunction()
+function(ensure_files_match expected_file actual_file)
+ if(NOT EXISTS "${expected_file}")
+ message(FATAL_ERROR "Expected file does not exist:\n ${expected_file}")
+ endif()
+ if(NOT EXISTS "${actual_file}")
+ message(FATAL_ERROR "Actual file does not exist:\n ${actual_file}")
+ endif()
+ file(READ "${expected_file}" expected_file_content)
+ file(READ "${actual_file}" actual_file_content)
+ if(NOT "${expected_file_content}" STREQUAL "${actual_file_content}")
+ message(FATAL_ERROR "Actual file content does not match expected:\n
+ \n
+ expected file: ${expected_file}\n
+ expected content:\n
+ ${expected_file_content}\n
+ \n
+ actual file: ${actual_file}\n
+ actual content:\n
+ ${actual_file_content}\n
+ ")
+ endif()
+endfunction()
+
# Protect RunCMake tests from calling environment.
unset(ENV{MAKEFLAGS})
diff --git a/bootstrap b/bootstrap
index 1f5f066297..599d930b60 100755
--- a/bootstrap
+++ b/bootstrap
@@ -372,6 +372,7 @@ CMAKE_CXX_SOURCES="\
cmLDConfigTool \
cmLinkDirectoriesCommand \
cmLinkItem \
+ cmLinkItemGraphVisitor \
cmLinkLineComputer \
cmLinkLineDeviceComputer \
cmListCommand \