mirror of
https://github.com/lvgl/lvgl.git
synced 2026-02-06 06:02:10 +08:00
1748 lines
60 KiB
Python
1748 lines
60 KiB
Python
"""doxygen_xml.py
|
|
|
|
Python Interface to Doxygen XML Output
|
|
|
|
DoxygenXml is the primary class defined herein.
|
|
It uses all the other classes, and is used by:
|
|
|
|
- ./docs/build.py via ./docs/api_doc_builder.py for the LVGL doc build to:
|
|
- edit Doxyfile to replace 2 string tokens
|
|
- run Doxygen
|
|
- load Doxygen XML output and use it to:
|
|
- generate API `.rst` pages,
|
|
- populate a set of `doxygen_xml.<...>` dictionaries, and
|
|
- use those dictionaries to determine in what LVGL documents
|
|
hyperlinks to API pages might be useful and appends them if they
|
|
are not already present.
|
|
|
|
- ./scripts/gen_json/gen_json.py => pycparser_monkeypatch.py to
|
|
- edit Doxyfile to replace 2 string tokens
|
|
- silently run Doxygen
|
|
- load Doxygen XML output and use it to:
|
|
- populate a set of `doxygen_xml.<...>` dictionaries, and
|
|
- monkey-patch pycparser AST (Abstract Syntax Tree) output to
|
|
extract API documentation into its JSON output.
|
|
|
|
"Loading Doxygen XML output" consists of
|
|
|
|
- opening and parsing the XML `index.xml` file generated by Doxygen which
|
|
is a summary of Doxygen-generated documentation linked to additional `.xml`
|
|
filenames that have more details. Note that `index.xml` has a complete list
|
|
of documented symbol-names from the C code. `./docs/api_doc_builder.py`
|
|
searches those names using search term(s) to determine in what LVGL
|
|
documents hyperlinks to API pages might be useful, and appends them
|
|
to those documents if they are not already present.
|
|
|
|
`index.xml` structure is described in the `doxygen/doxygen` repository
|
|
on GitHub under `./templates/xml/index.xsd`, and the structure of the
|
|
other files is described in `./templates/xml/compound.xsd`.
|
|
|
|
<doxygenindex ...>
|
|
Unlimited-length list of <compound> nodes.
|
|
<compound refid="..." kind="..."><name>...</name>
|
|
<member refid="..." kind="..."><name>...</name></member>
|
|
...
|
|
</compound>
|
|
...
|
|
</doxygenindex>
|
|
|
|
In `index.xml`, every <compound> element:
|
|
|
|
- always has attributes "refid" and "kind",
|
|
- always has exactly 1 <name> element, and
|
|
- is followed by an unbounded number of <member> elements.
|
|
|
|
Each <compound> element's 'kind' attribute will be one of the following:
|
|
|
|
class struct union interface
|
|
protocol category exception file
|
|
namespace group page example
|
|
dir type concept module
|
|
|
|
which is a group that applies to many languages. Since Doxygen
|
|
is parsing a C-language project, we will only find these in the
|
|
`index.xml` file:
|
|
|
|
- struct
|
|
- union
|
|
- file
|
|
- page (unused)
|
|
- dir (unused)
|
|
|
|
In `'index.xml`, every <member> element:
|
|
|
|
- always has attributes "refid" and "kind",
|
|
- always has exactly 1 <name> element.
|
|
|
|
The contents of the <member> elements differ based on the "kind" of
|
|
<compound> element they are contained by.
|
|
|
|
kind="struct"
|
|
|
|
- <member> sub-elements only have `kind="variable"` attributes.
|
|
|
|
kind="union"
|
|
|
|
- <member> sub-elements only have `kind="variable"` attributes.
|
|
|
|
kind="file"
|
|
|
|
- <member> sub-elements can have `kind` attributes:
|
|
- define
|
|
- enum
|
|
- enumvalue has refid that begins with containing enum's refid
|
|
- variable
|
|
- function
|
|
- typedef
|
|
|
|
- <member kind="define"...>
|
|
Has <name> child element with macro name. refid contains something
|
|
that looks like this:
|
|
|
|
"lv__obj_8h_1a8ec0fe9743c5c015162d64df591cd719"
|
|
/____________________________________________/
|
|
/_________/ |
|
|
| +-- ``id`` attribute of applicable item
|
|
|
|
|
+-- stem of `.xml` filename containing details
|
|
|
|
lv__obj_8h.xml has this format:
|
|
<doxygen ...>
|
|
<compounddef ...>
|
|
<compoundname>lv_obj.h</compoundname>
|
|
Section dealing with #include's (list, dependency graph, etc.)
|
|
<includes list>
|
|
<includedby list>
|
|
<incdepgraph ...>
|
|
</incdepgraph>
|
|
<invincdepgraph ...>
|
|
</invincdepgraph>
|
|
<sectiondef kind="...">
|
|
</sectiondef>
|
|
...List of <sectiondef> elements.
|
|
...kind can be:
|
|
- define
|
|
- enum
|
|
- var
|
|
- func
|
|
<sectiondef kind="define">
|
|
<memberdef kind="define" id="refid from index.xml">...</memberdef>
|
|
<memberdef>...</memberdef>
|
|
<memberdef>...</memberdef>
|
|
...List of all defines in file.
|
|
</sectiondef>
|
|
<sectiondef kind="enum">
|
|
<memberdef kind="enum" id="refid from index.xml">...</memberdef>
|
|
<type></type>
|
|
<name></name>
|
|
<enumvalue id="refid from index.xml" prot="public">
|
|
<name>LV_STATE_DEFAULT</name>
|
|
<initializer>= 0x0000</initializer>
|
|
<briefdescription>
|
|
</briefdescription>
|
|
<detaileddescription>
|
|
</detaileddescription>
|
|
</enumvalue>
|
|
...List of all the enumeration's enumvalue's.
|
|
</memberdef>
|
|
...List of all enums in the file.
|
|
</sectiondef>
|
|
<sectiondef kind="var">
|
|
<memberdef kind="variable" id="refid from index.xml" prot="public" static="no" extern="yes" mutable="no">
|
|
...List of all variables in file.
|
|
</sectiondef>
|
|
<sectiondef kind="func">
|
|
<memberdef kind="function" id="refid from index.xml" ...>
|
|
...List of all functions in file.
|
|
</sectiondef>
|
|
<briefdescription> @file @brief description
|
|
</briefdescription>
|
|
<detaileddescription> @file detailed description
|
|
</detaileddescription>
|
|
<programlisting> Only included if XML_PROGRAMLISTING = YES.
|
|
<codeline lineno="1">...</codeline>
|
|
<codeline lineno="2">...</codeline>
|
|
<codeline lineno="3">...</codeline>
|
|
</programlisting>
|
|
</compounddef>
|
|
</doxygen>
|
|
|
|
kind="page"
|
|
|
|
- Has no child members, but does have a child element <name>
|
|
(e.g. "deprecated" and "todo").
|
|
|
|
kind="dir"
|
|
|
|
- do not have <member> sub-elements
|
|
- contents of <name> element is a full path to a directory
|
|
- If sorted alphabetically, they form a sequence that can be
|
|
used to create something:
|
|
E:
|
|
E:/Dev
|
|
E:/Dev/lvgl
|
|
E:/Dev/lvgl/lvgl
|
|
E:/Dev/lvgl/lvgl/src
|
|
etc.
|
|
- refid="..." contains the stem of another `.xml` file that
|
|
contains more information on the directory, including any
|
|
documentation on it found by Doxygen. That XML file's structure is:
|
|
|
|
<refid>.xml:
|
|
------------
|
|
<compounddef id="<refid_from_above" kind="dir">
|
|
<compoundname>full_path_of_dir_wo_trailing_slash</compoundname>
|
|
<innerdir refid="stem_of_xml_file_containing_dir_details">subdirectory_path</innerdir>
|
|
<innerdir refid="stem_of_xml_file_containing_dir_details">subdirectory_path</innerdir>
|
|
<innerdir refid="stem_of_xml_file_containing_dir_details">subdirectory_path</innerdir>
|
|
... (list <innerdir> elements --- one for each subdirectory in directory)
|
|
...
|
|
<innerfile refid="stem_of_xml_file_containing_file_details">filename</innerfile>
|
|
<innerfile refid="stem_of_xml_file_containing_file_details">filename</innerfile>
|
|
<innerfile refid="stem_of_xml_file_containing_file_details">filename</innerfile>
|
|
... (list <innerfile> elements --- one for each file in directory)
|
|
...
|
|
<briefdescription>
|
|
Brief description of directory (Doxygen documentation)
|
|
</briefdescription>
|
|
<detaileddescription>
|
|
Detailed description of directory (Doxygen documentation)
|
|
</detaileddescription>
|
|
<location file="full_path_of_dir_with_trailing_slash"/>
|
|
</compounddef>
|
|
|
|
|
|
list
|
|
|
|
- define
|
|
- dir (unused)
|
|
- enum
|
|
- enumvalue
|
|
- example (unused)
|
|
- file
|
|
- function
|
|
- page (unused)
|
|
- struct
|
|
- typedef
|
|
- union
|
|
- variable
|
|
|
|
Most <compound> elements have child <member> elements with their own contents
|
|
depending on the 'kind' of <compound> element they are in.
|
|
|
|
This file defines classes, whose class names are the upper-case names above
|
|
except for the unused ones. These classes are used to build a data structure
|
|
that matches that of the `index.xml` file.
|
|
|
|
The list of classes is:
|
|
|
|
+-----------+----------------------------------------------------+
|
|
| Class | Adds New Instance to this Dictionary in __init__() |
|
|
+===========+====================================================+
|
|
| DEFINE | defines |
|
|
+-----------+----------------------------------------------------+
|
|
| ENUM | enums (have child ENUMVALUE objects) |
|
|
+-----------+----------------------------------------------------+
|
|
| VARIABLE | variables |
|
|
+-----------+----------------------------------------------------+
|
|
| NAMESPACE | namespaces |
|
|
+-----------+----------------------------------------------------+
|
|
| STRUCT | structures (have child STRUCT_FIELD objects) |
|
|
+-----------+----------------------------------------------------+
|
|
| UNION | unions (have child STRUCT_FIELD objects) |
|
|
+-----------+----------------------------------------------------+
|
|
| TYPEDEF | typedefs |
|
|
+-----------+----------------------------------------------------+
|
|
| FUNCTION | functions (have child FUNC_ARG objects) |
|
|
+-----------+----------------------------------------------------+
|
|
| GROUP | groups |
|
|
+-----------+----------------------------------------------------+
|
|
| FILE | files |
|
|
+-----------+----------------------------------------------------+
|
|
| CLASS | classes |
|
|
+-----------+----------------------------------------------------+
|
|
|
|
Additional classes:
|
|
|
|
- FUNC_ARG(object): (becomes child members of FUNCTION objects)
|
|
- STRUCT_FIELD(object): (becomes child members of STRUCT objects)
|
|
- ENUMVALUE(object): (becomes child members of ENUM objects)
|
|
- XMLSearch(object): (used only by ./scripts/gen_json/gen_json.py)
|
|
|
|
Each of the above Dictionary variables has entries with
|
|
|
|
- keys = documented symbol name from C code that Doxygen found
|
|
- values = instances of classes named above
|
|
|
|
Samples:
|
|
|
|
'defines': {'ZERO_MEM_SENTINEL': <doxygen_xml.DEFINE object at 0x000001FB5D866420>,
|
|
'LV_GLOBAL_DEFAULT': <doxygen_xml.DEFINE object at 0x000001FB5D866210>,
|
|
'LV_ASSERT_OBJ': <doxygen_xml.DEFINE object at 0x000001FB5D1EC080>,
|
|
'LV_TRACE_OBJ_CREATE': <doxygen_xml.DEFINE object at 0x000001FB5D8660F0>,...}
|
|
|
|
'enums': {'lv_key_t': <doxygen_xml.ENUM object at 0x000001FB5D1EEB40>,
|
|
'lv_group_refocus_policy_t': <doxygen_xml.ENUM object at 0x000001FB5D1E3DA0>,
|
|
'lv_obj_flag_t': <doxygen_xml.ENUM object at 0x000001FB5D29F830>,
|
|
'lv_obj_class_editable_t': <doxygen_xml.ENUM object at 0x000001FB5D29E300>,...}
|
|
|
|
'variables': {'lv_global': <doxygen_xml.VARIABLE object at 0x000001FB5D1E3FE0>,
|
|
'lv_obj_class': <doxygen_xml.VARIABLE object at 0x000001FB5D1EE1E0>,
|
|
'lv_font_montserrat_8': <doxygen_xml.VARIABLE object at 0x000001FB5DAB41A0>,
|
|
'lv_font_montserrat_10': <doxygen_xml.VARIABLE object at 0x000001FB5D99D040>,...}
|
|
|
|
'namespaces': {},
|
|
|
|
'structures': {'_lv_anim_t::_lv_anim_path_para_t': <doxygen_xml.UNION object at 0x000001FB5C4240E0>,
|
|
'_lv_anim_t': <doxygen_xml.STRUCT object at 0x000001FB5C45F680>,
|
|
'_lv_animimg_t': <doxygen_xml.STRUCT object at 0x000001FB5C4FE390>,
|
|
'_lv_arc_t': <doxygen_xml.STRUCT object at 0x000001FB59D350A0>,...}
|
|
|
|
'unions': {},
|
|
|
|
'typedefs': {'lv_global_t': <doxygen_xml.TYPEDEF object at 0x000001FB5D1EFFE0>,
|
|
'lv_group_focus_cb_t': <doxygen_xml.TYPEDEF object at 0x000001FB5D1F1CA0>,
|
|
'lv_group_edge_cb_t': <doxygen_xml.TYPEDEF object at 0x000001FB5D1EE7E0>,...}
|
|
|
|
'functions': {'lv_group_create': <doxygen_xml.FUNCTION object at 0x000001FB5D1E0470>,
|
|
'lv_group_delete': <doxygen_xml.FUNCTION object at 0x000001FB5D1F3800>,
|
|
'lv_group_set_default': <doxygen_xml.FUNCTION object at 0x000001FB5D1ECAA0>,...}
|
|
|
|
Additional dictionaries:
|
|
'files': {'lv_global.h': <doxygen_xml.FILE object at 0x000001FB5D864E00>,
|
|
'lv_group.h': <doxygen_xml.FILE object at 0x000001FB5D1EFD40>,
|
|
'lv_group_private.h': <doxygen_xml.FILE object at 0x000001FB5D0D7DD0>,...}
|
|
|
|
"""
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from xml.etree import ElementTree
|
|
import doxygen_config
|
|
from announce import *
|
|
|
|
EMIT_WARNINGS = True
|
|
DOXYGEN_OUTPUT = True
|
|
|
|
MISSING_FUNC = 'MissingFunctionDoc'
|
|
MISSING_FUNC_ARG = 'MissingFunctionArgDoc'
|
|
MISSING_FUNC_RETURN = 'MissingFunctionReturnDoc'
|
|
MISSING_FUNC_ARG_MISMATCH = 'FunctionArgMissing'
|
|
MISSING_STRUCT = 'MissingStructureDoc'
|
|
MISSING_STRUCT_FIELD = 'MissingStructureFieldDoc'
|
|
MISSING_UNION = 'MissingUnionDoc'
|
|
MISSING_UNION_FIELD = 'MissingUnionFieldDoc'
|
|
MISSING_ENUM = 'MissingEnumDoc'
|
|
MISSING_ENUM_ITEM = 'MissingEnumItemDoc'
|
|
MISSING_TYPEDEF = 'MissingTypedefDoc'
|
|
MISSING_VARIABLE = 'MissingVariableDoc'
|
|
MISSING_MACRO = 'MissingMacroDoc'
|
|
|
|
# Dictionaries built from Doxygen XML output via `xml.etree.ElementTree`
|
|
defines = {} # dictionary of doxygen_xml.DEFINE objects
|
|
enums = {} # dictionary of doxygen_xml.ENUM objects
|
|
variables = {} # dictionary of doxygen_xml.VARIABLE objects
|
|
namespaces = {} # dictionary of doxygen_xml.NAMESPACE objects
|
|
structures = {} # dictionary of doxygen_xml.STRUCT objects
|
|
typedefs = {} # dictionary of doxygen_xml.TYPEDEF objects
|
|
functions = {} # dictionary of doxygen_xml.FUNCTION objects
|
|
groups = {} # dictionary of doxygen_xml.GROUP objects
|
|
files = {} # dictionary of doxygen_xml.FILE objects
|
|
classes = {} # dictionary of doxygen_xml.CLASS objects
|
|
unions = {} # appears to be unused at this time (unions => structures dict).
|
|
|
|
# Module-Global Variables
|
|
xml_path = ''
|
|
|
|
|
|
def run_ext_cmd(cmd_str: str, start_dir: str = None, quiet: bool = False, exit_on_error: bool = True):
|
|
"""
|
|
Run external command `cmd_str` (possibly silently) in directory
|
|
`start_dir` (if provided), and optionally abort execution on error.
|
|
|
|
:param cmd_str: String to pass to OS.
|
|
:param start_dir: Directory to start in, or `None` to run in "cwd".
|
|
:param quiet: Should STDOUT and STDERR be suppressed?
|
|
:param exit_on_error: Should this function abort execution on
|
|
a non-zero exit status?
|
|
:return: n/a
|
|
"""
|
|
saved_dir = None
|
|
|
|
if start_dir is not None:
|
|
saved_dir = os.getcwd()
|
|
os.chdir(start_dir)
|
|
|
|
if quiet:
|
|
# This method of running Doxygen is used because we do not
|
|
# want anything going to STDOUT. Running it via `os.system()`
|
|
# would send its output to STDOUT.
|
|
p = subprocess.Popen(
|
|
cmd_str,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
shell=True
|
|
)
|
|
|
|
out, err = p.communicate()
|
|
|
|
if p.returncode:
|
|
if out:
|
|
# Note the `.decode("utf-8")` is required because
|
|
# `sys.stdout.write()` requires a string, and `out` is
|
|
# a byte array; generates an exception if passed alone.
|
|
sys.stdout.write(out.decode("utf-8"))
|
|
sys.stdout.flush()
|
|
if err:
|
|
sys.stderr.write(err.decode("utf-8"))
|
|
sys.stdout.flush()
|
|
|
|
if exit_on_error:
|
|
sys.exit(p.returncode)
|
|
else:
|
|
announce_start(__file__, f'Running [{cmd_str}] in [{os.getcwd()}]...')
|
|
return_code = os.system(cmd_str)
|
|
announce_finish()
|
|
|
|
if return_code != 0 and exit_on_error:
|
|
print(f'Exiting due to error [{return_code}] running [{cmd_str}].')
|
|
sys.exit(return_code)
|
|
|
|
if saved_dir is not None:
|
|
os.chdir(saved_dir)
|
|
|
|
|
|
def warn(warning_type, *args):
|
|
if EMIT_WARNINGS:
|
|
args = ' '.join(str(arg) for arg in args)
|
|
|
|
if warning_type is None:
|
|
output = f'\033[31;1m {args}\033[0m\n'
|
|
else:
|
|
output = f'\033[31;1m{warning_type}: {args}\033[0m\n'
|
|
|
|
sys.stdout.write(output)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def build_docstring(element):
|
|
docstring = None
|
|
if element.tag == 'parameterlist':
|
|
return None
|
|
|
|
if element.text:
|
|
docstring = element.text.strip()
|
|
|
|
for item in element:
|
|
ds = build_docstring(item)
|
|
if ds:
|
|
if docstring:
|
|
docstring += ' ' + ds
|
|
else:
|
|
docstring = ds.strip()
|
|
|
|
if element.tag == 'para':
|
|
if docstring:
|
|
docstring = '\n\n' + docstring
|
|
|
|
if element.tag == 'ref':
|
|
docstring = f':ref:`{docstring}`'
|
|
|
|
if element.tail:
|
|
if docstring:
|
|
docstring += ' ' + element.tail.strip()
|
|
else:
|
|
docstring = element.tail.strip()
|
|
|
|
return docstring
|
|
|
|
|
|
def read_as_xml(d):
|
|
try:
|
|
return ElementTree.fromstring(d)
|
|
except: # NOQA
|
|
return None
|
|
|
|
|
|
def load_xml_etree(fle):
|
|
fle = os.path.join(xml_path, fle + '.xml')
|
|
|
|
with open(fle, 'rb') as f:
|
|
d = f.read().decode('utf-8')
|
|
|
|
# This code is to correct a bug in Doxygen. That bug incorrectly parses
|
|
# a typedef, and it causes an error to occur building the docs. The Error
|
|
# doesn't stop the documentation from being generated, I just don't want
|
|
# to see the ugly red output.
|
|
#
|
|
# if 'typedef void() lv_lru_free_t(void *v)' in d:
|
|
# d = d.replace(
|
|
# '<type>void()</type>\n '
|
|
# '<definition>typedef void() lv_lru_free_t(void *v)</definition>',
|
|
# '<type>void</type>\n '
|
|
# '<definition>typedef void(lv_lru_free_t)(void *v)</definition>'
|
|
# )
|
|
# with open(fle, 'wb') as f:
|
|
# f.write(d.encode('utf-8'))
|
|
|
|
return ElementTree.fromstring(d)
|
|
|
|
|
|
def get_type(node):
|
|
def gt(n):
|
|
for c in n:
|
|
if c.tag == 'ref':
|
|
t = c.text.strip()
|
|
break
|
|
else:
|
|
t = node.text.strip()
|
|
|
|
return t.replace('*', '').replace('(', '').replace(')', '').strip()
|
|
|
|
for child in node:
|
|
if child.tag == 'type':
|
|
return gt(child)
|
|
|
|
|
|
def build_define(element):
|
|
define = None
|
|
|
|
if element.text:
|
|
define = element.text.strip()
|
|
|
|
for item in element:
|
|
ds = build_define(item)
|
|
if ds:
|
|
if define:
|
|
define += ' ' + ds
|
|
else:
|
|
define = ds.strip()
|
|
|
|
if element.tail:
|
|
if define:
|
|
define += ' ' + element.tail.strip()
|
|
else:
|
|
define = element.tail.strip()
|
|
|
|
return define
|
|
|
|
|
|
class STRUCT_FIELD(object):
|
|
|
|
def __init__(self, name, _type, description, file_name, line_no):
|
|
self.name = name
|
|
self.type = _type
|
|
self.description = description
|
|
self.file_name = file_name
|
|
self.line_no = line_no
|
|
|
|
|
|
class STRUCT(object):
|
|
"""<compound kind="struct"> elements in Doxygen `index.xml`"""
|
|
_missing = MISSING_STRUCT
|
|
_missing_field = MISSING_STRUCT_FIELD
|
|
|
|
template = '''\
|
|
.. doxygenstruct:: {name}
|
|
:project: lvgl
|
|
:members:
|
|
:protected-members:
|
|
:private-members:
|
|
:undoc-members:
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global structures
|
|
global unions
|
|
|
|
if type(self) is UNION:
|
|
# UNION inherits from STRUCT.
|
|
if name in unions:
|
|
self.__dict__.update(unions[name].__dict__)
|
|
else:
|
|
unions[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.types = set()
|
|
self._deps = None
|
|
self.header_file = ''
|
|
self.description = None
|
|
self.fields = []
|
|
self.file_name = None
|
|
self.line_no = None
|
|
else:
|
|
# STRUCT type
|
|
if name in structures:
|
|
self.__dict__.update(structures[name].__dict__)
|
|
else:
|
|
structures[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.types = set()
|
|
self._deps = None
|
|
self.header_file = ''
|
|
self.description = None
|
|
self.fields = []
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
# Prior to 9-Mar-2025, the code below was never executing since this
|
|
# __init__() was never called with a `parent` value other than `None`.
|
|
# Reason: `kind="struct"` only occurs in `index.xml` as a top-level
|
|
# entry, and not as a child element of `kind="file"` as do <sectiondef>
|
|
# elements with kind = define, var, enum and func.
|
|
# Original code:
|
|
# if parent and refid:
|
|
if refid:
|
|
root = load_xml_etree(refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != self.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag == 'includes':
|
|
self.header_file = os.path.splitext(child.text)[0]
|
|
continue
|
|
|
|
elif child.tag == 'location':
|
|
self.file_name = child.attrib['file']
|
|
self.line_no = child.attrib['line']
|
|
|
|
elif child.tag == 'detaileddescription':
|
|
self.description = build_docstring(child)
|
|
|
|
elif child.tag == 'sectiondef':
|
|
for memberdef in child:
|
|
t = get_type(memberdef)
|
|
description = None
|
|
name = ''
|
|
file_name = None
|
|
line_no = None
|
|
|
|
# For each struct member...
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
file_name = element.attrib['file']
|
|
line_no = element.attrib['line']
|
|
|
|
elif element.tag == 'name':
|
|
name = element.text
|
|
|
|
elif element.tag == 'detaileddescription':
|
|
description = build_docstring(element)
|
|
|
|
field = STRUCT_FIELD(name, t, description, file_name, line_no)
|
|
self.fields.append(field)
|
|
|
|
if t is None:
|
|
continue
|
|
|
|
self.types.add(t)
|
|
|
|
if EMIT_WARNINGS:
|
|
if not self.description:
|
|
warn(self._missing, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
for field in self.fields:
|
|
if not field.description:
|
|
warn(self._missing_field, self.name)
|
|
warn(None, 'FIELD:', field.name)
|
|
warn(None, 'FILE:', field.file_name)
|
|
warn(None, 'LINE:', field.line_no)
|
|
warn(None)
|
|
|
|
def get_field(self, name):
|
|
for field in self.fields:
|
|
if field.name == name:
|
|
return field
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
for type_ in self.types:
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class UNION(STRUCT):
|
|
"""<compound kind="union"> elements in Doxygen `index.xml`"""
|
|
_missing = MISSING_UNION
|
|
_missing_field = MISSING_UNION_FIELD
|
|
|
|
template = '''\
|
|
.. doxygenunion:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
|
|
class VARIABLE(object):
|
|
"""<compound kind="struct" ...>
|
|
<member ... kind="variable"> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygenvariable:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global variables
|
|
|
|
if name in variables:
|
|
self.__dict__.update(variables[name].__dict__)
|
|
else:
|
|
variables[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.type = ''
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml_etree(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if (
|
|
child.tag == 'sectiondef' and
|
|
child.attrib['kind'] == 'var'
|
|
):
|
|
for memberdef in child:
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
self.type = get_type(memberdef)
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
elif element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
if not self.description:
|
|
warn(MISSING_VARIABLE, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class NAMESPACE(object):
|
|
"""<compound kind="namespace"> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygennamespace:: {name}
|
|
:project: lvgl
|
|
:members:
|
|
:protected-members:
|
|
:private-members:
|
|
:undoc-members:
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global namespaces
|
|
|
|
if name in namespaces:
|
|
self.__dict__.update(namespaces[name].__dict__)
|
|
else:
|
|
namespaces[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.line_no = None
|
|
self.file_name = None
|
|
self.enums = []
|
|
self.funcs = []
|
|
self.vars = []
|
|
self.typedefs = []
|
|
self.structs = []
|
|
self.unions = []
|
|
self.classes = []
|
|
|
|
# root = load_xml(refid)
|
|
#
|
|
# for compounddef in root:
|
|
# if compounddef.attrib['id'] != refid:
|
|
# continue
|
|
#
|
|
# for sectiondef in compounddef:
|
|
# if sectiondef.tag != 'sectiondef':
|
|
# continue
|
|
#
|
|
# enum
|
|
# typedef
|
|
# func
|
|
# struct
|
|
# union
|
|
#
|
|
#
|
|
# cls = globals()[sectiondef.attrib['kind'].upper()]
|
|
# if cls == ENUM:
|
|
# if sectiondef[0].text:
|
|
# sectiondef.attrib['name'] = sectiondef[0].text.strip()
|
|
# enums_.append(cls(self, **sectiondef.attrib))
|
|
# else:
|
|
# sectiondef.attrib['name'] = None
|
|
# enums_.append(cls(self, **sectiondef.attrib))
|
|
#
|
|
# elif cls == ENUMVALUE:
|
|
# if enums_[-1].is_member(sectiondef):
|
|
# enums_[-1].add_member(sectiondef)
|
|
#
|
|
# else:
|
|
# sectiondef.attrib['name'] = sectiondef[0].text.strip()
|
|
# cls(self, **sectiondef.attrib)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class GROUP(object):
|
|
"""<compound kind="group"> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygengroup:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global groups
|
|
|
|
if name in groups:
|
|
self.__dict__.update(functions[name].__dict__)
|
|
else:
|
|
functions[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class FUNC_ARG(object):
|
|
"""<compound kind="?"><member ...> elements in Doxygen `index.xml`"""
|
|
|
|
def __init__(self, name, _type):
|
|
self.name = name
|
|
self.type = _type
|
|
self.description = None
|
|
|
|
|
|
class FUNCTION(object):
|
|
"""<compound kind="file" ...>
|
|
<member ... kind="function"> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygenfunction:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global functions
|
|
|
|
if name in functions:
|
|
self.__dict__.update(functions[name].__dict__)
|
|
else:
|
|
functions[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.types = set()
|
|
self.restype = None
|
|
self.args = []
|
|
self._deps = None
|
|
self.description = None
|
|
self.res_description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
self.void_return = False
|
|
|
|
if parent is not None:
|
|
root = load_xml_etree(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'func':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
self.restype = get_type(memberdef)
|
|
|
|
for child in memberdef:
|
|
if child.tag == 'type':
|
|
if child.text and child.text.strip() == 'void':
|
|
self.void_return = True
|
|
|
|
if child.tag == 'param':
|
|
t = get_type(child)
|
|
if t is not None:
|
|
self.types.add(t)
|
|
|
|
for element in child:
|
|
if element.tag == 'declname':
|
|
arg = FUNC_ARG(element.text, t)
|
|
self.args.append(arg)
|
|
|
|
for child in memberdef:
|
|
if child.tag == 'location':
|
|
self.file_name = child.attrib['file']
|
|
self.line_no = child.attrib['line']
|
|
|
|
elif child.tag == 'detaileddescription':
|
|
self.description = build_docstring(child)
|
|
for element in child:
|
|
if element.tag != 'para':
|
|
continue
|
|
|
|
for desc_element in element:
|
|
if desc_element.tag == 'simplesect' and desc_element.attrib['kind'] == 'return':
|
|
self.res_description = build_docstring(desc_element)
|
|
|
|
if desc_element.tag != 'parameterlist':
|
|
continue
|
|
|
|
for parameter_item in desc_element:
|
|
parameternamelist = parameter_item[0]
|
|
if parameternamelist.tag != 'parameternamelist':
|
|
continue
|
|
|
|
parameter_name = parameternamelist[0].text
|
|
|
|
try:
|
|
parameterdescription = parameter_item[1]
|
|
if parameterdescription.tag == 'parameterdescription':
|
|
parameter_description = build_docstring(parameterdescription)
|
|
else:
|
|
parameter_description = None
|
|
except IndexError:
|
|
parameter_description = None
|
|
|
|
if parameter_name is not None:
|
|
for arg in self.args:
|
|
if arg.name != parameter_name:
|
|
continue
|
|
|
|
arg.description = parameter_description
|
|
break
|
|
else:
|
|
warn(MISSING_FUNC_ARG_MISMATCH, self.name)
|
|
warn(None, 'ARG:', parameter_name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if not self.description:
|
|
warn(MISSING_FUNC, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
else:
|
|
for arg in self.args:
|
|
if not arg.description:
|
|
warn(MISSING_FUNC_ARG, self.name)
|
|
warn(None, 'ARG:', arg.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if not self.res_description and not self.void_return:
|
|
warn(MISSING_FUNC_RETURN, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if self.restype in self.types:
|
|
self.restype = None
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
if self.restype is not None:
|
|
self.types.add(self.restype)
|
|
|
|
for type_ in self.types:
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class FILE(object):
|
|
"""<compound kind="file"> elements in Doxygen `index.xml`"""
|
|
|
|
def __init__(self, _, refid, name, node, **__):
|
|
global files
|
|
|
|
if name.endswith('lv_types.h'):
|
|
return
|
|
|
|
if name in files:
|
|
self.__dict__.update(files[name].__dict__)
|
|
return
|
|
|
|
files[name] = self
|
|
|
|
self.refid = refid
|
|
self.name = name
|
|
self.header_file = os.path.splitext(name)[0]
|
|
self.types_contained = set()
|
|
|
|
enums_ = []
|
|
|
|
for member in node:
|
|
if member.tag != 'member':
|
|
continue
|
|
|
|
cls_name = member.attrib['kind'].upper()
|
|
self.types_contained.add(cls_name)
|
|
cls = globals()[cls_name]
|
|
if cls == ENUM:
|
|
if member[0].text:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
enums_.append(cls(self, **member.attrib))
|
|
else:
|
|
member.attrib['name'] = None
|
|
enums_.append(cls(self, **member.attrib))
|
|
|
|
elif cls == ENUMVALUE:
|
|
if enums_[-1].is_member(member):
|
|
enums_[-1].add_member(member)
|
|
|
|
else:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
cls(self, **member.attrib)
|
|
|
|
|
|
class ENUMVALUE(object):
|
|
"""<compound kind="file"...>
|
|
<member kind="enum"...>
|
|
<member kind="enumvalue"> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygenenumvalue:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
if parent.file_name is not None:
|
|
self.file_name = parent.file_name
|
|
elif hasattr(parent, 'parent') and parent.parent is not None:
|
|
if parent.parent.name is not None:
|
|
self.file_name = parent.parent.name
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class ENUM(object):
|
|
"""<compound kind="file"...>
|
|
<member kind="enum"...> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygenenum:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global enums
|
|
|
|
if name in enums:
|
|
# This happens when `name` is `None`, for example.
|
|
# This is true for unnamed enumerations.
|
|
self.__dict__.update(enums[name].__dict__)
|
|
else:
|
|
|
|
enums[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.members = []
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml_etree(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'enum':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
# raise RuntimeError(f'not able to locate enum {name} ({refid})')
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
if element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
elif element.tag == 'enumvalue':
|
|
item_name = None
|
|
item_description = None
|
|
item_file_name = None
|
|
item_line_no = None
|
|
|
|
for s_element in element:
|
|
if s_element.tag == 'name':
|
|
item_name = s_element.text
|
|
elif s_element.tag == 'detaileddescription':
|
|
item_description = build_docstring(s_element)
|
|
|
|
elif s_element.tag == 'location':
|
|
item_file_name = child.attrib['file']
|
|
item_line_no = child.attrib['line']
|
|
|
|
if item_name is not None:
|
|
for ev in self.members:
|
|
if ev.name != item_name:
|
|
continue
|
|
break
|
|
else:
|
|
ev = ENUMVALUE(
|
|
self,
|
|
element.attrib['id'],
|
|
item_name
|
|
)
|
|
|
|
self.members.append(ev)
|
|
|
|
ev.description = item_description
|
|
|
|
if not self.description:
|
|
warn(MISSING_ENUM, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
for member in self.members:
|
|
if not member.description:
|
|
warn(MISSING_ENUM_ITEM, self.name)
|
|
warn(None, 'MEMBER:', member.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def is_member(self, member):
|
|
return (
|
|
member.attrib['kind'] == 'enumvalue' and
|
|
member.attrib['refid'].startswith(self.refid)
|
|
)
|
|
|
|
def add_member(self, member):
|
|
name = member[0].text.strip()
|
|
for ev in self.members:
|
|
if ev.name == name:
|
|
return
|
|
|
|
self.members.append(
|
|
ENUMVALUE(
|
|
self,
|
|
member.attrib['refid'],
|
|
name
|
|
)
|
|
)
|
|
|
|
def __str__(self):
|
|
template = [self.template.format(name=self.name)]
|
|
template.extend(list(str(member) for member in self.members))
|
|
|
|
return '\n'.join(template)
|
|
|
|
|
|
class DEFINE(object):
|
|
"""<compound kind="file"...>
|
|
<member kind="define"...> elements in Doxygen `index.xml`"""
|
|
template = '''\
|
|
.. doxygendefine:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global defines
|
|
|
|
if name in defines:
|
|
self.__dict__.update(defines[name].__dict__)
|
|
else:
|
|
defines[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
self.params = None
|
|
self.initializer = None
|
|
|
|
if parent is not None:
|
|
root = load_xml_etree(parent.refid)
|
|
memberdef = []
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'define':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
elif element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
elif element.tag == 'param':
|
|
for child in element:
|
|
if child.tag == 'defname':
|
|
if self.params is None:
|
|
self.params = []
|
|
|
|
if child.text:
|
|
self.params.append(child.text)
|
|
|
|
elif element.tag == 'initializer':
|
|
initializer = build_define(element)
|
|
if initializer is None:
|
|
self.initializer = ''
|
|
else:
|
|
self.initializer = initializer
|
|
|
|
if not self.description:
|
|
warn(MISSING_MACRO, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class TYPEDEF(object):
|
|
template = '''\
|
|
.. doxygentypedef:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
global typedefs
|
|
|
|
if name in typedefs:
|
|
self.__dict__.update(typedefs[name].__dict__)
|
|
else:
|
|
typedefs[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.type = None
|
|
self._deps = None
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml_etree(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
if child.attrib['kind'] != 'typedef':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
if element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
if not self.description:
|
|
warn(MISSING_TYPEDEF, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
self.type = get_type(memberdef)
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
if self.type is not None:
|
|
type_ = self.type
|
|
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class CLASS(object):
|
|
"""<compound kind="file" ...>
|
|
<member ... kind="class"> elements in Doxygen `index.xml`"""
|
|
|
|
def __init__(self, _, refid, name, node, **__):
|
|
global classes
|
|
|
|
if name in classes:
|
|
self.__dict__.update(classes[name].__dict__)
|
|
return
|
|
|
|
classes[name] = self
|
|
|
|
self.refid = refid
|
|
self.name = name
|
|
|
|
enums_ = []
|
|
|
|
for member in node:
|
|
if member.tag != 'member':
|
|
continue
|
|
|
|
cls = globals()[member.attrib['kind'].upper()]
|
|
if cls == ENUM:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
enums_.append(cls(self, **member.attrib))
|
|
elif cls == ENUMVALUE:
|
|
if enums_[-1].is_member(member):
|
|
enums_[-1].add_member(member)
|
|
|
|
else:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
cls(self, **member.attrib)
|
|
|
|
|
|
class DoxygenXml(object):
|
|
"""Opens, parses and loads a Doxygen-generated `index.xml` file
|
|
and makes it available as a set of dictionary attributes of this
|
|
module, documented at the top of this file and named below in
|
|
`global` statements.
|
|
"""
|
|
|
|
def __init__(self,
|
|
lvgl_src_dir: str,
|
|
intermediate_dir: str,
|
|
doxyfile_src_file: str,
|
|
silent_mode=False,
|
|
doxy_tagfile=''):
|
|
"""
|
|
- Prepare and run Doxygen, generating XML output.
|
|
- Load that XML output, and use it to populate
|
|
:param lvgl_src_dir:
|
|
:param intermediate_dir:
|
|
:param doxyfile_src_file:
|
|
:param silent_mode:
|
|
"""
|
|
# Dictionaries to Be Populated:
|
|
global defines
|
|
global enums
|
|
global variables
|
|
global namespaces
|
|
global structures
|
|
global typedefs
|
|
global functions
|
|
global groups
|
|
global files
|
|
global classes
|
|
global unions
|
|
|
|
global xml_path
|
|
|
|
announce_set_silent_mode(silent_mode)
|
|
base_dir = os.path.abspath(os.path.dirname(__file__))
|
|
doxyfile_filename = str(os.path.split(doxyfile_src_file)[1])
|
|
doxyfile_dst_file = str(os.path.join(intermediate_dir, doxyfile_filename))
|
|
lv_conf_file = os.path.join(intermediate_dir, 'lv_conf.h')
|
|
xml_path = os.path.join(intermediate_dir, 'xml')
|
|
|
|
# In case DoxygenXml() is ever instantiated twice in 1 session,
|
|
# clear these dictionaries before they are (re-)populated below.
|
|
defines.clear()
|
|
enums.clear()
|
|
variables.clear()
|
|
namespaces.clear()
|
|
structures.clear()
|
|
typedefs.clear()
|
|
functions.clear()
|
|
groups.clear()
|
|
files.clear()
|
|
classes.clear()
|
|
unions.clear()
|
|
|
|
# -----------------------------------------------------------------
|
|
# Prep and run Doxygen
|
|
# -----------------------------------------------------------------
|
|
# Generate Doxyfile into `intermediate_dir` replacing certain
|
|
# config options for this run.
|
|
# 1. Load from Doxyfile
|
|
cfg = doxygen_config.DoxygenConfig()
|
|
cfg.load(doxyfile_src_file)
|
|
# 2. Update cfg.
|
|
cfg.set('OUTPUT_DIRECTORY', '.')
|
|
cfg.set('XML_OUTPUT', "xml")
|
|
cfg.set('HTML_OUTPUT', 'doxygen_html')
|
|
cfg.set('INPUT', lvgl_src_dir)
|
|
cfg.set('QUIET', 'YES')
|
|
cfg.set('GENERATE_HTML', 'NO')
|
|
cfg.set('GENERATE_DOCSET', 'NO')
|
|
cfg.set('GENERATE_HTMLHELP', 'NO')
|
|
cfg.set('GENERATE_CHI', 'NO')
|
|
cfg.set('GENERATE_QHP', 'NO')
|
|
cfg.set('GENERATE_ECLIPSEHELP', 'NO')
|
|
cfg.set('GENERATE_LATEX', 'NO')
|
|
cfg.set('GENERATE_RTF', 'NO')
|
|
cfg.set('GENERATE_MAN', 'NO')
|
|
cfg.set('GENERATE_XML', 'YES')
|
|
cfg.set('GENERATE_DOCBOOK', 'NO')
|
|
cfg.set('GENERATE_PERLMOD', 'NO')
|
|
|
|
# The predefined definitions are:
|
|
# - DOXYGEN (defines it as 1 and allows conditional directives [e.g. #if]
|
|
# to do special things when DOXYGEN is processing it, as opposed to
|
|
# a C compiler.
|
|
# - LV_CONF_PATH predefines the path where `lv_conf_internal.h` will
|
|
# include the `lv_conf.h` file.
|
|
# - All the others here are used when macros prefix a line of code like this:
|
|
#
|
|
# LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_obj_class;
|
|
#
|
|
# which occurs in `lv_obj.h`. When these are added to the PREDEFINED list as
|
|
# "MACRO_NAME=" with no value, Doxygen expands the macro to an empty string
|
|
# allowing Doxygen to correctly parse the line. The additional macros that
|
|
# are treated that way are found in the middle of `lv_conf_template.h` in a
|
|
# section called "COMPILER SETTINGS" with no definitions, made for prefixing
|
|
# various code. (This works around Doxygen's failure to correctly deal with
|
|
# macros that are defined with no values, as these are in lv_conf.h.)
|
|
# This list is current as of 28-Apr-2025.
|
|
predefined_symbols = [
|
|
'DOXYGEN',
|
|
f'LV_CONF_PATH="{lv_conf_file}"',
|
|
'LV_ATTRIBUTE_TICK_INC=',
|
|
'LV_ATTRIBUTE_TIMER_HANDLER=',
|
|
'LV_ATTRIBUTE_FLUSH_READY=',
|
|
'LV_ATTRIBUTE_MEM_ALIGN=',
|
|
'LV_ATTRIBUTE_LARGE_CONST=',
|
|
'LV_ATTRIBUTE_LARGE_RAM_ARRAY=',
|
|
'LV_ATTRIBUTE_FAST_MEM=',
|
|
'LV_ATTRIBUTE_EXTERN_DATA=',
|
|
'LV_FORMAT_ATTRIBUTE(fmt,va)=',
|
|
'FASTGLTF_EXPORT=',
|
|
]
|
|
|
|
cfg.set('PREDEFINED', predefined_symbols)
|
|
|
|
# Exclude OSAL `.h` files except for `lv_os_none.h`. Reason: they define
|
|
# lv_mutex_t, lv_thread_t and lv_thread_sync_t in multiple places. Doxygen
|
|
# must only see one.
|
|
osal_dir = 'osal'
|
|
|
|
osal_exclude_list = [
|
|
'lv_cmsis_rtos2.h',
|
|
'lv_freertos.h',
|
|
'lv_mqx.h',
|
|
'lv_pthread.h',
|
|
'lv_rtthread.h',
|
|
'lv_sdl2.h',
|
|
'lv_windows.h',
|
|
]
|
|
|
|
exclude_paths = []
|
|
|
|
for osal_h in osal_exclude_list:
|
|
full_path = os.path.join(lvgl_src_dir, osal_dir, osal_h)
|
|
exclude_paths.append(full_path)
|
|
|
|
# Exclude as a workaround for Breathe parsing problems.
|
|
full_path = os.path.join(lvgl_src_dir, 'core', 'lv_obj_property.h')
|
|
exclude_paths.append(full_path)
|
|
|
|
# Exclude GLTF templates that Breathe appears to not know how to parse.
|
|
full_path = os.path.join(lvgl_src_dir, 'libs', 'gltf', 'fastgltf', 'lv_fastgltf.hpp')
|
|
exclude_paths.append(full_path)
|
|
|
|
cfg.set('EXCLUDE', exclude_paths)
|
|
|
|
# Include TAGFILES if requested.
|
|
if doxy_tagfile:
|
|
cfg.set('GENERATE_TAGFILE', doxy_tagfile)
|
|
|
|
# 3. Store it for use by Doxygen in intermediate directory.
|
|
cfg.save(doxyfile_dst_file)
|
|
|
|
# Run Doxygen in intermediate directory.
|
|
run_ext_cmd('doxygen Doxyfile', intermediate_dir, quiet=silent_mode)
|
|
|
|
# -----------------------------------------------------------------
|
|
# Load root of Doxygen output (index.xml) as an `xml.etree.ElementTree`.
|
|
# -----------------------------------------------------------------
|
|
index_xml_etree = load_xml_etree('index')
|
|
|
|
# Populate these dictionaries.
|
|
# Keys : C-code-element names (str) found by Doxygen.
|
|
# Values: The <compound> XML-node created by `xml.etree::ElementTree` in `load_xml()` above.
|
|
#
|
|
# defines, enums, variables,
|
|
# namespaces, structures, typedefs,
|
|
# functions, unions, groups,
|
|
# files, classes.
|
|
announce_start(__file__, "Building source-code symbol dictionaries...")
|
|
module_namespace = globals()
|
|
|
|
for compound in index_xml_etree:
|
|
# Here we will encounter these "kind" in the index.xml
|
|
# <compound> elements: dir, file, page, struct, union.
|
|
compound.attrib['name'] = compound[0].text.strip()
|
|
|
|
# Filter out dir, page, example.
|
|
if compound.attrib['kind'] not in ('example', 'page', 'dir'):
|
|
class_name = compound.attrib['kind'].upper()
|
|
class_obj = module_namespace[class_name]
|
|
_ = class_obj(None, node=compound, **compound.attrib)
|
|
|
|
announce_finish()
|
|
|
|
# Additional data: the above instantiates the class, but doesn't
|
|
# store the resulting object anywhere, since each class' __init__()
|
|
# function adds the new object to the appropriate dictionary,
|
|
# and uses the arguments it gets to "build out" additional
|
|
# structure and/or populate additional dictionaries based on
|
|
# content. Each class __init__() function specifies args it
|
|
# in its parameter list. They match needed key values in the
|
|
# `**compound.attrib` dictionary. The remaining (unused)
|
|
# keys in that dictionary are accepted in the `**_` parameter
|
|
# at the end of the parameter list.
|
|
#
|
|
# FILE class populates `files` dict plus:
|
|
# - `defines` dictionary
|
|
# - `enums` dictionary with children:
|
|
# - ENUMVALUEs
|
|
# - `variables` dictionary
|
|
# - `typedefs` dictionary
|
|
# - `functions` dictionary with children:
|
|
# - FUNC_ARGs
|
|
# STRUCT class populates the `structures` dictionary with children:
|
|
# - STRUCT_FIELDs
|
|
#
|
|
# and possibly `namespaces`, `groups` and `classes` if
|
|
# they are present in `index.xml`.
|
|
|
|
|
|
def get_macros(self):
|
|
global defines
|
|
return list(defines.values())
|
|
|
|
def get_enum_item(self, e_name):
|
|
global enums
|
|
for enum, obj in enums.items():
|
|
for enum_item in obj.members:
|
|
if enum_item.name == e_name:
|
|
return enum_item
|
|
|
|
def get_enum(self, e_name):
|
|
global enums
|
|
return enums.get(e_name, None)
|
|
|
|
def get_function(self, f_name):
|
|
global functions
|
|
return functions.get(f_name, None)
|
|
|
|
def get_variable(self, v_name):
|
|
global variables
|
|
return variables.get(v_name, None)
|
|
|
|
def get_union(self, u_name):
|
|
global unions
|
|
return unions.get(u_name, None)
|
|
|
|
def get_structure(self, s_name):
|
|
global structures
|
|
return structures.get(s_name, None)
|
|
|
|
def get_typedef(self, t_name):
|
|
global typedefs
|
|
return typedefs.get(t_name, None)
|
|
|
|
def get_macro(self, m_name):
|
|
global defines
|
|
return defines.get(m_name, None)
|