Logo AND Algorithmique Numérique Distribuée

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