Logo AND Algorithmique Numérique Distribuée

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