Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
[sonar] Mention new configuration keys.
[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_1ExecPar.xml',
34     'build/xml/classsimgrid_1_1s4u_1_1ExecSeq.xml',
35     'build/xml/classsimgrid_1_1s4u_1_1Exec.xml',
36     'build/xml/classsimgrid_1_1s4u_1_1Host.xml',
37     'build/xml/classsimgrid_1_1s4u_1_1Io.xml',
38     'build/xml/classsimgrid_1_1s4u_1_1Link.xml',
39     'build/xml/classsimgrid_1_1s4u_1_1Mailbox.xml',
40     'build/xml/classsimgrid_1_1s4u_1_1Mutex.xml',
41     'build/xml/classsimgrid_1_1s4u_1_1NetZone.xml',
42     'build/xml/classsimgrid_1_1s4u_1_1Semaphore.xml',
43     'build/xml/classsimgrid_1_1s4u_1_1VirtualMachine.xml',
44     'build/xml/actor_8h.xml',
45     'build/xml/barrier_8h.xml',
46     'build/xml/cond_8h.xml',
47     'build/xml/engine_8h.xml',
48     'build/xml/forward_8h.xml',
49     'build/xml/host_8h.xml',
50     'build/xml/link_8h.xml',
51     'build/xml/mailbox_8h.xml',
52     'build/xml/msg_8h.xml',
53     'build/xml/mutex_8h.xml',
54     'build/xml/semaphore_8h.xml',
55     'build/xml/vm_8h.xml',
56     'build/xml/zone_8h.xml'
57 ]
58
59 python_modules = [
60     'simgrid'
61 ]
62 python_ignore = [
63     'simgrid.ActorKilled'
64 ]
65
66
67 ############ Search the python elements in the source, and report undocumented ones
68 ############
69
70 # data structure in which we store the declaration we find
71 python_decl = {}
72
73 def handle_python_module(fullname, englobing, elm):
74     """Recursive function exploring the python modules."""
75
76     def found_decl(kind, obj):
77         """Helper function that add an object in the python_decl data structure"""
78         if kind not in python_decl:
79             python_decl[kind] = []
80         python_decl[kind].append(obj)
81
82
83     if fullname in python_ignore:
84         print ("Ignore Python symbol '{}' as requested.".format(fullname))
85         return
86
87     if inspect.isroutine(elm) and inspect.isclass(englobing):
88         found_decl("method", fullname)
89 #        print('.. automethod:: {}'.format(fullname))
90     elif inspect.isroutine(elm) and (not inspect.isclass(englobing)):
91         found_decl("function", fullname)
92 #        print('.. autofunction:: {}'.format(fullname))
93     elif inspect.isdatadescriptor(elm):
94         found_decl("attribute", fullname)
95 #        print('.. autoattribute:: {}'.format(fullname))
96     elif isinstance(elm, str) or isinstance(elm, int): # We do have such a data, directly in the SimGrid top module
97         found_decl("data", fullname)
98 #        print('.. autodata:: {}'.format(fullname))
99     elif inspect.ismodule(elm) or inspect.isclass(elm):
100         for name, data in inspect.getmembers(elm):
101             if name.startswith('__'):
102                 continue
103 #            print("Recurse on {}.{}".format(fullname, name))
104             handle_python_module("{}.{}".format(fullname, name), elm, data)
105     else:
106         print('UNHANDLED TYPE {} : {!r} Type: {}'.format(fullname, elm, type(elm)))
107
108 # Start the recursion on the provided Python modules
109 for name in python_modules:
110     try:
111         module = __import__(name)
112     except Exception:
113         print("Cannot import {}. Did you set PYTHONPATH=../lib accordingly?".format(name))
114         sys.exit(1)
115     for sub in dir(module):
116         if sub[0] == '_':
117             continue
118         handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
119
120 # Forget about the python declarations that were actually done in the RST
121 for kind in python_decl:
122     with os.popen('grep \'[[:blank:]]*auto{}::\' source/*rst|sed \'s/^.*auto{}:: //\''.format(kind, kind)) as pse:
123         for fullname in (l.strip() for l in pse):
124             if fullname not in python_decl[kind]:
125                 print("Warning: {} documented but declaration not found in python.".format(fullname))
126             else:
127                 python_decl[kind].remove(fullname)
128 # Dump the missing ones
129 for kind in python_decl:
130     for fullname in python_decl[kind]:
131         print(" .. auto{}:: {}".format(kind, fullname))
132
133 ################ And now deal with Doxygen declarations
134 ################
135
136 doxy_funs = {} # {classname: {func_name: [args]} }
137 doxy_vars = {} # {classname: [names]}
138
139 # find the declarations in the XML files
140 for arg in xml_files:
141     if arg[-4:] != '.xml':
142         print ("Argument '{}' does not end with '.xml'".format(arg))
143         continue
144     #print("Parse file {}".format(arg))
145     tree = ET.parse(arg)
146     for elem in tree.findall(".//compounddef"):
147         if elem.attrib["kind"] == "class":
148             if elem.attrib["prot"] != "public":
149                 continue
150             if "compoundname" in elem:
151                 raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
152             compoundname = elem.find("compoundname").text
153             #print ("compoundname {}".format(compoundname))
154         elif elem.attrib["kind"] == "file":
155             compoundname = ""
156         else:
157             print("Element {} is of kind {}".format(elem.attrib["id"], elem.attrib["kind"]))
158
159         for member in elem.findall('.//memberdef'):
160             if member.attrib["prot"] != "public":
161                 continue
162             kind = member.attrib["kind"]
163             name = member.find("name").text
164             #print("kind:{} compoundname:{} name:{}".format( kind,compoundname, name))
165             if kind == "variable":
166                 if compoundname not in doxy_vars:
167                     doxy_vars[compoundname] = []
168                 doxy_vars[compoundname].append(name)
169             elif kind == "function":
170                 args = member.find('argsstring').text
171                 args = re.sub('\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
172
173                 if compoundname not in doxy_funs:
174                     doxy_funs[compoundname] = {}
175                 if name not in doxy_funs[compoundname]:
176                     doxy_funs[compoundname][name] = []
177                 doxy_funs[compoundname][name].append(args)
178             elif kind == "friend":
179                 pass # Ignore friendship
180             else:
181                 print ("member {}::{} is of kind {}".format(compoundname, name, kind))
182
183 # Forget about the declarations that are done in the RST
184 with os.popen('grep autodoxymethod:: source/*rst|sed \'s/^.*autodoxymethod:: //\'') as pse:
185     for line in (l.strip() for l in pse):
186         (klass, obj, args) = (None, None, None)
187         if "(" in line:
188             (line, args) = line.split('(', 1)
189             args = "({}".format(args)
190         if '::' in line:
191             (klass, obj) = line.rsplit('::', 1)
192         else:
193             (klass, obj) = ("", line)
194
195         if klass not in doxy_funs:
196             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
197             continue
198         if obj not in doxy_funs[klass]:
199             print("Warning: Object '{}' documented but not found in '{}'".format(line, klass))
200 #            for obj in doxy_funs[klass]:
201 #                print("  found: {}::{}".format(klass, obj))
202         elif len(doxy_funs[klass][obj])==1:
203             del doxy_funs[klass][obj]
204         elif args not in doxy_funs[klass][obj]:
205             print("Warning: Function {}{} not found in {}".format(obj, args, klass))
206         else:
207             #print("Found {} in {}".format(line, klass))
208             doxy_funs[klass][obj].remove(args)
209             if len(doxy_funs[klass][obj]) == 0:
210                 del doxy_funs[klass][obj]
211 with os.popen('grep autodoxyvar:: source/*rst|sed \'s/^.*autodoxyvar:: //\'') as pse:
212     for line in (l.strip() for l in pse):
213         (klass, var) = line.rsplit('::', 1)
214
215         if klass not in doxy_vars:
216             print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
217             continue
218         if var not in doxy_vars[klass]:
219             print("Warning: Object {} documented but not found in {}".format(line, klass))
220         else:
221 #            print("Found {} in {}".format(line, klass))
222             doxy_vars[klass].remove(var)
223             if len(doxy_vars[klass]) == 0:
224                 del doxy_vars[klass]
225
226 # Dump the undocumented Doxygen declarations 
227 for obj in sorted(doxy_funs):
228     for meth in sorted(doxy_funs[obj]):
229         for args in sorted(doxy_funs[obj][meth]):
230             print(".. autodoxymethod:: {}::{}{}".format(obj, meth, args))
231
232 for obj in doxy_vars:
233     for meth in sorted(doxy_vars[obj]):
234         print(".. autodoxyvar:: {}::{}".format(obj, meth))