2e0f8b5a70
On each documentation build (‘make html’), doxygen regenerates XML files. In addition to that, gen-dxd.py regenerates API reference files under _build/inc/. This results in Sphinx flagging about half of the input files as modified, and incremental builds taking long time. With this change, XML files generated by Doxygen are copied into docs/xml_in directory only when they are changed. Breathe is pointed to docs/xml_in directory instead of docs/xml. In addition to that, gen-dxd.py is modified to only write to the output file when contents change. Overall, incremental build time (with no source files changed) is reduced from ~7 minutes to ~8 seconds (on a particular OS X computer). Due to the way Breathe includes Doxygen XML files, there is still going to be a massive rebuild every time functions, enums, macros, structures are added or removed from the header files scanned by Doxygen, but at least individual .rst files can be edited at a much faster pace.
312 lines
9 KiB
Python
312 lines
9 KiB
Python
# gen-dxd.py - Generate Doxygen Directives
|
|
#
|
|
# This code is in the Public Domain (or CC0 licensed, at your option.)
|
|
# Unless required by applicable law or agreed to in writing, this
|
|
# software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
# CONDITIONS OF ANY KIND, either express or implied.
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
|
|
# Determime build directory
|
|
builddir = '_build'
|
|
if 'BUILDDIR' in os.environ:
|
|
builddir = os.environ['BUILDDIR']
|
|
|
|
# Script configuration
|
|
header_file_path_prefix = "../components/"
|
|
"""string: path prefix for header files.
|
|
"""
|
|
doxyfile_path = "Doxyfile"
|
|
"""string: path to a file containing header files to processs.
|
|
"""
|
|
xml_directory_path = "xml"
|
|
"""string: path to directory with XML files by Doxygen.
|
|
"""
|
|
inc_directory_path = os.path.join(builddir, 'inc')
|
|
"""string: path prefix for header files.
|
|
"""
|
|
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 get_doxyfile_input():
|
|
"""Get contents of Doxyfile's INPUT statement.
|
|
|
|
Returns:
|
|
Contents of Doxyfile's INPUT.
|
|
|
|
"""
|
|
if not os.path.isfile(doxyfile_path):
|
|
print "Doxyfile '%s' does not exist!" % doxyfile_path
|
|
sys.exit()
|
|
|
|
print "Getting Doxyfile's INPUT"
|
|
|
|
input_file = open(doxyfile_path, "r")
|
|
|
|
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(header_file_path_prefix + "(.*\.h)", line)
|
|
header_file_path = m.group(1)
|
|
doxyfile_INPUT += header_file_path + "\n"
|
|
# proceed reading next line
|
|
line = input_file.readline()
|
|
|
|
input_file.close()
|
|
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 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
|
|
|
|
|
|
def generate_directives(header_file_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 i in range(len(all_kinds)):
|
|
kind = all_kinds[i][0]
|
|
rst_output += get_directives(tree, kind)
|
|
|
|
return rst_output
|
|
|
|
|
|
def generate_api_inc_files():
|
|
"""Generate header_file.inc files
|
|
with API reference made of doxygen directives
|
|
for each header file
|
|
specified in the 'INPUT' statement of Doxyfile.
|
|
|
|
"""
|
|
|
|
if not os.path.isdir(xml_directory_path):
|
|
print "Directory %s does not exist!" % xml_directory_path
|
|
sys.exit()
|
|
|
|
if not os.path.exists(inc_directory_path):
|
|
os.makedirs(inc_directory_path)
|
|
|
|
list_to_generate = get_doxyfile_input()
|
|
print "Generating 'api_name.inc' files with Doxygen directives"
|
|
for header_file_path in list_to_generate.splitlines():
|
|
api_name = get_api_name(header_file_path)
|
|
inc_file_path = inc_directory_path + "/" + api_name + ".inc"
|
|
rst_output = generate_directives(header_file_path)
|
|
|
|
previous_rst_output = ''
|
|
if os.path.isfile(inc_file_path):
|
|
with open(inc_file_path, "r") as inc_file_old:
|
|
previous_rst_output = inc_file_old.read()
|
|
|
|
if previous_rst_output != rst_output:
|
|
with open(inc_file_path, "w") as inc_file:
|
|
inc_file.write(rst_output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
"""The main script that generates
|
|
Doxygen directives.
|
|
|
|
"""
|
|
|
|
# Process command line arguments, if any
|
|
if len(sys.argv) > 1:
|
|
if not os.path.isdir(xml_directory_path):
|
|
print "Directory %s does not exist!" % xml_directory_path
|
|
sys.exit()
|
|
header_file_path = sys.argv[1]
|
|
api_name = get_api_name(header_file_path)
|
|
if api_name:
|
|
rst_output = generate_directives(header_file_path)
|
|
print "Doxygen directives for '%s'" % header_file_path
|
|
print
|
|
print rst_output
|
|
else:
|
|
print "Options to execute 'gen-dxd.py' application:"
|
|
print "1: $ python gen-dxd.py"
|
|
print " Generate API 'header_file.inc' files for headers defined in '%s'" % doxyfile_path
|
|
print "2: $ python gen-dxd.py header_file_path"
|
|
print " Print out Doxygen directives for a single header file"
|
|
print " example: $ python gen-dxd.py mdns/include/mdns.h"
|
|
print " NOTE: Run Doxygen first to get XML files for the header file"
|
|
|
|
sys.exit()
|
|
|
|
# No command line arguments given
|
|
generate_api_inc_files()
|