Embed module dependency graph in Doxygen docs
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 13 Sep 2014 18:11:13 +0000 (21:11 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Fri, 19 Sep 2014 14:46:37 +0000 (16:46 +0200)
The module dependency graph generated by the dep-graphs target is now
embedded into the Doxygen documentation as a separate page (in the lib
and full variants).  Building these flavors of documentation now
requires the depgraphs to be built first, which in turn depends on the
doxygen XML output.  Added various -fast targets that skip these
dependencies, with the cost of potentially producing some warnings about
missing stuff.

Change-Id: Id3314c598d79588c429fe57108349edf42de3b6b

docs/doxygen/CMakeLists.txt
docs/doxygen/Doxyfile-common.cmakein
docs/doxygen/Doxyfile-xml.cmakein
docs/doxygen/generateGraphs.cmake
docs/doxygen/gmxtree.py
docs/doxygen/graphbuilder.py
docs/doxygen/lib/doxygen.md
docs/doxygen/lib/modulegraph.md [new file with mode: 0644]
docs/doxygen/user/codelayout.md

index 7ffe276de5d3c69fb49ddb90cab59b33bc824e81..9d439695b3a0c685214d8a92a15cb4004545296d 100644 (file)
@@ -89,6 +89,7 @@ if (DOXYGEN_FOUND)
         SET(NB_KERNEL_DIRS_TO_IGNORE_IN_DOXYGEN
             "${NB_KERNEL_DIRS_TO_IGNORE_IN_DOXYGEN} \\\n                         ${NB_KERNEL_DIR}")
     endforeach()
+    set(DEPGRAPH_DIR ${CMAKE_CURRENT_BINARY_DIR}/depgraphs)
     set(DOXYGEN_SECTIONS "")
     set(DOXYGEN_EXTRA_SETTINGS "")
     if (GMX_COMPACT_DOXYGEN)
@@ -106,17 +107,27 @@ if (DOXYGEN_FOUND)
 
     function (add_doxygen_target TARGET TYPE COMMENT)
         add_custom_target(${TARGET}
-            ${CMAKE_COMMAND} -DDOCTYPE=${TYPE} -P RunDoxygen.cmake
+            # Ensure the directory exists to avoid spurious warnings
+            ${CMAKE_COMMAND} -E make_directory ${DEPGRAPH_DIR}
+            COMMAND ${CMAKE_COMMAND} -DDOCTYPE=${TYPE} -P RunDoxygen.cmake
             WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
             COMMENT "${COMMENT}" VERBATIM)
         add_dependencies(${TARGET} doxygen-version)
     endfunction()
-    add_doxygen_target(doc-full full "Generating full documentation with Doxygen")
-    add_doxygen_target(doc-lib lib "Generating library documentation with Doxygen")
-    add_doxygen_target(doc-user user "Generating public API documentation with Doxygen")
-    add_doxygen_target(doc-xml xml "Extracting Doxygen documentation to XML")
+    # The targets ending with -fast do the same thing as the target without the
+    # suffix, but assume that time-consuming dependencies have already been
+    # built, making it faster and more convenient to test a single part of the
+    # system.
+    add_doxygen_target(doc-full      full "Generating full documentation with Doxygen")
+    add_doxygen_target(doc-full-fast full "Generating full documentation with Doxygen")
+    add_doxygen_target(doc-lib       lib  "Generating library documentation with Doxygen")
+    add_doxygen_target(doc-lib-fast  lib  "Generating library documentation with Doxygen")
+    add_doxygen_target(doc-user      user "Generating public API documentation with Doxygen")
+    add_doxygen_target(doc-xml       xml  "Extracting Doxygen documentation to XML")
     add_custom_target(doc-all)
+    add_custom_target(doc-all-fast)
     add_dependencies(doc-all doc-full doc-lib doc-user)
+    add_dependencies(doc-all-fast doc-full-fast doc-lib-fast doc-user)
 
     if (USE_PYTHON_SCRIPTS)
         # TODO: Consider whether this is the best name and location for this
@@ -132,34 +143,39 @@ if (DOXYGEN_FOUND)
         add_custom_target(doc-check COMMAND ${doc_check_command}
             COMMENT "Checking Doxygen documentation" VERBATIM)
         add_dependencies(doc-check doc-xml find-installed-headers)
+        add_custom_target(doc-check-fast COMMAND ${doc_check_command}
+            COMMENT "Checking Doxygen documentation" VERBATIM)
+        add_dependencies(doc-check-fast find-installed-headers)
 
-        set(graphdir ${CMAKE_CURRENT_BINARY_DIR}/depgraphs)
         set(dep_graphs_command_python
             ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/graphbuilder.py
             -S ${CMAKE_SOURCE_DIR} -B ${CMAKE_BINARY_DIR}
             --ignore-cycles ${CMAKE_CURRENT_SOURCE_DIR}/cycle-suppressions.txt
-            -o ${CMAKE_CURRENT_BINARY_DIR}/depgraphs)
+            -o ${DEPGRAPH_DIR})
         set(dep_graphs_command_dot
-            ${CMAKE_COMMAND} -DGRAPHDIR=${graphdir}
+            ${CMAKE_COMMAND} -DGRAPHDIR=${DEPGRAPH_DIR}
             -DDOT_EXECUTABLE=${DOXYGEN_DOT_EXECUTABLE}
             -P ${CMAKE_CURRENT_SOURCE_DIR}/generateGraphs.cmake)
-        add_custom_target(dep-graphs
+        add_custom_target(dep-graphs-dot
             COMMAND ${dep_graphs_command_python}
+            COMMENT "Generating include dependency graphs for dot" VERBATIM)
+        add_custom_target(dep-graphs
             COMMAND ${dep_graphs_command_dot}
-            COMMENT "Generating include dependency graphs" VERBATIM)
-        add_dependencies(dep-graphs doc-xml find-installed-headers)
+            COMMENT "Generating PNG include dependency graphs" VERBATIM)
+        add_dependencies(dep-graphs-dot doc-xml find-installed-headers)
+        add_dependencies(dep-graphs dep-graphs-dot)
+        add_dependencies(doc-full dep-graphs-dot)
+        add_dependencies(doc-lib dep-graphs-dot)
 
-        # These targets are the same as above, but they don't rerun Doxygen
-        # each time, making it faster and more convenient for testing.
-        add_custom_target(doc-check-fast COMMAND ${doc_check_command}
-            COMMENT "Checking Doxygen documentation" VERBATIM)
-        add_custom_target(dep-graphs-fast
+        add_custom_target(dep-graphs-dot-fast
             COMMAND ${dep_graphs_command_python}
+            COMMENT "Generating include dependency graphs for dot" VERBATIM)
+        add_custom_target(dep-graphs-fast
             COMMAND ${dep_graphs_command_dot}
-            COMMENT "Generating include dependency graphs" VERBATIM)
+            COMMENT "Generating PNG include dependency graphs" VERBATIM)
         # Finding the installed headers doesn't actually run again if nothing
         # has changed, so that can be safely added as a dependency.
-        add_dependencies(doc-check-fast find-installed-headers)
-        add_dependencies(dep-graphs-fast find-installed-headers)
+        add_dependencies(dep-graphs-dot-fast find-installed-headers)
+        add_dependencies(dep-graphs-fast dep-graphs-dot-fast)
     endif()
 endif()
index c6555f16ea82c40c3613a540e0fa51caf78c88a8..6aa36632099e54826313defbd89211c5a2c1ce2e 100644 (file)
@@ -27,6 +27,7 @@ INCLUDE_PATH           = @CMAKE_SOURCE_DIR@/src
 HAVE_DOT               = @DOXYGEN_DOT_FOUND@
 DOT_PATH               = @DOXYGEN_DOT_PATH@
 MSCGEN_PATH            = @DOXYGEN_MSCGEN_PATH@
+DOTFILE_DIRS           = @DEPGRAPH_DIR@
 @DOXYGEN_EXTRA_SETTINGS@
 
 ENABLED_SECTIONS       = @DOXYGEN_SECTIONS@
index babca03873086e7471c08f1d9ea64cf362d81a97..7c100e12a102269e60601def4c8d526a642ba405 100644 (file)
@@ -2,7 +2,7 @@
 
 PREDEFINED            += F77_FUNC(name,NAME)=name
 
-ENABLED_SECTIONS      += libapi internal
+ENABLED_SECTIONS      += libapi internal xml
 INTERNAL_DOCS          = YES
 EXTRACT_LOCAL_CLASSES  = YES
 EXTRACT_ANON_NSPACES   = YES
index 3006e4a83031d04ef692230abdd2e23f24883c9f..b451c9728164d2c2b35fc1b5b94e6b7eaca1c663 100644 (file)
@@ -33,7 +33,6 @@
 # the research papers on the package. Check out http://www.gromacs.org.
 
 if (DOT_EXECUTABLE)
-    message("Running dot...")
     file(GLOB DOT_INPUT_FILES ${GRAPHDIR}/*.dot)
     execute_process(COMMAND ${DOT_EXECUTABLE} -Tpng -O ${DOT_INPUT_FILES})
 endif()
index 67240f391f603074436a3e1c5297ecd2f3a572aa..6359b6391eca4b115a9df8541518e0b56ac6f98a 100644 (file)
@@ -325,6 +325,10 @@ class GeneratedFile(File):
         File.__init__(self, abspath, relpath, directory)
         self._generator_source_file = None
 
+    def scan_contents(self, sourcetree, keep_contents):
+        if os.path.exists(self.get_abspath()):
+            File.scan_contents(self, sourcetree, keep_contents)
+
     def set_generator_source(self, sourcefile):
         self._generator_source_file = sourcefile
 
index f666dbeaa7bf09b71cdb33db0cf8d2c69c6e6bc9..0026e60c093f9ede5fdf8238c090c07d6643e4a3 100755 (executable)
@@ -124,6 +124,7 @@ class Edge(object):
 
     def format(self):
         """Format this edge for 'dot'."""
+        # If you change these styles, update also the legend in modulegraph.md
         if self._fromnode.is_file_node() and self._tonode.is_file_node():
             properties = ''
         elif self._edgetype == EdgeType.intramodule:
@@ -409,6 +410,7 @@ class GraphBuilder(object):
         return edges
 
     def _get_module_color(self, modulegroup):
+        # If you change these styles, update also the legend in modulegraph.md
         if modulegroup == 'legacy':
             return 'fillcolor=grey75'
         elif modulegroup == 'analysismodules':
@@ -424,7 +426,8 @@ class GraphBuilder(object):
         style = []
         properties = []
         properties.append('shape=ellipse')
-        properties.append('URL="\\ref module_{0}"'.format(module.get_name()))
+        if module.is_documented():
+            properties.append('URL="\\ref {0}"'.format(module.get_name()))
         if not module.is_documented():
             fillcolor = self._get_module_color('legacy')
         else:
@@ -463,21 +466,6 @@ class GraphBuilder(object):
                     edges.append(edge)
         return edges
 
-
-    def _create_legend_node(self, label, modulegroup):
-        if modulegroup:
-            nodename = 'legend_' + modulegroup
-            fillcolor = self._get_module_color(modulegroup)
-        else:
-            nodename = 'legend_' + label
-            fillcolor = None
-        style = []
-        properties = []
-        if fillcolor:
-            style.append('filled')
-            properties.append(fillcolor)
-        return Node(nodename, label, style, properties)
-
     def create_modules_graph(self):
         """Create module dependency graph."""
         nodes = []
@@ -492,15 +480,6 @@ class GraphBuilder(object):
                 nodes.append(node)
             modulenodes[moduleobj] = node
         edges = self._create_module_edges(modulenodes)
-        # TODO: Consider adding invisible edges to order the nodes better.
-        # TODO: Consider adding legend for the edge types as well.
-        legendnode = Node('legend', 'legend')
-        legendnode.add_child(self._create_legend_node('legacy', 'legacy'))
-        legendnode.add_child(self._create_legend_node('analysis', 'analysismodules'))
-        legendnode.add_child(self._create_legend_node('utility', 'utilitymodules'))
-        legendnode.add_child(self._create_legend_node('mdrun', 'mdrun'))
-        legendnode.add_child(Node('legend_installed', 'installed', properties=['color=".66 .5 1"', 'penwidth=3']))
-        nodes.append(legendnode)
         graph = Graph(nodes, edges)
         graph.set_options(concentrate=False)
         return graph
index f9583280ffa956b5bcab51656d9161cdac4fac0f..9670069da027e289375bb672a76dce9d76fe9355 100644 (file)
@@ -375,7 +375,7 @@ their include dependencies.  Additionally, a file-level graph is produced for
 each module, showing the include dependencies within that module.  Currently,
 these are mostly for eye candy, but they can also be used for analyzing
 problematic dependencies to clean up the architecture.
-The output is put in `doxygen/depgraphs/` in the build tree.
+The output is put in `docs/doxygen/depgraphs/` in the build tree.
 
 As with `doc-check`, Python 2.7 is required (other versions may work, but have
 not been tested).  To get `.png` versions of the graphs, `graphviz` is
@@ -383,41 +383,9 @@ additionally required.
 
 ### Module graph ###
 
-The graph is written into `module-deps.dot.png`.
-
-Node colors:
-<dl>
-<dt>gray background</dt>
-<dd>undocumented module</dd>
-<dt>orange background</dt>
-<dd>documented utility modules</dd>
-<dt>red background</dt>
-<dd>documented analysis modules</dd>
-<dt>violet background</dt>
-<dd>documented MD execution modules</dd>
-<dt>light blue border</dt>
-<dd>module contains public API (installed) headers</dd>
-</dl>
-
-Edge colors (an edge with a certain color indicates that types above it in the
-list are not present):
-<dl>
-<dt>red</dt>
-<dd>invalid dependency</dd>
-<dt>gray</dt>
-<dd>legacy dependency
-(dependency on undocumented file, or to undocumented directories)</dd>
-<dt>solid black</dt>
-<dd>public header depends on the other module</dd>
-<dt>solid blue</dt>
-<dd>library header depends on the other module</dd>
-<dt>dashed blue</dt>
-<dd>source file depends on library header in the other module</dd>
-<dt>dashed black</dt>
-<dd>source file depends on public header in the other module</dd>
-<dt>dashed green</dt>
-<dd>test file depends on the other module</dd>
-</dl>
+The graph is written into `module-deps.dot.png`, and embedded into the Doxygen
+documentation: \ref page_modulegraph.  The embedded version contains a legend
+explaining the graph.
 
 ### File graph ###
 
diff --git a/docs/doxygen/lib/modulegraph.md b/docs/doxygen/lib/modulegraph.md
new file mode 100644 (file)
index 0000000..5467bed
--- /dev/null
@@ -0,0 +1,89 @@
+Module dependency graph {#page_modulegraph}
+=======================
+
+The graph below shows the dependencies between the source code modules,
+computed from include statements in the code.
+For documented modules (those that do not have a gray background), clicking on
+the module takes you to the module documentation.
+Legend for the graph can be found below the graph.
+
+\ifnot xml
+\dotfile module-deps.dot
+\endif
+
+Legend
+======
+
+The graph below annotates the colors and line styles used in the module
+dependency graph above.  More detailed textual annotation is below the graph.
+
+\dot
+digraph legend {
+    node [fontname="FreeSans",fontsize=10,height=.2,shape=box]
+    edge [fontname="FreeSans",fontsize=10]
+    rankdir = "LR"
+    subgraph cluster_nodes {
+        label = "Nodes"
+        legacy    [label="undocumented", fillcolor=grey75, style="filled"]
+        analysis  [label="analysis", fillcolor="0 .2 1", style="filled"]
+        utility   [label="utility", fillcolor=".08 .2 1", style="filled"]
+        mdrun     [label="mdrun", fillcolor=".75 .2 1", style="filled"]
+        installed [label="installed", color=".66 .5 1", penwidth=3]
+    }
+    subgraph cluster_edges {
+        label = "Edges"
+        node [label="<any>"]
+        invalid1 -> invalid2 [label="invalid", color=red]
+        legacy1 -> legacy2 [label="legacy", color=grey75]
+        legacy2 [label="undoc"]
+        public1 -> public2 [label="public", color=black]
+        public1 [label="public"]
+        public2 [label="public"]
+        library1 -> library2 [label="library", color=".66 .8 .8"]
+        library1 [label="library"]
+        pubimpl1 -> pubimpl2 [color=black, style=dashed]
+        pubimpl1 [label="internal"]
+        pubimpl2 [label="public"]
+        libimpl1 -> libimpl2 [color=".66 .8 .8", style=dashed]
+        libimpl1 [label="internal"]
+        libimpl2 [label="library"]
+        test1 -> test2 [label="test", color=".33 .8 .8", style=dashed]
+        test1 [label="test"]
+    }
+    legacy -> invalid1 [style="invis"]
+}
+\enddot
+
+Node colors:
+<dl>
+<dt>gray background</dt>
+<dd>undocumented module</dd>
+<dt>orange background</dt>
+<dd>documented utility modules</dd>
+<dt>red background</dt>
+<dd>documented analysis modules</dd>
+<dt>violet background</dt>
+<dd>documented MD execution modules</dd>
+<dt>light blue border</dt>
+<dd>module contains public API (installed) headers</dd>
+</dl>
+
+Edge colors (an edge with a certain color indicates that types above it in the
+list are not present):
+<dl>
+<dt>red</dt>
+<dd>invalid dependency</dd>
+<dt>gray</dt>
+<dd>legacy dependency
+(dependency on undocumented file, or to undocumented directories)</dd>
+<dt>solid black</dt>
+<dd>public header depends on the other module</dd>
+<dt>solid blue</dt>
+<dd>library header depends on the other module</dd>
+<dt>dashed blue</dt>
+<dd>source file depends on library header in the other module</dd>
+<dt>dashed black</dt>
+<dd>source file depends on public header in the other module</dd>
+<dt>dashed green</dt>
+<dd>test file depends on the other module</dd>
+</dl>
index 727f978c4371324d5a477c56006706d451695abc..efe5a64ff577fc3088546c3f70ea839c22ae0a36 100644 (file)
@@ -38,6 +38,9 @@ not compile.  It is not included in the build.
 </dd>
 \endif
 </dl>
+\if libapi
+\subpage page_modulegraph
+\endif
 
 Organization under `src/gromacs/`
 ---------------------------------