Files
lvgl/docs/api_doc_builder.py
Victor Wheeler 69052cd2ea
Some checks are pending
Arduino Lint / lint (push) Waiting to run
MicroPython CI / Build esp32 port (push) Waiting to run
MicroPython CI / Build rp2 port (push) Waiting to run
MicroPython CI / Build stm32 port (push) Waiting to run
MicroPython CI / Build unix port (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - Ubuntu (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - cl - Windows (push) Waiting to run
C/C++ CI / Build OPTIONS_VG_LITE - gcc - Windows (push) Waiting to run
C/C++ CI / Build ESP IDF ESP32S3 (push) Waiting to run
C/C++ CI / Run tests with 32bit build (push) Waiting to run
C/C++ CI / Run tests with 64bit build (push) Waiting to run
BOM Check / bom-check (push) Waiting to run
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Waiting to run
Verify the widget property name / verify-property-name (push) Waiting to run
Verify code formatting / verify-formatting (push) Waiting to run
Build docs / build-and-deploy (push) Waiting to run
Test API JSON generator / Test API JSON (push) Waiting to run
Check Makefile / Build using Makefile (push) Waiting to run
Check Makefile for UEFI / Build using Makefile for UEFI (push) Waiting to run
Port repo release update / run-release-branch-updater (push) Waiting to run
Verify Kconfig / verify-kconfig (push) Waiting to run
docs(doc_builder.py): overhaul, remove bugs, clean up, modularize (#7913)
2025-03-20 13:54:45 +01:00

771 lines
29 KiB
Python

"""api_doc_builder.py
Create and provide links to API pages in LVGL doc build.
Uses doxygen_xml.py module to:
- Prep and run Doxygen
- make Doxygen XML output available, and
- make Doxygen-documented symbols from the C code available.
"""
import os
import re
import doxygen_xml
from announce import *
old_html_files = {}
EMIT_WARNINGS = True
rst_section_line_char = '='
# Multi-line match ``API + newline + \*\*\* + whitespace``.
# NB: the ``\s*`` at the end forces the regex to match the whitespace
# at the end including all \r\n's. This will match UP TO:
# - the next non-blank character (could be many blank lines), or
# - to the end of the file, whichever comes first.
_re_api_section_sep = re.compile(r'(?mi)^API *\r?\n^\*\*\*\s*')
# Regex to identify '.. API equals: lv_obj_t, lv_array_t' directives.
_re_api_equals = re.compile(r'(?mi)^\s*\.\.\s+API\s+equals:\s*([\w,\s]+)\r\n\s*')
# Regex to identify '.. API startswith: lv_obj, lv_array' directives.
_re_api_startswith = re.compile(r'(?mi)^\s*\.\.\s+API\s+startswith:\s*([\w,\s]+)\r\n\s*')
# Regex to match comma and whitespace list-item separators on multiple lines.
_re_multi_line_comma_sep = re.compile(r'(?m)[,\s]+')
# Regex to identify editor-added hyperlinks: :ref:`lv_obj_h`
_re_editor_added_hyperlink = re.compile(r'^\s*:ref:`(\w+)`')
# Separator to mark place where this script added hyperlinks.
_auto_gen_sep = '.. Autogenerated'
# List of symbol dictionaries
_defines = {}
_enums = {}
_variables = {}
_namespaces = {}
_structs = {}
_unions = {}
_typedefs = {}
_functions = {}
_symbol_dict_list = [
_defines,
_enums,
_variables,
_namespaces,
_structs,
_unions,
_typedefs,
_functions
]
def old_clean_name(nme):
"""Strip beginning "_lv" and ending "_t"."""
# Handle error:
# AttributeError: 'NoneType' object has no attribute 'startswith'
if nme is None:
return nme
if nme.startswith('_lv_'):
nme = nme[4:]
elif nme.startswith('lv_'):
nme = nme[3:]
if nme.endswith('_t'):
nme = nme[:-2]
return nme
# Definitions:
# - "section" => The name "abc_def" has 2 sections.
# - N = number of sections in `item_name`.
# After removing leading '_lv_', 'lv_' and trailing '_t' from `obj_name`,
# do the remaining first N "sections" of `obj_name` match `item_name`
# (case sensitive)?
def old_is_name_match(item_name, obj_name):
# Handle error:
# AttributeError: 'NoneType' object has no attribute 'split'
if obj_name is None:
return False
sect_count = item_name.count('_') + 1
obj_name = obj_name.split('_')
# Reject (False) if `obj_name` doesn't have as many sections as `item_name`.
if len(obj_name) < sect_count:
return False
obj_name = '_'.join(obj_name[:sect_count])
return item_name == obj_name
def old_get_includes(name1, name2, obj, includes):
name2 = old_clean_name(name2)
if not old_is_name_match(name1, name2):
return
if obj.parent is not None and hasattr(obj.parent, 'header_file'):
header_file = obj.parent.header_file
elif hasattr(obj, 'header_file'):
header_file = obj.header_file
else:
return
if not header_file:
return
if header_file not in old_html_files:
return
includes.add((header_file, old_html_files[header_file]))
def _conditionally_add_hyperlink(obj, genned_link_set: set, exclude_set: set):
"""
Add hyperlink names to `link_set` if:
- not in `exclude_set`, and
- not already in `link_set`.
:param obj: "thing" from dictionary with matching symbol.
These are objects instantiated from classes
in `doxygen_xml` module such as
STRUCT, FUNCTION, DEFINE, ENUMVALUE, etc.
:param genned_link_set: Set in which to accumulate link names
:param exclude_set: Set with link names not to add to `link_set`
:return:
"""
if obj.file_name is not None:
link_name = os.path.basename(obj.file_name).replace('.', '_')
if link_name not in genned_link_set:
if link_name not in exclude_set:
genned_link_set.add(link_name)
def _add_startswith_matches(strings: [str], genned_link_set, editor_link_set):
"""
Add set of hyperlinks to `genned_link_set` that are not already in
`editor_link_set`, for C symbols that start with strings in `strings`.
:param strings: List of strings to match against
:param genned_link_set: Generated link set
:param editor_link_set: Hyperlinks added by editor
:return: n/a
"""
for partial_symbol in strings:
for symbol_dict in _symbol_dict_list:
for key in symbol_dict:
if key is None:
# Dictionary `enums` has a key `None` which contains
# all enumvalues from all unnamed enums, and each
# enumvalue has a `file_name` field.
enum_values_list = symbol_dict[None].members
for enum_val in enum_values_list:
if enum_val.name.startswith(partial_symbol):
_conditionally_add_hyperlink(enum_val, genned_link_set, editor_link_set)
else:
if key.startswith(partial_symbol):
obj = symbol_dict[key]
_conditionally_add_hyperlink(obj, genned_link_set, editor_link_set)
def _add_exact_matches(symbols: [str], genned_link_set, editor_link_set):
"""
Add set of hyperlinks to `genned_link_set` that are not already in
`editor_link_set`, for exact C symbol matches.
:param symbols: List of C symbols to match against
:param genned_link_set: Generated link set
:param editor_link_set: Hyperlinks added by editor
:return: n/a
"""
for symbol in symbols:
for symbol_dict in _symbol_dict_list:
if symbol in symbol_dict:
obj = symbol_dict[symbol]
_conditionally_add_hyperlink(obj, genned_link_set, editor_link_set)
def _hyperlink_sort_value(init_value: str):
if init_value.endswith('_h'):
result = init_value[:-2]
else:
result = init_value
return result
def _process_end_of_eligible_doc(b: str, rst_file: str) -> (str, str, int):
"""
Edit end section after API section heading.
3. Initialize:
- links_added_count = 0
- editor_link_set = set()
- genned_link_set = set()
- C = '' # string for generated hyperlinks
4. Remove `_auto_gen_sep` and everything after it:
- new_B = B.split(_auto_gen_sep, 1)[0]
5. With `new_B, add any editor-added hyperlinks to set:
`editor_link_set`.
6. If `_re_api_equals` match present:
- build list of symbols
- compute list of hyperlinks from symbols
- add hyperlinks to `genned_link_set` if not in `editor_link_set`.
7. If `_re_api_startswith` match present:
- build list of symbols
- compute list of hyperlinks from symbols
- add hyperlinks to `genned_link_set` if not in `editor_link_set`.
8. Lacking either of the above custom directives, use the lower-case
stem of the filename and prefix it with "lv_" and try an
"API startswith" search with it.
9. If len(genned_link_set) > 0:
- `C` = _auto_gen_sep + '\n\n' + `genned_link_set`
(with a blank line between each).
10. Return tuple: (new_B, C, links_added_count).
:param b: End of document after API section heading + whitespace.
:return: Tuple: (new_B, C, links_added_count)
"""
# 3. Initialize:
editor_link_set = set()
genned_link_set = set()
c = ''
api_directives_found_count = 0
# 4. Remove `_auto_gen_sep` and everything after it:
new_b = b.split(_auto_gen_sep, 1)[0]
# 5. With `new_B, add any editor-added hyperlinks to set:
# `editor_link_set`.
for line in new_b.splitlines():
match = _re_editor_added_hyperlink.match(line)
if match is not None:
editor_link_set.add(match[1])
# 6. If `_re_api_equals` present:
# - build list of symbols
# - compute list of hyperlinks from symbols
# - add hyperlinks to `genned_link_set` if not in `editor_link_set`.
match = _re_api_equals.search(new_b)
if match is not None:
api_directives_found_count += 1
comma_sep_list = match[1].strip()
symbols = _re_multi_line_comma_sep.split(comma_sep_list)
_add_exact_matches(symbols, genned_link_set, editor_link_set)
# 7. If `_re_api_startswith` present:
# - build list of symbols
# - compute list of hyperlinks from symbols
# - add hyperlinks to `genned_link_set` if not in `editor_link_set`.
match = _re_api_startswith.search(new_b)
if match is not None:
api_directives_found_count += 1
comma_sep_list = match[1].strip()
symbols = _re_multi_line_comma_sep.split(comma_sep_list)
_add_startswith_matches(symbols, genned_link_set, editor_link_set)
# 8. Lacking either of the above custom directives, use the lower-case
# stem of the filename and prefix it with "lv_" and try an
# "API startswith" with it.
if api_directives_found_count == 0:
base = os.path.basename(rst_file)
stem = os.path.splitext(base)[0]
_add_startswith_matches(['lv_' + stem], genned_link_set, editor_link_set)
# 9. If len(genned_link_set) > 0:
# - `C` = _auto_gen_sep + '\n\n' + `genned_link_set`
# (with a blank line between each).
links_added_count = len(genned_link_set)
if links_added_count > 0:
c = _auto_gen_sep + '\n\n'
for link_name in sorted(genned_link_set, key=_hyperlink_sort_value):
c += ':ref:`' + link_name + '`\n\n'
# 10. Return tuple: (new_B, C, links_added_count).
return new_b, c, links_added_count
def _process_one_file(rst_file: str):
"""
Add applicable API hyperlinks to one file.
Eligible
An `.rst` file is eligible if it contains an API section at
its end. This can happen also in `index.rst` files when
they head a single subject for which an API section is
appropriate there and not in the sub-docs. So `index.rst`
files are included, whereas they were not included previously.
Algorithm:
----------
A. Doc editors may have already added a set of hyperlinks of their
own. This routine takes note of and does not duplicate what is
already there.
B. Doc editors may also have added specifications for this routine
that look like this:
.. API equals: lv_obj_t, lv_arc_t, lv_barcode_t,
lv_win_t, lv_list_t,
lv_button_t
.. API startswith: lv_obj, lv_arc, lv_barcode,
lv_win, lv_list,
lv_button
as directives for this routine to build a set of applicable
hyperlinks.
C. Lacking any of the above custom directives, use the lower-case
stem of the filename and prefix it with "lv_" and try an
"API startswith" search with it.
Any hyperlinks added by this routine are prefixed with the
reStructuredText comment defined by the `_auto_gen_sep`
variable, normally:
.. Autogenerated
If `rst_file` is eligible, edit after API section heading such that:
- any editor-added hyperlinks are retained at the top of the list;
- `_auto_gen_sep` (looked for in case a source file ends up having it;
anything after it is replaced);
- applicable hyperlinks added such that they do not repeat those
added by editors of `.rst` file.
Steps to Implement:
-------------------
0. links_added_count = 0
1. Determine if eligible.
- If not, skip to step 12.
- If so, continue.
2. Split doc into 2 parts:
A. beginning through API section heading and subsequent
whitespace including subsequent blank lines;
B. anything after that which may include editor-added hyperlinks.
3-10. new_B, C, links_added_count = _process_end_of_eligible_doc()
11. Write `A` + `new_B` + `C` back to `rst_file`.
12. Return links_added_count.
:param rst_file: Full path to `.rst` file in question.
It may or may not be eligible.
:return: Number of links added.
"""
links_added_count = 0
with open(rst_file, 'rb') as f:
try:
rst_contents = f.read().decode('utf-8')
except UnicodeDecodeError:
announce(__file__, f'Error: UnicodeDecodeError in [{rst_file}].')
raise
eligible_match = _re_api_section_sep.search(rst_contents)
if eligible_match is not None:
# Eligible (API section found).
i = eligible_match.end()
# Split just after the API section heading + whitespace.
a = rst_contents[:i]
b = rst_contents[i:]
new_b, c, links_added_count = _process_end_of_eligible_doc(b, rst_file)
if links_added_count > 0:
rst_contents = a + new_b + c
with open(rst_file, 'wb') as f:
f.write(rst_contents.encode('utf-8'))
return links_added_count
def _build_one_local_dictionary(local_dict, remote_dict):
"""
Remove '_' prefix in symbols beginning with '_lv' to make
symbols like `lv_obj_t` actually connect with the struct
in `lv_obj_private.h`, and not the typedef in `lv_types.h`.
:param local_dict: Local (adjusted) symbol dictionary
:param remote_dict: Dictionary from `doxygen_xml` module
:return: n/a
"""
for symbol in remote_dict:
# Note: symbol `None` is actually a valid symbol in the
# `enums` dictionary, containing all enumvalue symbols
# for enums without names.
if symbol is None or not symbol.startswith('_lv'):
loc_symbol = symbol
else:
# Remove '_' prefix.
loc_symbol = symbol[1:]
local_dict[loc_symbol] = remote_dict[symbol]
def _build_local_symbol_dictionaries():
"""
Build "work-around" dictionaries so that a symbol like `lv_obj_t`
actually connects with the struct in `lv_obj_private.h`, and not
the typedef in `lv_types.h`.
:return: n/a
"""
_build_one_local_dictionary(_defines, doxygen_xml.defines)
_build_one_local_dictionary(_enums, doxygen_xml.enums)
_build_one_local_dictionary(_variables, doxygen_xml.variables)
_build_one_local_dictionary(_namespaces, doxygen_xml.namespaces)
_build_one_local_dictionary(_structs, doxygen_xml.structures)
_build_one_local_dictionary(_unions, doxygen_xml.unions)
_build_one_local_dictionary(_typedefs, doxygen_xml.typedefs)
_build_one_local_dictionary(_functions, doxygen_xml.functions)
def _add_hyperlinks_to_eligible_files(intermediate_dir: str,
new_algorithm: bool,
*doc_rel_paths: [str]):
"""
Add applicable hyperlinks to eligible docs found joining
`intermediate_dir` with each relative path in `doc_rel_paths`.
See API-link algorithm documented under `_process_one_file()`.
:param intermediate_dir: Top directory where hyperlinks are to be added.
:param doc_rel_paths: Tuple of relative paths from `intermediate_dir` to
walk to find docs eligible for API hyperlinks.
:return:
"""
if new_algorithm:
# Populate local symbol dictionary set with
# symbols WITHOUT any '_' prefixes.
_build_local_symbol_dictionaries()
# Build `.rst` file list.
file_list = []
for rel_path in doc_rel_paths:
top_dir = os.path.join(intermediate_dir, rel_path)
for dir_bep, sub_dirs, files in os.walk(top_dir, topdown=False):
for file in files:
if file.lower().endswith('.rst'):
file_list.append(os.path.join(dir_bep, file))
total_eligible_doc_count = 0
total_links_added_count = 0
# For each `.rst` file, add appropriate API hyperlinks.
for rst_file in file_list:
links_added_count = _process_one_file(rst_file)
if links_added_count > 0:
total_links_added_count += links_added_count
total_eligible_doc_count += 1
# announce(__file__, f'Eligible doc: [{rst_file}].')
announce(__file__, f'Docs eligible for API hyperlinks: {total_eligible_doc_count:>4}')
announce(__file__, f'API hyperlinks added : {total_links_added_count:>4}')
else:
for folder in doc_rel_paths:
# Fetch a list of '.rst' files excluding 'index.rst'.
rst_files = list(
(os.path.splitext(item)[0], os.path.join(folder, item))
for item in os.listdir(folder)
if item.endswith('.rst') and 'index.rst' not in item
)
# For each .RST file in that directory...
for stem, path in rst_files:
# Start with an empty set.
html_includes = set()
# Build `html_includes` set as a list of tuples containing
# (name, html_file). Example: "draw.rst" has `stem` == 'draw',
# and generates a list of tuples from .H files where matching
# C-code-element names were found. Example:
# {('lv_draw_line', 'draw\\lv_draw_line.html'),
# ('lv_draw_sdl', 'draw\\sdl\\lv_draw_sdl.html'),
# ('lv_draw_sw_blend_to_i1', 'draw\\sw\\blend\\lv_draw_sw_blend_to_i1.html'),
# etc.}
for symbol_dict in (
doxygen_xml.defines,
doxygen_xml.enums,
doxygen_xml.variables,
doxygen_xml.namespaces,
doxygen_xml.structures,
doxygen_xml.unions,
doxygen_xml.typedefs,
doxygen_xml.functions
):
for key, obj in symbol_dict.items():
old_get_includes(stem, key, obj, html_includes)
if html_includes:
# Convert `html_includes` set to a list of strings containing the
# Sphinx hyperlink syntax "link references". Example from above:
# [':ref:`lv_draw_line_h`\n',
# ':ref:`lv_draw_sdl_h`\n',
# ':ref:`lv_draw_sw_blend_to_i1_h`\n',
# etc.]
html_includes = list(
':ref:`{0}_h`\n'.format(inc)
for inc, _ in html_includes
)
# Convert that list to a single string of Sphinx hyperlink
# references with blank lines between them.
# :ref:`lv_draw_line_h`
#
# :ref:`lv_draw_sdl_h`
#
# :ref:`lv_draw_sw_blend_to_i1_h`
#
# etc.
output = ('\n'.join(html_includes)) + '\n'
# Append that string to the source .RST file being processed.
with open(path, 'rb') as f:
try:
data = f.read().decode('utf-8')
except UnicodeDecodeError:
print(path)
raise
data = data.split(_auto_gen_sep, 1)[0]
data += f'{_auto_gen_sep}\n\n'
data += output
with open(path, 'wb') as f:
f.write(data.encode('utf-8'))
def _create_rst_files_for_dir(src_root_dir_len: int,
src_dir_bep: str,
elig_h_files: [str],
elig_sub_dirs: [str],
out_root_dir: str):
"""
- Create `index.rst` file and add its top section.
- For each file in `elig_h_files`:
- Create one `.rst` file.
- Add reference to it in `index.rst`.
- For each subdir in `elig_sub_dirs`:
- add reference "sub_dir_name/index" in `index.rst`.
:param src_root_dir_len: Length of source-root path string, used with `out_root_dir` to build paths
:param src_dir_bep: Directory currently *being processed*
:param elig_h_files: Eligible `.h` files directly contained in `src_dir_bep`
:param elig_sub_dirs: List of sub-dirs that contained eligible `.h` files
:param out_root_dir: Root of output directory, used with to build paths.
:return: n/a
"""
indent = ' '
sub_path = src_dir_bep[src_root_dir_len:]
out_dir = str(os.path.join(out_root_dir, sub_path))
# Ensure dir exists. Multiple dirs MAY have to be created
# since `.rst` files are created in bottom-up sequence.
if not os.path.isdir(out_dir):
os.makedirs(out_dir)
# For top-level directory only... (the last index.rst created,
# since they are created in bottom-up sequence)
if len(sub_path) == 0 and out_dir.endswith(os.sep):
# Trim trailing slash from `out_dir`.
out_dir = out_dir[:-1]
# index.rst
with open(os.path.join(out_dir, 'index.rst'), 'w') as f:
subdir_stem = os.path.split(out_dir)[-1]
section_line = (rst_section_line_char * len(subdir_stem)) + '\n'
f.write(section_line)
f.write(subdir_stem + '\n')
f.write(section_line)
f.write('\n')
f.write('.. toctree::\n :maxdepth: 2\n\n')
# One entry per `.rst` file
for h_file in elig_h_files:
filename = os.path.basename(h_file)
stem = os.path.splitext(filename)[0]
f.write(indent + stem + '\n')
# One entry per eligible subdirectory.
for sub_dir in elig_sub_dirs:
stem = os.path.split(sub_dir)[-1]
f.write(indent + stem + '/index\n')
# One .rst file per h_file
for h_file in elig_h_files:
filename = os.path.basename(h_file)
stem = os.path.splitext(filename)[0]
rst_file = os.path.join(out_dir, stem + '.rst')
html_file = os.path.join(sub_path, stem + '.html')
old_html_files[stem] = html_file
with open(rst_file, 'w') as f:
# Sphinx link target.
f.write(f'.. _{stem}_h:\n\n')
# Doc title.
section_line = (rst_section_line_char * len(filename)) + '\n'
f.write(section_line)
f.write(filename + '\n')
f.write(section_line)
f.write('\n')
# Content for `breathe`.
f.write(f'.. doxygenfile:: {filename}\n')
f.write(' :project: lvgl\n\n')
def _recursively_create_api_rst_files(depth: int,
src_root_len: int,
src_dir_bep: str,
out_root_dir: str) -> int:
"""
Create `.rst` files for the eligible `.h` found in `src_dir_bep` and
recursively for subdirectories below it. ("bep" = being processed.)
Eligible
An `.h` file is eligible if Doxygen generated documentation for it.
The `EXCLUDE_PATTERNS` Doxygen configuration value can cause
Doxygen to skip certain files and directories, in which case,
the `.h` files skipped ARE NOT eligible.
Whether a subdirectory is eligible to be included in an `index.rst`
file depends upon whether any eligible `.h` files were recursively
found within it. And that isn't known until this function finishes
(recursively) processing a directory and returns the number of
eligible `.h` files found. Thus, the steps taken within are:
- Discover all eligible `.h` files directly contained in `src_dir_bep`.
- Recursively do the same for each subdirectory, adding the returned
count of eligible `.h` files to the sum (`elig_h_file_count`).
- If `elig_h_file_count > 0`:
- call _create_rst_files_for_dir() to generate appropriate
`.rst` files for this directory.
- Return `elig_h_file_count`.
Once we have accumulated this information, then we can generate
all the `.rst` files for the current directory without any further
directory-tree walking.
:param depth: Only used for testing/debugging
:param src_root_len: Length of source-root path
:param src_dir_bep: Source directory *being processed*
:param out_root_dir: Output root directory (used to build output paths)
:return: Number of `.h` files encountered (so caller knows
whether that directory recursively held any
eligible `.h` files, to know whether to include
"subdir/index" in caller's local `index.rst` file).
"""
elig_h_files = []
sub_dirs = []
elig_sub_dirs = []
elig_h_file_count = 0
# For each "thing" found in `src_dir_bep`, build lists:
# `elig_sub_dirs` and `elig_h_files`.
# By design change, we are including files with 'private'
# in their names. Reason: advanced users who need to use
# the structs defined within will need the documentation
# in those API pages!
for dir_item in os.listdir(src_dir_bep):
path_bep = os.path.join(src_dir_bep, dir_item)
if os.path.isdir(path_bep):
sub_dirs.append(path_bep) # Add to sub-dir list.
else:
if dir_item.lower().endswith('.h'):
eligible = (dir_item in doxygen_xml.files)
if eligible:
elig_h_files.append(path_bep) # Add to .H file list.
elig_h_file_count += 1
# For each subdir...
for sub_dir in sub_dirs:
subdir_eligible_h_file_count = \
_recursively_create_api_rst_files(depth + 1,
src_root_len,
sub_dir,
out_root_dir)
if subdir_eligible_h_file_count > 0:
elig_sub_dirs.append(sub_dir)
elig_h_file_count += subdir_eligible_h_file_count
if elig_h_file_count > 0:
# Create index.rst plus .RST files for any direct .H files in dir.
_create_rst_files_for_dir(src_root_len,
src_dir_bep,
elig_h_files,
elig_sub_dirs,
out_root_dir)
return elig_h_file_count
def create_api_rst_files(src_root_dir: str, out_root_dir: str):
"""
Create `.rst` files for API pages based on the `.h` files found
in a tree-walk of `a_src_root` and the current contents of the
`doxygen_xml.files` dictionary (used to filter out `.h` files that
Doxygen generated no documentation for). Output the `.rst` files
into `out_root_dir` mirroring the `a_src_root` directory structure.
:param src_root_dir: root source directory to walk
:param out_root_dir: output directory
:return: n/a
"""
src_root_len = len(src_root_dir) + 1
_recursively_create_api_rst_files(0, src_root_len, src_root_dir, out_root_dir)
def build_api_docs(lvgl_src_dir, intermediate_dir, doxyfile_src_file, *doc_rel_paths):
"""
- Prep and run Doxygen, outputting XML.
- Load that XML in a form that can quickly tie C symbols to the
source files they came from.
- Generate API page `.rst` files for source files Doxygen generated
documentation for.
- Add hyperlinks to these API pages for `.rst` files in `*doc_rel_paths`
that are eligible.
:param lvgl_src_dir: Path to LVGL src directory
:param intermediate_dir: Path to intermediate dir being built
:param doxyfile_src_file: Full path to src doxygen configuration file
:param doc_rel_paths: List of relative paths from `intermediate_dir` to
walk to find docs eligible for API hyperlinks.
"""
# ---------------------------------------------------------------------
# - Generate Doxyfile replacing tokens,
# - run Doxygen generating XML, and
# - load the generated XML from Doxygen output.
# ---------------------------------------------------------------------
doxygen_xml.EMIT_WARNINGS = EMIT_WARNINGS
xml_parser = doxygen_xml.DoxygenXml(lvgl_src_dir,
intermediate_dir,
doxyfile_src_file,
silent_mode=False
)
# ---------------------------------------------------------------------
# Generate .RST files for API pages.
# ---------------------------------------------------------------------
announce(__file__, "Generating API documentation .RST files...")
api_out_root_dir = os.path.join(intermediate_dir, 'API')
create_api_rst_files(lvgl_src_dir, api_out_root_dir)
# ---------------------------------------------------------------------
# For each directory entry in `doc_rel_paths` array...
# - add API hyperlinks to .RST files in the directories in passed array.
# ---------------------------------------------------------------------
announce(__file__, "Adding API-page hyperlinks to source docs...")
_add_hyperlinks_to_eligible_files(intermediate_dir,
True,
*doc_rel_paths)