-
Notifications
You must be signed in to change notification settings - Fork 7.1k
/
run_doxygen.py
300 lines (226 loc) · 9.42 KB
/
run_doxygen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# Extension to generate Doxygen XML include files, with IDF config & soc macros included
from __future__ import print_function
from __future__ import unicode_literals
from io import open
import os
import os.path
import re
import subprocess
from .util import copy_if_modified
ALL_KINDS = [
("function", "Functions"),
("union", "Unions"),
("struct", "Structures"),
("define", "Macros"),
("typedef", "Type Definitions"),
("enum", "Enumerations")
]
"""list of items that will be generated for a single API file
"""
def setup(app):
# The idf_build_system extension will emit this event once it has generated documentation macro definitions
app.connect('idf-defines-generated', generate_doxygen)
return {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': '0.2'}
def generate_doxygen(app, defines):
build_dir = os.path.dirname(app.doctreedir.rstrip(os.sep))
# Call Doxygen to get XML files from the header files
print("Calling Doxygen to generate latest XML files")
doxy_env = os.environ
doxy_env.update({
"ENV_DOXYGEN_DEFINES": " ".join('{}={}'.format(key, value) for key, value in defines.items()),
"IDF_PATH": app.config.idf_path,
"IDF_TARGET": app.config.idf_target,
})
doxyfile = os.path.join(app.config.docs_root, "Doxyfile")
print("Running doxygen with doxyfile {}".format(doxyfile))
# It's possible to have doxygen log warnings to a file using WARN_LOGFILE directive,
# but in some cases it will still log an error to stderr and return success!
#
# So take all of stderr and redirect it to a logfile (will contain warnings and errors)
logfile = os.path.join(build_dir, "doxygen-warning-log.txt")
with open(logfile, "w") as f:
# note: run Doxygen in the build directory, so the xml & xml_in files end up in there
subprocess.check_call(["doxygen", doxyfile], env=doxy_env, cwd=build_dir, stderr=f)
# Doxygen has generated XML files in 'xml' directory.
# Copy them to 'xml_in', only touching the files which have changed.
copy_if_modified(os.path.join(build_dir, 'xml/'), os.path.join(build_dir, 'xml_in/'))
# Generate 'api_name.inc' files from the Doxygen XML files
convert_api_xml_to_inc(app, doxyfile)
def convert_api_xml_to_inc(app, doxyfile):
""" Generate header_file.inc files
with API reference made of doxygen directives
for each header file
specified in the 'INPUT' statement of the Doxyfile.
"""
build_dir = app.config.build_dir
xml_directory_path = "{}/xml".format(build_dir)
inc_directory_path = "{}/inc".format(build_dir)
if not os.path.isdir(xml_directory_path):
raise RuntimeError("Directory {} does not exist!".format(xml_directory_path))
if not os.path.exists(inc_directory_path):
os.makedirs(inc_directory_path)
header_paths = get_doxyfile_input_paths(app, doxyfile)
print("Generating 'api_name.inc' files with Doxygen directives")
for header_file_path in header_paths:
api_name = get_api_name(header_file_path)
inc_file_path = inc_directory_path + "/" + api_name + ".inc"
rst_output = generate_directives(header_file_path, xml_directory_path)
previous_rst_output = ''
if os.path.isfile(inc_file_path):
with open(inc_file_path, "r", encoding='utf-8') as inc_file_old:
previous_rst_output = inc_file_old.read()
if previous_rst_output != rst_output:
with open(inc_file_path, "w", encoding='utf-8') as inc_file:
inc_file.write(rst_output)
def get_doxyfile_input_paths(app, doxyfile_path):
"""Get contents of Doxyfile's INPUT statement.
Returns:
Contents of Doxyfile's INPUT.
"""
if not os.path.isfile(doxyfile_path):
raise RuntimeError("Doxyfile '{}' does not exist!".format(doxyfile_path))
print("Getting Doxyfile's INPUT")
with open(doxyfile_path, "r", encoding='utf-8') as input_file:
line = input_file.readline()
# read contents of Doxyfile until 'INPUT' statement
while line:
if line.find("INPUT") == 0:
break
line = input_file.readline()
doxyfile_INPUT = []
line = input_file.readline()
# skip input_file contents until end of 'INPUT' statement
while line:
if line.isspace():
# we have reached the end of 'INPUT' statement
break
# process only lines that are not comments
if line.find("#") == -1:
# extract header file path inside components folder
m = re.search("components/(.*\.h)", line) # noqa: W605 - regular expression
header_file_path = m.group(1)
# Replace env variable used for multi target header
header_file_path = header_file_path.replace("$(IDF_TARGET)", app.config.idf_target)
doxyfile_INPUT.append(header_file_path)
# proceed reading next line
line = input_file.readline()
return doxyfile_INPUT
def get_api_name(header_file_path):
"""Get name of API from header file path.
Args:
header_file_path: path to the header file.
Returns:
The name of API.
"""
api_name = ""
regex = r".*/(.*)\.h"
m = re.search(regex, header_file_path)
if m:
api_name = m.group(1)
return api_name
def generate_directives(header_file_path, xml_directory_path):
"""Generate API reference with Doxygen directives for a header file.
Args:
header_file_path: a path to the header file with API.
Returns:
Doxygen directives for the header file.
"""
api_name = get_api_name(header_file_path)
# in XLT file name each "_" in the api name is expanded by Doxygen to "__"
xlt_api_name = api_name.replace("_", "__")
xml_file_path = "%s/%s_8h.xml" % (xml_directory_path, xlt_api_name)
rst_output = ""
rst_output = ".. File automatically generated by 'gen-dxd.py'\n"
rst_output += "\n"
rst_output += get_rst_header("Header File")
rst_output += "* :component_file:`" + header_file_path + "`\n"
rst_output += "\n"
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
tree = ET.ElementTree(file=xml_file_path)
for kind, label in ALL_KINDS:
rst_output += get_directives(tree, kind)
return rst_output
def get_rst_header(header_name):
"""Get rst formatted code with a header.
Args:
header_name: name of header.
Returns:
Formatted rst code with the header.
"""
rst_output = ""
rst_output += header_name + "\n"
rst_output += "^" * len(header_name) + "\n"
rst_output += "\n"
return rst_output
def select_unions(innerclass_list):
"""Select unions from innerclass list.
Args:
innerclass_list: raw list with unions and structures
extracted from Dogygen's xml file.
Returns:
Doxygen directives with unions selected from the list.
"""
rst_output = ""
for line in innerclass_list.splitlines():
# union is denoted by "union" at the beginning of line
if line.find("union") == 0:
union_id, union_name = re.split(r"\t+", line)
rst_output += ".. doxygenunion:: "
rst_output += union_name
rst_output += "\n"
return rst_output
def select_structs(innerclass_list):
"""Select structures from innerclass list.
Args:
innerclass_list: raw list with unions and structures
extracted from Dogygen's xml file.
Returns:
Doxygen directives with structures selected from the list.
Note: some structures are excluded as described on code below.
"""
rst_output = ""
for line in innerclass_list.splitlines():
# structure is denoted by "struct" at the beginning of line
if line.find("struct") == 0:
# skip structures that are part of union
# they are documented by 'doxygenunion' directive
if line.find("::") > 0:
continue
struct_id, struct_name = re.split(r"\t+", line)
rst_output += ".. doxygenstruct:: "
rst_output += struct_name
rst_output += "\n"
rst_output += " :members:\n"
rst_output += "\n"
return rst_output
def get_directives(tree, kind):
"""Get directives for specific 'kind'.
Args:
tree: the ElementTree 'tree' of XML by Doxygen
kind: name of API "kind" to be generated
Returns:
Doxygen directives for selected 'kind'.
Note: the header with "kind" name is included.
"""
rst_output = ""
if kind in ["union", "struct"]:
innerclass_list = ""
for elem in tree.iterfind('compounddef/innerclass'):
innerclass_list += elem.attrib["refid"] + "\t" + elem.text + "\n"
if kind == "union":
rst_output += select_unions(innerclass_list)
else:
rst_output += select_structs(innerclass_list)
else:
for elem in tree.iterfind(
'compounddef/sectiondef/memberdef[@kind="%s"]' % kind):
name = elem.find('name')
rst_output += ".. doxygen%s:: " % kind
rst_output += name.text + "\n"
if rst_output:
all_kinds_dict = dict(ALL_KINDS)
rst_output = get_rst_header(all_kinds_dict[kind]) + rst_output + "\n"
return rst_output