1 #! /usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # Copyright (c) 2019-2020. 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 autodoxy in the RST files.
13 This script is tailored to SimGrid own needs and should be made more generic for autodoxy.
15 If you are missing some dependencies, try: pip3 install --requirement docs/requirements.txt
22 import xml.etree.ElementTree as ET
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'
68 ############ Search the python elements in the source, and report undocumented ones
71 # data structure in which we store the declaration we find
74 def handle_python_module(fullname, englobing, elm):
75 """Recursive function exploring the python modules."""
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)
84 if fullname in python_ignore:
85 print ("Ignore Python symbol '{}' as requested.".format(fullname))
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('__'):
104 # print("Recurse on {}.{}".format(fullname, name))
105 handle_python_module("{}.{}".format(fullname, name), elm, data)
107 print('UNHANDLED TYPE {} : {!r} Type: {}'.format(fullname, elm, type(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]}
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))
154 #print("Parse file {}".format(arg))
156 for elem in tree.findall(".//compounddef"):
157 if elem.attrib["kind"] == "class":
158 if elem.attrib["prot"] != "public":
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":
166 elif elem.attrib["kind"] == "namespace":
167 compoundname = elem.find("compoundname").text
169 print("Element {} is of kind {}".format(elem.attrib["id"], elem.attrib["kind"]))
171 for member in elem.findall('.//memberdef'):
172 if member.attrib["prot"] != "public":
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')
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
193 print ("member {}::{} is of kind {}".format(compoundname, name, kind))
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)
200 (line, args) = line.split('(', 1)
201 args = "({}".format(args)
203 (klass, obj) = line.rsplit('::', 1)
205 (klass, obj) = ("", line)
207 if klass not in doxy_funs:
208 print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
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))
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)
227 if klass not in doxy_vars:
228 print("Warning: {} documented, but class {} not found in doxygen.".format(line, klass))
230 if var not in doxy_vars[klass]:
231 print("Warning: Object {} documented but not found in {}".format(line, klass))
233 # print("Found {} in {}".format(line, klass))
234 doxy_vars[klass].remove(var)
235 if len(doxy_vars[klass]) == 0:
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))
244 for obj in doxy_vars:
245 for meth in sorted(doxy_vars[obj]):
246 print(".. autodoxyvar:: {}::{}".format(obj, meth))