Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'random_readwritestate' into 'master'
[simgrid.git] / docs / find-missing.py
1 #! /usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2019-2020. The SimGrid Team. All rights reserved.
5
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the license (GNU LGPL) which comes with this package.
8
9 """
10 Search for symbols documented in both the XML files produced by Doxygen and the python modules,
11 but not documented with autodoxy in the RST files.
12
13 This script is tailored to SimGrid own needs and should be made more generic for autodoxy.
14
15 If you are missing some dependencies, try:  pip3 install --requirement docs/requirements.txt
16 """
17
18 import fnmatch
19 import os
20 import re
21 import sys
22 import xml.etree.ElementTree as ET
23 import inspect
24
25 xml_files = [
26     'build/xml/classsimgrid_1_1s4u_1_1Activity.xml',
27     'build/xml/classsimgrid_1_1s4u_1_1Actor.xml',
28     'build/xml/classsimgrid_1_1s4u_1_1Barrier.xml',
29     'build/xml/classsimgrid_1_1s4u_1_1Comm.xml',
30     'build/xml/classsimgrid_1_1s4u_1_1ConditionVariable.xml',
31     'build/xml/classsimgrid_1_1s4u_1_1Disk.xml',
32     'build/xml/classsimgrid_1_1s4u_1_1Engine.xml',
33     'build/xml/classsimgrid_1_1s4u_1_1Exec.xml',
34     'build/xml/classsimgrid_1_1s4u_1_1Host.xml',
35     'build/xml/classsimgrid_1_1s4u_1_1Io.xml',
36     'build/xml/classsimgrid_1_1s4u_1_1Link.xml',
37     'build/xml/classsimgrid_1_1s4u_1_1Mailbox.xml',
38     'build/xml/classsimgrid_1_1s4u_1_1Mutex.xml',
39     'build/xml/classsimgrid_1_1s4u_1_1NetZone.xml',
40     'build/xml/classsimgrid_1_1s4u_1_1Semaphore.xml',
41     'build/xml/classsimgrid_1_1s4u_1_1VirtualMachine.xml',
42     'build/xml/namespacesimgrid_1_1s4u_1_1this__actor.xml',
43     'build/xml/actor_8h.xml',
44     'build/xml/barrier_8h.xml',
45     'build/xml/cond_8h.xml',
46     'build/xml/engine_8h.xml',
47     'build/xml/forward_8h.xml',
48     'build/xml/host_8h.xml',
49     'build/xml/link_8h.xml',
50     'build/xml/mailbox_8h.xml',
51     'build/xml/msg_8h.xml',
52     'build/xml/mutex_8h.xml',
53     'build/xml/semaphore_8h.xml',
54     'build/xml/vm_8h.xml',
55     'build/xml/zone_8h.xml'
56 ]
57
58 python_modules = [
59     'simgrid'
60 ]
61 python_ignore = [
62     'simgrid.ActorKilled'
63 ]
64
65
66 ############ Search the python elements in the source, and report undocumented ones
67 ############
68
69 # data structure in which we store the declaration we find
70 python_decl = {}
71
72 def handle_python_module(fullname, englobing, elm):
73     """Recursive function exploring the python modules."""
74
75     def found_decl(kind, obj):
76         """Helper function that add an object in the python_decl data structure"""
77         if kind not in python_decl:
78             python_decl[kind] = []
79         python_decl[kind].append(obj)
80
81
82     if fullname in python_ignore:
83         print ("Ignore Python symbol '{}' as requested.".format(fullname))
84         return
85
86     if inspect.isroutine(elm) and inspect.isclass(englobing):
87         found_decl("method", fullname)
88 #        print('.. automethod:: {}'.format(fullname))
89     elif inspect.isroutine(elm) and (not inspect.isclass(englobing)):
90         found_decl("function", fullname)
91 #        print('.. autofunction:: {}'.format(fullname))
92     elif inspect.isdatadescriptor(elm):
93         found_decl("attribute", fullname)
94 #        print('.. autoattribute:: {}'.format(fullname))
95     elif isinstance(elm, str) or isinstance(elm, int): # We do have such a data, directly in the SimGrid top module
96         found_decl("data", fullname)
97 #        print('.. autodata:: {}'.format(fullname))
98     elif inspect.ismodule(elm) or inspect.isclass(elm):
99         for name, data in inspect.getmembers(elm):
100             if name.startswith('__'):
101                 continue
102 #            print("Recurse on {}.{}".format(fullname, name))
103             handle_python_module("{}.{}".format(fullname, name), elm, data)
104     else:
105         print('UNHANDLED TYPE {} : {!r} Type: {}'.format(fullname, elm, type(elm)))
106
107 # Start the recursion on the provided Python modules
108 for name in python_modules:
109     try:
110         module = __import__(name)
111     except Exception:
112         if os.path.exists("../lib") and "../lib" not in sys.path:
113             print("Adding ../lib to PYTHONPATH as {} cannot be imported".format(name))
114             sys.path.append("../lib")
115             try:
116                 module = __import__(name)
117             except Exception:
118                 print("Cannot import {}, even with PYTHONPATH=../lib".format(name))
119                 sys.exit(1)
120         else:
121             print("Cannot import {}".format(name))
122             sys.exit(1)
123     for sub in dir(module):
124         if sub[0] == '_':
125             continue
126         handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
127
128 # Forget about the python declarations that were actually done in the RST
129 for kind in python_decl:
130     with os.popen('grep \'[[:blank:]]*auto{}::\' source/*rst|sed \'s/^.*auto{}:: //\''.format(kind, kind)) as pse:
131         for fullname in (l.strip() for l in pse):
132             if fullname not in python_decl[kind]:
133                 print("Warning: {} documented but declaration not found in python.".format(fullname))
134             else:
135                 python_decl[kind].remove(fullname)
136 # Dump the missing ones
137 for kind in python_decl:
138     for fullname in python_decl[kind]:
139         print(" .. auto{}:: {}".format(kind, fullname))
140
141 ################ And now deal with Doxygen declarations
142 ################
143
144 doxy_funs = {} # {classname: {func_name: [args]} }
145 doxy_vars = {} # {classname: [names]}
146
147 # find the declarations in the XML files
148 for arg in xml_files:
149     if arg[-4:] != '.xml':
150         print ("Argument '{}' does not end with '.xml'".format(arg))
151         continue
152     #print("Parse file {}".format(arg))
153     tree = ET.parse(arg)
154     for elem in tree.findall(".//compounddef"):
155         if elem.attrib["kind"] == "class":
156             if elem.attrib["prot"] != "public":
157                 continue
158             if "compoundname" in elem:
159                 raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
160             compoundname = elem.find("compoundname").text
161             #print ("compoundname {}".format(compoundname))
162         elif elem.attrib["kind"] == "file":
163             compoundname = ""
164         elif elem.attrib["kind"] == "namespace":
165             compoundname = elem.find("compoundname").text
166         else:
167             print("Element {} is of kind {}".format(elem.attrib["id"], elem.attrib["kind"]))
168
169         for member in elem.findall('.//memberdef'):
170             if member.attrib["prot"] != "public":
171                 continue
172             kind = member.attrib["kind"]
173             name = member.find("name").text
174             #print("kind:{} compoundname:{} name:{}".format( kind,compoundname, name))
175             if kind == "variable":
176                 if compoundname not in doxy_vars:
177                     doxy_vars[compoundname] = []
178                 doxy_vars[compoundname].append(name)
179             elif kind == "function":
180                 args = member.find('argsstring').text
181                 args = re.sub('\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
182
183                 if compoundname not in doxy_funs:
184                     doxy_funs[compoundname] = {}
185                 if name not in doxy_funs[compoundname]:
186                     doxy_funs[compoundname][name] = []
187                 doxy_funs[compoundname][name].append(args)
188             elif kind == "friend":
189                 pass # Ignore friendship
190             else:
191                 print ("member {}::{} is of kind {}".format(compoundname, name, kind))
192
193 # Forget about the declarations that are done in the RST
194 with os.popen('grep autodoxymethod:: find-missing.ignore source/*rst|sed \'s/^.*autodoxymethod:: //\'') as pse:
195     for line in (l.strip() for l in pse):
196         (klass, obj, args) = (None, None, None)
197         if "(" in line:
198             (line, args) = line.split('(', 1)
199             args = "({}".format(args)
200         if '::' in line:
201             (klass, obj) = line.rsplit('::', 1)
202         else:
203             (klass, obj) = ("", line)
204
205         if klass not in doxy_funs:
206             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
207             continue
208         if obj not in doxy_funs[klass]:
209             print("Warning: Object '{}' documented but not found in '{}'".format(line, klass))
210 #            for obj in doxy_funs[klass]:
211 #                print("  found: {}::{}".format(klass, obj))
212         elif len(doxy_funs[klass][obj])==1:
213             del doxy_funs[klass][obj]
214         elif args not in doxy_funs[klass][obj]:
215             print("Warning: Function {}{} not found in {}".format(obj, args, klass))
216         else:
217             #print("Found {} in {}".format(line, klass))
218             doxy_funs[klass][obj].remove(args)
219             if len(doxy_funs[klass][obj]) == 0:
220                 del doxy_funs[klass][obj]
221 with os.popen('grep autodoxyvar:: find-missing.ignore source/*rst|sed \'s/^.*autodoxyvar:: //\'') as pse:
222     for line in (l.strip() for l in pse):
223         (klass, var) = line.rsplit('::', 1)
224
225         if klass not in doxy_vars:
226             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
227             continue
228         if var not in doxy_vars[klass]:
229             print("Warning: Object {} documented but not found in {}".format(line, klass))
230         else:
231 #            print("Found {} in {}".format(line, klass))
232             doxy_vars[klass].remove(var)
233             if len(doxy_vars[klass]) == 0:
234                 del doxy_vars[klass]
235
236 # Dump the undocumented Doxygen declarations 
237 for obj in sorted(doxy_funs):
238     for meth in sorted(doxy_funs[obj]):
239         for args in sorted(doxy_funs[obj][meth]):
240             print(".. autodoxymethod:: {}::{}{}".format(obj, meth, args))
241
242 for obj in doxy_vars:
243     for meth in sorted(doxy_vars[obj]):
244         print(".. autodoxyvar:: {}::{}".format(obj, meth))