1 #! /usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # Copyright (c) 2019-2023. The SimGrid Team. All rights reserved.
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.
10 Search for symbols documented in both the XML files produced by Doxygen and the python modules,
11 but not documented with breathe in the RST files.
13 This script is tailored to SimGrid own needs.
15 If you are missing some dependencies, try: pip3 install --requirement docs/requirements.txt
21 import xml.etree.ElementTree as ET
25 'build/xml/classsimgrid_1_1s4u_1_1Activity.xml',
26 'build/xml/classsimgrid_1_1s4u_1_1Actor.xml',
27 'build/xml/classsimgrid_1_1s4u_1_1Barrier.xml',
28 'build/xml/classsimgrid_1_1s4u_1_1Comm.xml',
29 'build/xml/classsimgrid_1_1s4u_1_1ConditionVariable.xml',
30 'build/xml/classsimgrid_1_1s4u_1_1Disk.xml',
31 'build/xml/classsimgrid_1_1s4u_1_1Engine.xml',
32 'build/xml/classsimgrid_1_1s4u_1_1Exec.xml',
33 'build/xml/classsimgrid_1_1s4u_1_1Host.xml',
34 'build/xml/classsimgrid_1_1s4u_1_1Io.xml',
35 'build/xml/classsimgrid_1_1s4u_1_1Link.xml',
36 'build/xml/classsimgrid_1_1s4u_1_1Mailbox.xml',
37 'build/xml/classsimgrid_1_1s4u_1_1Mutex.xml',
38 'build/xml/classsimgrid_1_1s4u_1_1NetZone.xml',
39 'build/xml/classsimgrid_1_1s4u_1_1Semaphore.xml',
40 'build/xml/classsimgrid_1_1s4u_1_1VirtualMachine.xml',
41 'build/xml/classsimgrid_1_1xbt_1_1signal_3_01R_07P_8_8_8_08_4.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/mutex_8h.xml',
52 'build/xml/semaphore_8h.xml',
53 'build/xml/vm_8h.xml',
54 'build/xml/zone_8h.xml'
65 ############ Search the python elements in the source, and report undocumented ones
68 # data structure in which we store the declaration we find
71 def handle_python_module(fullname, englobing, elm):
72 """Recursive function exploring the python modules."""
74 def found_decl(kind, obj):
75 """Helper function that add an object in the python_decl data structure"""
76 if kind not in python_decl:
77 python_decl[kind] = []
78 python_decl[kind].append(obj)
81 if fullname in python_ignore:
82 print("Ignore Python symbol '{}' as requested.".format(fullname))
85 if inspect.isroutine(elm) and inspect.isclass(englobing):
86 found_decl("method", fullname)
87 # print('.. automethod:: {}'.format(fullname))
88 elif inspect.isroutine(elm) and (not inspect.isclass(englobing)):
89 found_decl("function", fullname)
90 # print('.. autofunction:: {}'.format(fullname))
91 elif inspect.isdatadescriptor(elm):
92 found_decl("attribute", fullname)
93 # print('.. autoattribute:: {}'.format(fullname))
94 elif isinstance(elm, (int, str)): # We do have such a data, directly in the SimGrid top module
95 found_decl("data", fullname)
96 # print('.. autodata:: {}'.format(fullname))
97 elif inspect.ismodule(elm) or inspect.isclass(elm):
98 for name, data in inspect.getmembers(elm):
99 if name.startswith('__'):
101 # print("Recurse on {}.{}".format(fullname, name))
102 handle_python_module("{}.{}".format(fullname, name), elm, data)
103 elif inspect.isclass(type(elm)):
104 found_decl("enumvalue", fullname)
105 print('.. autoenumvalue:: {}'.format(fullname))
107 print('UNHANDLED TYPE {} : {!r} Type: {} Englobing: {} str: {} Members: \n{}\n'.format(fullname, elm, type(elm), englobing, str(elm), inspect.getmembers(elm)))
109 # Start the recursion on the provided Python modules
110 for name in python_modules:
112 module = __import__(name)
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")
118 module = __import__(name)
120 print("Cannot import {}, even with PYTHONPATH=../lib".format(name))
123 print("Cannot import {}".format(name))
125 for sub in dir(module):
128 handle_python_module("{}.{}".format(name, sub), module, getattr(module, sub))
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))
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))
143 ################ And now deal with Doxygen declarations
146 doxy_funs = {} # {classname: {func_name: [args]} }
147 doxy_vars = {} # {classname: [names]}
148 doxy_type = {} # {classname: [names]}
150 # find the declarations in the XML files
151 for arg in xml_files:
152 if arg[-4:] != '.xml':
153 print("Argument '{}' does not end with '.xml'".format(arg))
155 #print("Parse file {}".format(arg))
157 for elem in tree.findall(".//compounddef"):
158 if elem.attrib["kind"] == "class":
159 if elem.attrib["prot"] != "public":
161 if "compoundname" in elem:
162 raise Exception("Compound {} has no 'compoundname' child tag.".format(elem))
163 compoundname = elem.find("compoundname").text
164 #print("compoundname {}".format(compoundname))
165 elif elem.attrib["kind"] == "file":
167 elif elem.attrib["kind"] == "namespace":
168 compoundname = elem.find("compoundname").text
170 print("Element {} is of kind {}".format(elem.attrib["id"], elem.attrib["kind"]))
172 for member in elem.findall('.//memberdef'):
173 if member.attrib["prot"] != "public":
175 kind = member.attrib["kind"]
176 name = member.find("name").text
177 #print("kind:{} compoundname:{} name:{}".format( kind,compoundname, name))
178 if kind == "variable":
179 if compoundname not in doxy_vars:
180 doxy_vars[compoundname] = []
181 doxy_vars[compoundname].append(name)
182 elif kind == "function":
183 args = member.find('argsstring').text
184 args = re.sub(r'\)[^)]*$', ')', args) # ignore what's after the parameters (eg, '=0' or ' const')
186 if compoundname not in doxy_funs:
187 doxy_funs[compoundname] = {}
188 if name not in doxy_funs[compoundname]:
189 doxy_funs[compoundname][name] = []
190 doxy_funs[compoundname][name].append(args)
191 elif kind == "typedef":
192 if compoundname not in doxy_type:
193 doxy_type[compoundname] = []
194 doxy_type[compoundname].append(name)
195 elif kind == "friend":
196 pass # Ignore friendship
198 print("member {}::{} is of kind {}".format(compoundname, name, kind))
200 # Forget about the declarations that are done in the RST
201 with os.popen('grep doxygenfunction:: find-missing.ignore source/*rst|sed \'s/^.*doxygenfunction:: //\'|sed \'s/ *const//\'') as pse:
202 for line in (l.strip() for l in pse):
203 (klass, obj, args) = (None, None, None)
205 (line, args) = line.split('(', 1)
206 args = "({}".format(args)
208 (klass, obj) = line.rsplit('::', 1)
210 (klass, obj) = ("", line)
212 if klass not in doxy_funs:
213 print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
215 if obj not in doxy_funs[klass]:
216 print("Warning: Object '{}' documented but not found in '{}'".format(line, klass))
217 # for obj in doxy_funs[klass]:
218 # print(" found: {}::{}".format(klass, obj))
219 elif len(doxy_funs[klass][obj]) == 1:
220 del doxy_funs[klass][obj]
221 elif args not in doxy_funs[klass][obj]:
222 print("Warning: Function {}{} not found in {}".format(obj, args, klass))
224 #print("Found {} in {}".format(line, klass))
225 doxy_funs[klass][obj].remove(args)
226 if len(doxy_funs[klass][obj]) == 0:
227 del doxy_funs[klass][obj]
228 with os.popen('grep doxygenvariable:: find-missing.ignore source/*rst|sed \'s/^.*doxygenvariable:: //\'') as pse:
229 for line in (l.strip() for l in pse):
230 (klass, var) = line.rsplit('::', 1)
232 if klass not in doxy_vars:
233 print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
235 if var not in doxy_vars[klass]:
236 print("Warning: Object {} documented but not found in '{}'".format(line, klass))
238 # print("Found {} in {}".format(line, klass))
239 doxy_vars[klass].remove(var)
240 if len(doxy_vars[klass]) == 0:
242 with os.popen('grep doxygentypedef:: find-missing.ignore source/*rst|sed \'s/^.*doxygentypedef:: //\'') as pse:
243 for line in (l.strip() for l in pse):
245 (klass, typ) = line.rsplit('::', 1)
247 (klass, typ) = ('', line)
249 if klass not in doxy_type:
250 print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
252 if typ not in doxy_type[klass]:
253 print("Warning: Type {} documented but not found in '{}'".format(line, klass))
255 # print("Found {} in {}".format(line, klass))
256 doxy_type[klass].remove(typ)
257 if len(doxy_type[klass]) == 0:
260 # Dump the undocumented Doxygen declarations
261 for obj in sorted(doxy_funs):
262 for meth in sorted(doxy_funs[obj]):
263 for args in sorted(doxy_funs[obj][meth]):
265 print(".. doxygenfunction:: {}{}".format(meth, args))
267 print(".. doxygenfunction:: {}::{}{}".format(obj, meth, args))
269 for obj in doxy_vars:
270 for meth in sorted(doxy_vars[obj]):
271 print(".. doxygenvariable:: {}::{}".format(obj, meth))
273 for obj in doxy_type:
274 for meth in sorted(doxy_type[obj]):
276 print(".. doxygentypedef:: {}".format(meth))
278 print(".. doxygentypedef:: {}::{}".format(obj, meth))