]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Add two utility scripts that help debugging module problems.
authorWolfgang Bangerth <bangerth@colostate.edu>
Fri, 23 May 2025 15:31:24 +0000 (09:31 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Tue, 27 May 2025 14:58:54 +0000 (08:58 -0600)
contrib/utilities/detect_module_cycles.py [new file with mode: 0644]
contrib/utilities/find_module_partition_dependencies.py [new file with mode: 0644]

diff --git a/contrib/utilities/detect_module_cycles.py b/contrib/utilities/detect_module_cycles.py
new file mode 100644 (file)
index 0000000..620203d
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/bin/python3
+
+## ------------------------------------------------------------------------
+##
+## SPDX-License-Identifier: LGPL-2.1-or-later
+## Copyright (C) 2025 by the deal.II authors
+##
+## This file is part of the deal.II library.
+##
+## Part of the source code is dual licensed under Apache-2.0 WITH
+## LLVM-exception OR LGPL-2.1-or-later. Detailed license information
+## governing the source code and code contributions can be found in
+## LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
+##
+## ------------------------------------------------------------------------
+
+# Read through a list of the C++ files and see if we have any cycles
+# in the 'export module' or 'module' and 'import' statements of these
+# files. The script here assumes that all of the files export module
+# partitions of the overall module 'dealii', and import module
+# partitions from the same module.
+#
+# Call this script via
+#   python3 contrib/utilities/detect_module_cycles.py
+# from the top-level directory of a build directory.
+
+from glob import glob
+import networkx as nx
+import re
+
+
+match_imports = re.compile(r"import *: *(.*);")
+match_exports = re.compile(r"export module dealii *: *(.*);")
+
+# For a given .cc file, read through all the lines and extract the
+# ones that correspond to export or import statements. For those, add
+# a link to the graph.
+def add_imports_for_file(interface_module_partition_file, G) :
+    f = open(interface_module_partition_file)
+    lines = f.readlines()
+    f.close()
+    
+    for line in lines :
+        m = match_exports.match(line)
+        if m :
+            module_partition = m.group(1)
+            # There can only be one 'export' statement per file, so
+            # stop reading:
+            break
+
+    for line in lines :
+        m = match_imports.match(line)
+        if m :
+            imported_partition = m.group(1)
+            G.add_edge(module_partition,
+                       imported_partition)
+
+
+
+# Create a list of all source files in the build folder
+filelist = glob("module/interface_module_partitions/**/*.cc*", recursive=True)
+assert filelist, "Please call the script from the top-level directory."
+
+# For each header file, add the imports as the edges of a directed graph.
+G = nx.DiGraph()
+for interface_module_partition_file in filelist :
+    add_imports_for_file(interface_module_partition_file, G)
+
+# Then figure out whether there are cycles and if so, print them:
+cycles = nx.simple_cycles(G)
+cycles_as_list = list(cycles)
+if (len(cycles_as_list) > 0) :
+    print (f"Cycles in the module partition graph detected!")
+    for cycle in cycles_as_list :
+        print(cycle)
+    exit(1)
diff --git a/contrib/utilities/find_module_partition_dependencies.py b/contrib/utilities/find_module_partition_dependencies.py
new file mode 100644 (file)
index 0000000..6bdad6b
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/python3
+
+## ------------------------------------------------------------------------
+##
+## SPDX-License-Identifier: LGPL-2.1-or-later
+## Copyright (C) 2025 by the deal.II authors
+##
+## This file is part of the deal.II library.
+##
+## Part of the source code is dual licensed under Apache-2.0 WITH
+## LLVM-exception OR LGPL-2.1-or-later. Detailed license information
+## governing the source code and code contributions can be found in
+## LICENSE.md and CONTRIBUTING.md at the top level directory of deal.II.
+##
+## ------------------------------------------------------------------------
+
+# Given a module partition name 'A' as the first argument, find all other module
+# partitions 'B' it depends on either by way of direct
+#   import :B;
+# statements, or because a module partition 'B' so imported itself has a statement
+# of the form
+#   export import :C;
+#
+# Call this script via
+#   python3 contrib/utilities/find_module_partition_dependencies.py A
+# where 'A' is the name of the module partition (which must be defined in
+# one of the files in the current build directory).
+
+
+from glob import glob
+import networkx as nx
+import re
+import sys
+
+module_partition_name = sys.argv[1]
+partition_to_filename = dict()
+
+match_imports = re.compile(r"(export )?import *: *(.*);")
+match_exports = re.compile(r"(export )?module dealii *: *(.*);")
+
+
+# For a given source file, read through all the lines and extract the
+# ones that correspond to export or import statements. For those, add
+# a link to the graph.
+def add_imports_for_file(module_partition_file, G) :
+    f = open(module_partition_file)
+    lines = f.readlines()
+    f.close()
+
+    module_partition = ""
+    for line in lines :
+        m = match_exports.match(line)
+        if m :
+            module_partition = m.group(2)
+            # There can only be one 'export' statement per file, so
+            # stop reading after recording the mapping from partition
+            # name to file name:
+            partition_to_filename[module_partition] = module_partition_file
+            break
+    assert module_partition != "", f"File <{module_partition_file} does not seem to export a module partition!"
+
+    for line in lines :
+        m = match_imports.match(line)
+        if m :
+            imported_partition = m.group(2)
+            G.add_edge(module_partition,
+                       imported_partition)
+
+
+
+# Create a list of all source files in the build folder
+filelist = glob("module/*_module_partitions/**/*.cc*", recursive=True)
+assert filelist, "Please call the script from the top-level build directory."
+
+
+# For each header file, add the imports as the edges of a directed graph.
+G = nx.DiGraph()
+G.add_node(module_partition_name)  # Make sure the graph has at least one node
+for module_partition_file in filelist :
+    add_imports_for_file(module_partition_file, G)
+
+assert module_partition_name in partition_to_filename, \
+    "The module partition given as argument could not be found in the module units found"
+    
+
+# Now find everything that is upstream of the module partition
+# specified on the command line:
+print(f"The dependencies of module partition '{module_partition_name}' are:")
+for node in nx.dfs_postorder_nodes(G, source=module_partition_name) :
+    if node != module_partition_name :
+        if node in partition_to_filename :
+            print(f"  {node}, implemented in {partition_to_filename[node]}")
+        else :
+            print(f"  {node}, implemented in unknown file")
+
+n_dependencies = sum(1 for _ in nx.dfs_postorder_nodes(G, source=module_partition_name))-1
+print(f"{n_dependencies} partitions are upstream dependencies for '{module_partition_name}'")

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.