docs(doc_builder.py): overhaul, remove bugs, clean up, modularize (#7913)
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

This commit is contained in:
Victor Wheeler
2025-03-20 06:54:45 -06:00
committed by GitHub
parent 40cba753fb
commit 69052cd2ea
12 changed files with 2168 additions and 847 deletions

1
docs/.gitignore vendored
View File

@@ -1,3 +1,4 @@
build/
doxygen/
intermediate/
src/_static/built_lv_examples

View File

@@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "LVGL"
PROJECT_NAME = LVGL
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = .
OUTPUT_DIRECTORY = doxygen
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
@@ -118,16 +118,16 @@ REPEAT_BRIEF = YES
# the entity):The $name class, The $name widget, The $name file, is, provides,
# specifies, contains, represents, a, an and the.
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
@@ -724,7 +724,7 @@ CITE_BIB_FILES =
# messages are off.
# The default value is: NO.
QUIET = YES
QUIET = NO
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
@@ -790,7 +790,7 @@ WARN_LOGFILE = doxygen_warnings.txt
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = <<SRC>>
INPUT = ../src
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -815,11 +815,11 @@ INPUT_ENCODING = UTF-8
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
FILE_PATTERNS = *.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
FILE_PATTERNS = *.h \
*.hh \
*.hxx \
*.hpp \
*.h++
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@@ -850,14 +850,14 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = */libs/barcode/code* \
*/libs/freetype/ft* \
*/libs/gif/gif* \
*/libs/lodepng/lode* \
*/libs/qrcode/qr* \
*/libs/thorvg/* \
*/libs/tiny_ttf/stb* \
*/libs/tjpgd/tjp* \
EXCLUDE_PATTERNS = */libs/barcode/code* \
*/libs/freetype/ft* \
*/libs/gif/gif* \
*/libs/lodepng/lode* \
*/libs/qrcode/qr* \
*/libs/thorvg/* \
*/libs/tiny_ttf/stb* \
*/libs/tjpgd/tjp* \
*/others/vg_lite_tvg/vg*
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
@@ -1099,7 +1099,7 @@ GENERATE_HTML = YES
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = doxygen_html
HTML_OUTPUT = html
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
@@ -1915,7 +1915,7 @@ MAN_LINKS = NO
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = YES
GENERATE_XML = NO
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -1932,7 +1932,7 @@ XML_OUTPUT = xml
# The default value is: YES.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_PROGRAMLISTING = YES
XML_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
@@ -2068,7 +2068,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED = DOXYGEN LV_CONF_PATH="<<LV_CONF_PATH>>"
PREDEFINED = DOXYGEN LV_CONF_PATH="../../lv_conf.h"
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The

View File

@@ -42,7 +42,7 @@ or if you are on a Unix like OS:
python3 build.py html
Intermediate files are prepared in `./docs/intermediate/` and the final documentation will appear in `./docs/build/html/`.
Intermediate files are prepared in `./docs/intermediate/` and the final documentation will appear in `./docs/build/html/`. (Both of these directories can be overridden using environment variables. See documentation in `build.py` for details.)
If the list of document source files has changed (names or paths):
@@ -66,13 +66,36 @@ The below are some rules to follow when updating any of the `.rst` files located
### What to Name Your `.rst` File
The documentation-generation logic uses the stem of the file name (i.e. "event" from file name "event.rst") and compares this with code-element names found by Doxygen. If a match is found, then it appends hyperlinks to the API pages that contain those code elements (names of macros, enum/struct/union types, variables, namespaces, typedefs and functions).
The directory structure under the `./docs/src/` directory, and the filenames of the `.rst` files govern the eventual URLs that are generated in the HTML output. These directories are organized so as to reflect the nature of the content. Example: the `.rst` files under `./docs/src/intro` contain introductory material—detailed reference material would not go there, but instead in an appropriate subdirectory of `./docs/src/details/`. It is expected that the content and location of any new documents added would be in alignment with this directory structure, and placed and named according to their content. Additionally, to be linked into the eventual generated documentation, the stem of the new filename would need to appear in at least one (normally *only one*) `.. toctree::` directive, normally in an `index.rst` file in the directory where it will appear in the table of contents (TOC).
If this is appropriate for the .RST file you are creating, ensure the stem of the file name matches the beginning part of the code-element name you want it to be associated with.
Other than that, there are no restrictions on filenames. Previous linking of filenames to generated API links has been removed and replaced by a better scheme. For sake of illustration, let's say you are creating (or enhancing) documentation related to the `lv_scale_t` data type (one of the LVGL Widgets): if you want the doc-build logic to generate appropriate links to LVGL API pages, place an API section at the end of your document (it must be at the end) like this:
If this is *not* appropriate for the .RST file you are creating, ensure the stem of the file name DOES NOT match any code-element names found in the LVGL header files under the ./src/ directory.
```rst
API
***
```
In alignment with the above, use a file name stem that is appropriate to the topic being covered.
and then, if you want the API-link-generation logic to generate hyperlinks to API pages based on an ***exact, case-sensitive string match*** with specific C symbols, follow it with a reStructuredText comment using this syntax:
```rst
.. API equals: lv_scale_t, lv_scale_create
```
What follows the colon is a comma- or space-separated list of exact C symbols documented somewhere in the `lvgl/src/` directory. If the list is long, it can be wrapped to subsequent lines, though continuation lines must be all indented at the same level. The list ends with the first blank line after this pseudo-directive.
If you instead want the API-link-generation logic to simply include links to code that ***starts with a specific string*** use this syntax instead. The format of the list is the same as for `.. API equals:`:
```rst
.. API startswith: lv_scale, lv_obj_set_style
```
You can also manually link to API pages, in which case the API-link-generation logic will see that you have already added links and will not repeat them.
```rst
:ref:`lv_scale_h`
```
Note that the period before the `h` is replaced with an underscore (`_`). The naming of this reference (`lv_scale_h`) will generate a hyperlink to the documentation extracted by Doxygen from the `lvgl/src/widgets/scale/lv_scale.h` file.
### Text Format
@@ -234,7 +257,7 @@ To create a bulleted list, do the following:
lines to align with item text like this.
- If you want to include a code block under a list item,
it must be intended to align with the list item like this:
.. code-block: python
<=== blank line here is important
# this is some code
@@ -286,4 +309,3 @@ For such examples, simply use reStructuredText literal markup like this:
``lv_obj_set_layout(&widget, LV_LAYOUT_FLEX);``
``lv_obj_set_layout(widget, ...)``

80
docs/announce.py Normal file
View File

@@ -0,0 +1,80 @@
"""announce.py
Manage logging announcements to `stdout`
It is the designer's intention that:
1. The variable `__file__` be passed as the first argument in
`announce()` and `announce_start()`.
(Unfortunately, there is no way this author knows of yet to
have this module know what Python module is importing it. So
this is a hold-over requirement until that need is fulfilled.)
2. `announce_start()` and `announce_finish()` should be used
in pairs like this:
announce_start(__file__, 'something is running...')
# do something that takes a while here
announce_finish()
3. If this is used in a module that sometimes has a need to
not have anything output to STDOUT, when that is known,
call `announce_set_silent_mode()`. To turn "silent mode"
off, call `announce_set_silent_mode(False)`.
"""
import os
import datetime
__all__ = ('announce', 'announce_start', 'announce_finish', 'announce_set_silent_mode')
_announce_start_time: datetime.datetime
_announce_silent_mode: bool = False
def _announce(file: str, args: tuple, start=False):
if _announce_silent_mode:
return
_args = []
for arg in args:
# Avoid the single quotes `repr()` puts around strings.
if type(arg) is str:
_args.append(arg)
else:
_args.append(repr(arg))
if start:
_end = ''
else:
_end = '\n'
print(f'{os.path.basename(file)}: ', ' '.join(_args), end=_end, flush=True)
def announce(file: str, *args, start=False, finish=False):
global _announce_start_time
_announce_start_time = None
_announce(file, args)
def announce_start(file: str, *args, start=False, finish=False):
global _announce_start_time
_announce_start_time = datetime.datetime.now()
_announce(file, args, start=True)
def announce_finish():
# Just output line ending to terminate output for `announce_start()`.
global _announce_start_time
if _announce_start_time is not None:
if not _announce_silent_mode:
print(' Elapsed: ', datetime.datetime.now() - _announce_start_time, flush=True)
_announce_start_time = None
else:
if not _announce_silent_mode:
print(flush=True)
def announce_set_silent_mode(mode=True):
global _announce_silent_mode
_announce_silent_mode = mode

770
docs/api_doc_builder.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -172,12 +172,10 @@ from datetime import datetime
# LVGL Custom
import example_list
import doc_builder
import api_doc_builder
import config_builder
_ = os.path.abspath(os.path.dirname(__file__))
docs_src_dir = os.path.join(_, 'src')
sys.path.insert(0, docs_src_dir)
from lvgl_version import lvgl_version # NoQA
from src.lvgl_version import lvgl_version
from announce import *
# Not Currently Used
# (Code is kept in case we want to re-implement it later.)
@@ -188,7 +186,8 @@ from lvgl_version import lvgl_version # NoQA
# -------------------------------------------------------------------------
# These are relative paths from the ./docs/ directory.
cfg_project_dir = '..'
cfg_src_dir = 'src'
cfg_lvgl_src_dir = 'src'
cfg_doc_src_dir = 'src'
cfg_examples_dir = 'examples'
cfg_default_intermediate_dir = 'intermediate'
cfg_default_output_dir = 'build'
@@ -224,28 +223,29 @@ def print_usage_note():
def remove_dir(tgt_dir):
"""Remove directory `tgt_dir`."""
if os.path.isdir(tgt_dir):
print(f'Removing {tgt_dir}...')
announce(__file__, f'Removing {tgt_dir}...')
shutil.rmtree(tgt_dir)
else:
print(f'{tgt_dir} already removed...')
announce(__file__, f'{tgt_dir} already removed...')
def cmd(s, start_dir=None, exit_on_error=True):
def cmd(cmd_str, start_dir=None, exit_on_error=True):
"""Run external command and abort build on error."""
if start_dir is None:
start_dir = os.getcwd()
saved_dir = None
saved_dir = os.getcwd()
os.chdir(start_dir)
print("")
print(s)
print("-------------------------------------")
result = os.system(s)
os.chdir(saved_dir)
if start_dir is not None:
saved_dir = os.getcwd()
os.chdir(start_dir)
if result != 0 and exit_on_error:
print("Exiting build due to previous error.")
sys.exit(result)
announce(__file__, f'Running [{cmd_str}] in [{os.getcwd()}]...')
return_code = os.system(cmd_str)
if saved_dir is not None:
os.chdir(saved_dir)
if return_code != 0 and exit_on_error:
announce(__file__, "Exiting build due to previous error.")
sys.exit(return_code)
def intermediate_dir_contents_exists(dir):
@@ -293,7 +293,7 @@ def run(args):
def print_setting(setting_name, val):
"""Print one setting; used for debugging."""
print(f'{setting_name:18} = [{val}]')
announce(__file__, f'{setting_name:18} = [{val}]')
def print_settings(and_exit):
"""Print all settings and optionally exit; used for debugging."""
@@ -361,7 +361,7 @@ def run(args):
# fully regenerated, even if not changed.
# Note: Sphinx runs in ./docs/, but uses `intermediate_dir` for input.
if fresh_sphinx_env:
print("Force-regenerating all files...")
announce(__file__, "Force-regenerating all files...")
env_opt = '-E'
else:
env_opt = ''
@@ -383,7 +383,7 @@ def run(args):
base_dir = os.path.abspath(os.path.dirname(__file__))
project_dir = os.path.abspath(os.path.join(base_dir, cfg_project_dir))
examples_dir = os.path.join(project_dir, cfg_examples_dir)
lvgl_src_dir = os.path.join(project_dir, 'src')
lvgl_src_dir = os.path.join(project_dir, cfg_lvgl_src_dir)
# Establish intermediate directory. The presence of environment variable
# `LVGL_DOC_BUILD_INTERMEDIATE_DIR` overrides default in `cfg_default_intermediate_dir`.
@@ -440,9 +440,9 @@ def run(args):
# Change to script directory for consistent run-time environment.
# ---------------------------------------------------------------------
os.chdir(base_dir)
print(f'Intermediate dir: [{intermediate_dir}]')
print(f'Output dir : [{output_dir}]')
print(f'Running from : [{base_dir}]')
announce(__file__, f'Intermediate dir: [{intermediate_dir}]')
announce(__file__, f'Output dir : [{output_dir}]')
announce(__file__, f'Running from : [{base_dir}]')
# ---------------------------------------------------------------------
# Clean? If so, clean (like `make clean`), but do not exit.
@@ -451,9 +451,9 @@ def run(args):
or clean_all or (os.path.isdir(intermediate_dir) and build_intermediate)
if some_cleaning_to_be_done:
print("****************")
print("Cleaning...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Cleaning...")
announce(__file__, "****************")
if clean_intermediate:
remove_dir(intermediate_dir)
@@ -483,7 +483,7 @@ def run(args):
# - generated search window
# - establishing canonical page for search engines
# - `link_roles.py` to generate translation links
# - `doc_builder.py` to generate links to API pages
# - `doxygen_xml.py` to generate links to API pages
#
# LVGL_GITCOMMIT is used by:
# - `conf.py` => html_context['github_version'] for
@@ -527,8 +527,7 @@ def run(args):
os.environ['LVGL_GITCOMMIT'] = branch
# ---------------------------------------------------------------------
# Copy files to 'intermediate_dir' where they will be edited (translation
# link(s) and API links) before being used to generate new docs.
# Prep `intermediate_dir` to become the `sphinx-build` source dir.
# ---------------------------------------------------------------------
# dirsync `exclude_list` = list of regex patterns to exclude.
intermediate_re = r'^' + cfg_default_intermediate_dir + r'.*'
@@ -537,9 +536,9 @@ def run(args):
if intermediate_dir_contents_exists(intermediate_dir):
# We are just doing an update of the intermediate_dir contents.
print("****************")
print("Updating intermediate directory...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Updating intermediate directory...")
announce(__file__, "****************")
exclude_list.append(r'examples.*')
options = {
@@ -551,23 +550,24 @@ def run(args):
}
# action == 'sync' means copy files even when they do not already exist in tgt dir.
# action == 'update' means DO NOT copy files when they do not already exist in tgt dir.
dirsync.sync(cfg_src_dir, intermediate_dir, 'sync', **options)
dirsync.sync(cfg_doc_src_dir, intermediate_dir, 'sync', **options)
dirsync.sync(examples_dir, os.path.join(intermediate_dir, cfg_examples_dir), 'sync', **options)
elif build_intermediate or build_html or build_latex:
# We are having to create the intermediate_dir contents by copying.
print("****************")
print("Building intermediate directory...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Building intermediate directory...")
announce(__file__, "****************")
t1 = datetime.now()
copy_method = 1
# Both of these methods work.
if copy_method == 0:
# --------- Method 0:
ignore_func = shutil.ignore_patterns('tmp*', 'output*')
print('Copying docs...')
shutil.copytree(cfg_src_dir, intermediate_dir, ignore=ignore_func, dirs_exist_ok=True)
print('Copying examples...')
announce(__file__, 'Copying docs...')
shutil.copytree(cfg_doc_src_dir, intermediate_dir, ignore=ignore_func, dirs_exist_ok=True)
announce(__file__, 'Copying examples...')
shutil.copytree(examples_dir, os.path.join(intermediate_dir, cfg_examples_dir), dirs_exist_ok=True)
else:
# --------- Method 1:
@@ -577,37 +577,26 @@ def run(args):
}
# action == 'sync' means copy files even when they do not already exist in tgt dir.
# action == 'update' means DO NOT copy files when they do not already exist in tgt dir.
print('Copying docs...')
dirsync.sync(cfg_src_dir, intermediate_dir, 'sync', **options)
print('Copying examples...')
announce(__file__, 'Copying docs...')
dirsync.sync(cfg_doc_src_dir, intermediate_dir, 'sync', **options)
announce(__file__, 'Copying examples...')
dirsync.sync(examples_dir, os.path.join(intermediate_dir, cfg_examples_dir), 'sync', **options)
# -----------------------------------------------------------------
# Build Example docs, Doxygen output, API docs, and API links.
# Build <intermediate_dir>/lv_conf.h from lv_conf_template.h.
# -----------------------------------------------------------------
t1 = datetime.now()
# Build <intermediate_dir>/lv_conf.h from lv_conf_template.h for this build only.
config_builder.run(lv_conf_file)
# -----------------------------------------------------------------
# Copy `lv_version.h` into intermediate directory.
# -----------------------------------------------------------------
shutil.copyfile(version_src_file, version_dst_file)
# Replace tokens in Doxyfile in 'intermediate_dir' with data from this run.
with open(doxyfile_src_file, 'rb') as f:
data = f.read().decode('utf-8')
data = data.replace('<<LV_CONF_PATH>>', lv_conf_file)
data = data.replace('<<SRC>>', f'"{lvgl_src_dir}"')
with open(doxyfile_dst_file, 'wb') as f:
f.write(data.encode('utf-8'))
# -----------------------------------------------------------------
# Generate examples pages. Include sub-pages pages that get included
# in individual documents where applicable.
# -----------------------------------------------------------------
print("Generating examples...")
announce(__file__, "Generating examples...")
example_list.exec(intermediate_dir)
# -----------------------------------------------------------------
@@ -617,68 +606,46 @@ def run(args):
# -----------------------------------------------------------------
# Original code:
# if True:
# print("Skipping adding translation links.")
# announce(__file__, "Skipping adding translation links.")
# else:
# print("Adding translation links...")
# announce(__file__, "Adding translation links...")
# add_translation.exec(intermediate_dir)
# ---------------------------------------------------------------------
# Generate API pages and links thereto.
# ---------------------------------------------------------------------
if skip_api:
print("Skipping API generation as requested.")
announce(__file__, "Skipping API generation as requested.")
else:
print("Running Doxygen...")
cmd('doxygen Doxyfile', intermediate_dir)
# -------------------------------------------------------------
# Generate API pages and links thereto.
# -------------------------------------------------------------
announce(__file__, "API page and link processing...")
api_doc_builder.EMIT_WARNINGS = False
print("API page and link processing...")
doc_builder.EMIT_WARNINGS = False
# Create .RST files for API pages, plus
# add API hyperlinks to .RST files in the directories in passed array.
doc_builder.run(
project_dir,
intermediate_dir,
os.path.join(intermediate_dir, 'intro'),
os.path.join(intermediate_dir, 'details'),
os.path.join(intermediate_dir, 'details', 'common-widget-features'),
os.path.join(intermediate_dir, 'details', 'common-widget-features', 'layouts'),
os.path.join(intermediate_dir, 'details', 'common-widget-features', 'styles'),
os.path.join(intermediate_dir, 'details', 'debugging'),
os.path.join(intermediate_dir, 'details', 'integration'),
os.path.join(intermediate_dir, 'details', 'integration', 'adding-lvgl-to-your-project'),
os.path.join(intermediate_dir, 'details', 'integration', 'bindings'),
os.path.join(intermediate_dir, 'details', 'integration', 'building'),
os.path.join(intermediate_dir, 'details', 'integration', 'chip'),
os.path.join(intermediate_dir, 'details', 'integration', 'driver'),
os.path.join(intermediate_dir, 'details', 'integration', 'driver', 'display'),
os.path.join(intermediate_dir, 'details', 'integration', 'driver', 'touchpad'),
os.path.join(intermediate_dir, 'details', 'integration', 'framework'),
os.path.join(intermediate_dir, 'details', 'integration', 'ide'),
os.path.join(intermediate_dir, 'details', 'integration', 'os'),
os.path.join(intermediate_dir, 'details', 'integration', 'os', 'yocto'),
os.path.join(intermediate_dir, 'details', 'integration', 'renderers'),
os.path.join(intermediate_dir, 'details', 'libs'),
os.path.join(intermediate_dir, 'details', 'main-modules'),
# Note: details/main-modules/display omitted intentionally,
# since API links for those .RST files have been added manually.
os.path.join(intermediate_dir, 'details', 'auxiliary-modules'),
os.path.join(intermediate_dir, 'details', 'widgets')
)
# api_doc_builder.run() => doxy_xml_parser.DoxygenXml() now:
# - preps and runs Doxygen generating XML,
# - loads generated XML.
# Then api_doc_builder.run():
# - creates .RST files for API pages, and
# - adds API hyperlinks to .RST files in the directories in passed array.
api_doc_builder.build_api_docs(lvgl_src_dir,
intermediate_dir,
doxyfile_src_file,
'details',
'intro'
)
t2 = datetime.now()
print('Example/API run time: ' + str(t2 - t1))
announce(__file__, 'Example/API run time: ' + str(t2 - t1))
# ---------------------------------------------------------------------
# Build PDF
# ---------------------------------------------------------------------
if not build_latex:
print("Skipping Latex build.")
announce(__file__, "Skipping Latex build.")
else:
t1 = datetime.now()
print("****************")
print("Building Latex output...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Building Latex output...")
announce(__file__, "****************")
# If PDF link is present in top index.rst, remove it so PDF
# does not have a link to itself.
@@ -701,9 +668,9 @@ def run(args):
cmd(cmd_line)
# Generate PDF.
print("****************")
print("Building PDF...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Building PDF...")
announce(__file__, "****************")
cmd_line = 'latexmk -pdf "LVGL.tex"'
cmd(cmd_line, latex_output_dir, False)
@@ -713,19 +680,19 @@ def run(args):
shutil.move(pdf_src_file, pdf_dst_file)
t2 = datetime.now()
print('PDF : ' + pdf_dst_file)
print('Latex gen time: ' + str(t2 - t1))
announce(__file__, 'PDF : ' + pdf_dst_file)
announce(__file__, 'Latex gen time: ' + str(t2 - t1))
# ---------------------------------------------------------------------
# Build HTML
# ---------------------------------------------------------------------
if not build_html:
print("Skipping HTML build.")
announce(__file__, "Skipping HTML build.")
else:
t1 = datetime.now()
print("****************")
print("Building HTML output...")
print("****************")
announce(__file__, "****************")
announce(__file__, "Building HTML output...")
announce(__file__, "****************")
# If PDF is present in build directory, copy it to
# intermediate directory for use by HTML build.
@@ -772,14 +739,14 @@ def run(args):
cmd_line = f'sphinx-build -M html "{src}" "{dst}" -D version="{ver}" {env_opt} -j {cpu}'
cmd(cmd_line)
t2 = datetime.now()
print('HTML gen time : ' + str(t2 - t1))
announce(__file__, 'HTML gen time : ' + str(t2 - t1))
# ---------------------------------------------------------------------
# Indicate results.
# ---------------------------------------------------------------------
t_end = datetime.now()
print('Total run time: ' + str(t_end - t0))
print('Done.')
announce(__file__, 'Total run time: ' + str(t_end - t0))
announce(__file__, 'Done.')
if __name__ == '__main__':

429
docs/doxygen_config.py Normal file
View File

@@ -0,0 +1,429 @@
"""doxygen_config.py
Python Interface to Doxygen Config Files (Doxyfiles)
Author : "Victor Wheeler"
Copyright: "Copyright (C) 2025 WGA Crystal Research, Inc."
License : "MIT"
Version : "1.0"
This work was inspired by the `doxygen-python-interface` project at
https://github.com/TraceSoftwareInternational/doxygen-python-interface.
On 27-Feb-2025 I was engaged in a production project wherein I wanted
to find a Python module that I could re-use to reliably work with
Doxygen configuration files (Doxyfiles). The best one I found was
`doxygen-python-interface`. Unfortunately, the ``configParser`` from
that project could not be used because it both had important bugs and
design flaws in it (conflicts with legal Doxygen config syntax), and
it appears to have been abandoned after 26-Apr-2018, preventing these
things from being remedied.
So a brand-new module has been created herewith based on sound O-O design
principles and a design that actually works in alignment with Doxygen
configuration syntax.
Usage:
import doxygen_config
...
# 1. Load configuration from Doxyfile.
cfg = doxygen_config.DoxygenConfig()
cfg.load(doxyfile_src_file)
# 2. Get a list of Doxygen option names.
opt_list = cfg.options()
ok_to_proceed = cfg.is_valid_option('PREDEFINED') \
and cfg.is_valid_option('INPUT')
# 3. Update it.
if ok_to_proceed:
temp = cfg.value('PREDEFINED')
temp = temp.replace('<<CONFIG_PATH>>', config_file)
cfg.set('PREDEFINED', temp)
temp = cfg.value('INPUT')
temp = temp.replace('<<SRC>>', f'"{pjt_src_dir}"')
cfg.set('INPUT', temp)
# 4. Save it.
# The original comments and order of config options are preserved.
# The ``bare`` argument discards comments from the output.
cfg.save(cfg_dict, doxyfile_dst_file, bare=True)
Design Differences from `doxygen-python-interface`:
- The DoxygenConfig class represents the actual Doxygen configuration,
in alignment with O-O theory --- it is not just a place to store a
set of functions that never needed to be a class.
- If the user does a default ``save()`` (not requesting a "bare"
version of the Doxygen configuration), the saved Doxyfile
should be a binary match to the original Doxyfile loaded.
Exceptions:
1. Any trailing whitespace in original Doxyfile after the ``=``
on empty options is not preserved.
2. Multi-line lists that had unaligned backslashes after them like this:
EXCLUDE_PATTERNS = */libs/barcode/code* \
*/libs/freetype/ft* \
*/libs/gif/gif* \
*/libs/lodepng/lode* \
*/libs/qrcode/qr* \
*/libs/thorvg/* \
*/libs/tiny_ttf/stb* \
*/libs/tjpgd/tjp* \
*/others/vg_lite_tvg/vg*
will be saved like this:
EXCLUDE_PATTERNS = */libs/barcode/code* \
*/libs/freetype/ft* \
*/libs/gif/gif* \
*/libs/lodepng/lode* \
*/libs/qrcode/qr* \
*/libs/thorvg/* \
*/libs/tiny_ttf/stb* \
*/libs/tjpgd/tjp* \
*/others/vg_lite_tvg/vg*
``doxygen-python-interface`` did not save the comments so an
"edit in place" of a Doxyfile could be catastrophic if the
comments were needed in the source Doxyfile as they often are
in production scenarios.
- The ``save()`` method has an optional ``bare`` argument (default False)
that can be used to save a "bare" version of the Doxyfile options,
discarding the comments from the currently-loaded Doxyfile.
- Input values are preserved exactly as they were found. The
`doxygen-python-interface`'s ``configParser`` class removed
quotation marks from incoming values and added quotation marks
to values containing spaces before storing them again. While
this "sounds nice", it was incompatible with Doxygen for every
type of item that could have a "list" as a value, such as the
PREDEFINED and ABBREVIATE_BRIEF options.
Examples:
PREDEFINED = USE_LIST USE_TABLE USE_CHART
PREDEFINED = DOXYGEN CONFIG_PATH="/path with spaces/to/config.h"
PREDEFINED = DOXYGEN \
CONFIG_PATH="/path with spaces/to/config.h"
These are all valid values for the PREDEFINED option and
MUST NOT have quotes around any of them! Can you imagine the havoc
that would result if a Python module meant to handle Doxygen Doxyfiles
altered Doxygen configuration items like this?
PREDEFINED = "USE_LIST USE_TABLE USE_CHART"
Thus, it is up to the user to know when values he is changing
have space(s) AND ALSO need quotes and take appropriate measures
by adding quotes when needed and not otherwise.
- The storage of the list of Doxygen options is encapsulated
in the instance of the DoxygenConfig class instead of being
returned as a dictionary from the ``load...()`` function.
Its values are readable and writeable via methods. The
end user is not able to add options that were not part
of the original input Doxyfile, nor remove options that were
part of the original input Doxyfile. This gives some level of
control on retaining valid Doxygen options.
It is an error to attempt to set a value with an option name
that does not exist in the configuration. A NameError exception
is raised if it is attempted. Attempting to read the value of
an option name that does not exist returns the value ``None``.
While Doxygen options change from time to time, it is up to the
end user to use ``doxygen -u Doxyfile`` to keep his input
Doxyfile(s) up to date.
Storage:
The actual configuration values are represented in an internal
dictionary not intended to be accessed directly by the typical end
user. The keys are the Doxygen option names and the values are:
- str : single values with possibly embedded spaces
- list: multi-line values with possibly embedded spaces
Quotation marks are neither removed nor added, so it is up to the
user to set values compatible with Doxygen configuration syntax.
This also makes it okay for multi-line values to have more than one
value per line: if it is okay by Doxygen, then it is okay by
the DoxygenConfig class.
If the user sets an option value passing a list, those values
will be represented as a multi-line value in the saved Doxyfile.
The Philosophy of Removing Quotation Marks Is Not Workable for Doxygen:
When one asks, "Is it appropriate to remove the quotation marks?"
What if a value looked like this (2 quoted items in one line),
removing quotation marks would be an error:
"abc def" "ghi jkl"
The ABBREVIATE_BRIEF list could indeed appear like this.
If it were argued that all multi-value items should be formatted as
multi-line lists, then quotation marks theory works, as the
ABBREVIATE_BRIEF option does not require quotation marks around
every value.
However, since Doxygen does not require this, there is still a
strong argument for not tampering with quotation marks at all
when importing values. The strongest reasons are:
- Doxygen can and does accept values like this where the value of
an option can be a list. Doxygen sees this as 2 separate values:
"abc def" "ghi jkl"
- If the end user is going to set values with spaces in them,
it could be made the user's responsibility to know when
there are spaces and thus include quotes when needed.
In the end, the "do not tamper with quotation marks" argument wins
for sake of reliability. So the policy is: quotation marks are
neither removed nor added. It is up to the user to know when they
are needed and add them himself.
"""
import logging
import os
import re
__author__ = "Victor Wheeler"
__copyright__ = "Copyright (C) 2025 WGA Crystal Research, Inc."
__license__ = "MIT"
__version__ = "1.0"
class ParseException(Exception):
"""Exception thrown upon unexpected parsing errors."""
pass
class DoxygenConfig:
"""Doxygen Configurations (from/to Doxyfiles)"""
def __init__(self):
"""Prepare instantiated DoxygenConfig for use."""
# Regexes used during Doxyfile parsing
self._re_single_line_option = re.compile(r'^\s*(\w+)\s*=\s*([^\\]*)\s*$')
self._re_top_of_multiline_option = re.compile(r'^\s*(\w+)\s*=\s*(|.*\S)\s*\\$')
# Doxygen cfg items by option name
self._cfg_items_dict = {}
# Comments by name of option below it.
# Comments at end of file have key 'self._end_key'.
self._cfg_comments_dict = {}
# Key used for comments found after last option in Doxyfile
self._end_key = 'END'
# Configuration to match Doxygen -g output (template Doxyfile)
self._char_count_before_equals = 23
def load(self, doxyfile: str):
"""Load options and comments from `doxyfile`
:param doxyfile: Path to doxyfile
:raise FileNotFoundError: When doxyfile not found
:raise ParseException: When there is a parsing error
"""
if not os.path.exists(doxyfile):
logging.error(f'Doxyfile not found {doxyfile}.')
raise FileNotFoundError(doxyfile)
self._cfg_items_dict.clear()
self._cfg_comments_dict.clear()
# Default encoding: UTF-8.
with open(doxyfile, 'r') as file:
in_multiline_opt = False
multiline_opt_name_bep = None # "bep" = "being processed"
accumulated_other_lines = []
for line in file.readlines():
line = line.strip()
if in_multiline_opt:
# There are 2 ways this list can end:
# 1. the normal way when last item has no trailing `\`, or
# 2. the last item has a trailing `\` and there is a blank-
# or comment-line after it, which should NOT be added
# to the list, but instead signal end-of-list.
if not line.endswith('\\'):
in_multiline_opt = False
val = line.rstrip('\\').strip()
if self._bool_comment_or_blank_line(val):
accumulated_other_lines.append(line)
in_multiline_opt = False
else:
self._cfg_items_dict[multiline_opt_name_bep].append(val)
elif self._bool_comment_or_blank_line(line):
accumulated_other_lines.append(line)
elif self._bool_top_of_multiline_option(line):
multiline_opt_name_bep, val = self._parse_multiline_option(line)
self._cfg_items_dict[multiline_opt_name_bep] = [val]
self._cfg_comments_dict[multiline_opt_name_bep] = accumulated_other_lines
accumulated_other_lines = []
in_multiline_opt = True
elif self._bool_single_line_option(line):
option_name, val = self._parse_single_line_option(line)
self._cfg_items_dict[option_name] = val
self._cfg_comments_dict[option_name] = accumulated_other_lines
accumulated_other_lines = []
# Any comments or blank lines found after last Doxygen option
# are represented in _cfg_comments_dict with key `self._end_key`.
if accumulated_other_lines:
self._cfg_comments_dict[self._end_key] = accumulated_other_lines
accumulated_other_lines.clear()
def save(self, doxyfile: str, bare=False):
"""Save configuration to `doxyfile`.
:param doxyfile: Output path where Doxygen configuration will be
written. Overwrites file if it exists.
:param bare: Do not preserve comments from loaded file.
"""
lines = []
for option_name, val in self._cfg_items_dict.items():
if not bare:
lines.extend(self._cfg_comments_dict[option_name])
if type(val) is list:
# We will be aligning the backslashes after the
# items in the list, so we need to know the longest.
# First value in list:
multi_line_indent = ' ' * (self._char_count_before_equals + 2)
longest_len = len(max(val, key=len))
val_w_len = val[0].ljust(longest_len)
lines.append(f'{option_name:<23}= {val_w_len} \\')
# Next n-2 values in list:
if len(val) > 2:
for temp in val[1:-1]:
val_w_len = temp.ljust(longest_len)
lines.append(f'{multi_line_indent}{val_w_len} \\')
# Last value in list:
lines.append(f'{multi_line_indent}{val[-1]}')
elif type(val) is str:
val_w_len = option_name.ljust(self._char_count_before_equals)
if len(val) == 0:
lines.append(f'{val_w_len}=')
else:
lines.append(f'{val_w_len}= {val}')
if self._end_key in self._cfg_comments_dict:
if not bare:
lines.extend(self._cfg_comments_dict[self._end_key])
# Ensure there is exactly 1 newline at end of file.
lines.append('')
with open(doxyfile, 'w') as file:
file.write('\n'.join(lines))
logging.debug(f'Saved configuration to [{doxyfile}].')
def option_names(self):
"""List of contained Doxygen option names"""
return self._cfg_items_dict.keys()
def is_valid_option(self, option_name: str) -> bool:
"""Is `option_name` a valid option name?"""
return option_name in self._cfg_items_dict
def set(self, option_name: str, val: str or list):
"""Set value of specified option
:param option_name: Name of Doxygen option whose value to fetch
:param val: Value to set
- str = single-line value;
- list = multi-line value.
:raises NameError: When ``name`` is not found.
"""
if option_name in self._cfg_items_dict:
self._cfg_items_dict[option_name] = val
if type(val) is list:
logging.debug(f'Item [{option_name}] set to list.')
else:
logging.debug(f'Item [{option_name}] set to [{val}].')
else:
logging.error(f'Doxyfile option {option_name} not found.')
raise NameError(f'Doxygen option {option_name} not found.')
def value(self, option_name: str) -> str or list:
"""Value of specified option
:param option_name: Name of Doxygen option whose value to fetch
:returns string: single-line value
:returns list: multi-line value
:returns None: When ``option_name`` is not found.
"""
if option_name in self._cfg_items_dict:
result = self._cfg_items_dict[option_name]
logging.debug(f'Item [{option_name}] fetched.')
else:
result = None
logging.debug(f'Item [{option_name}] not found.')
return result
def _parse_multiline_option(self, line) -> (str, str):
"""Extract option name and first line of value of multi-line option.
:param line: line to parse
:return: name and first line of multi-line option
:raise ParseException: When process fail to extract data
"""
matches = self._re_top_of_multiline_option.search(line)
if matches is None or len(matches.groups()) != 2:
logging.error(f'Error extracting first value in multi-line option from [{line}].')
raise ParseException(f'Error extracting first value in multi-line option from [{line}].')
return matches.group(1), matches.group(2)
def _parse_single_line_option(self, line) -> (str, str):
"""Extract option name and value of single line option.
:param line: line to parse
:return: option name and value
:raise ParseException: When process fail to extract data
"""
matches = self._re_single_line_option.search(line)
if matches is None or len(matches.groups()) != 2:
logging.error(f'Error extracting option name and value from [{line}].')
raise ParseException(f'Error extracting option name and value from [{line}].')
return matches.group(1), matches.group(2)
def _bool_single_line_option(self, line: str) -> bool:
return self._re_single_line_option.match(line) is not None
def _bool_comment_or_blank_line(self, line: str) -> bool: # NoQA
return line.startswith("#") or (len(line) == 0)
def _bool_top_of_multiline_option(self, line) -> bool:
return self._re_top_of_multiline_option.match(line) is not None

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ if "%LVGL_DOC_BUILD_INTERMEDIATE_DIR%" == "" (
set SOURCEDIR=%LVGL_DOC_BUILD_INTERMEDIATE_DIR%
)
if "%SPHINXOPTS%" == "" (
rem python get_lvgl_version.py >_version_temp.txt
rem python ./src/lvgl_version.py >_version_temp.txt
rem set /p VER=<_version_temp.txt
rem del _version_temp.txt
for /F %%v in ('python lvgl_version.py') do set VER=%%v

View File

@@ -18,4 +18,3 @@ Details
../contributing/index
../CHANGELOG
../API/index
../ROADMAP

View File

@@ -9,47 +9,49 @@ import subprocess
base_path = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, base_path)
project_path = os.path.abspath(os.path.join(base_path, '..', '..'))
docs_path = os.path.join(project_path, 'docs')
project_dir = os.path.abspath(os.path.join(base_path, '..', '..'))
docs_path = os.path.join(project_dir, 'docs')
sys.path.insert(0, docs_path)
import create_fake_lib_c # NOQA
import pycparser_monkeypatch # NOQA
import pycparser # NOQA
doxyfile_filename = 'Doxyfile'
DEVELOP = False
temp_directory = tempfile.mkdtemp(suffix='.lvgl_json')
intermediate_dir = tempfile.mkdtemp(suffix='.lvgl_json')
def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_private, no_docstrings, *compiler_args):
def run(output_path, lv_conf_file, output_to_stdout, target_header, filter_private, no_docstrings, *compiler_args):
pycparser_monkeypatch.FILTER_PRIVATE = filter_private
lvgl_path = project_path
lvgl_src_path = os.path.join(lvgl_path, 'src')
temp_lvgl = os.path.join(temp_directory, 'lvgl')
lvgl_dir = project_dir
lvgl_src_dir = os.path.join(lvgl_dir, 'src')
int_lvgl_dir = os.path.join(intermediate_dir, 'lvgl')
lv_conf_dest_file = os.path.join(intermediate_dir, 'lv_conf.h')
target_header_base_name = (
os.path.splitext(os.path.split(target_header)[-1])[0]
)
try:
os.mkdir(temp_lvgl)
shutil.copytree(lvgl_src_path, os.path.join(temp_lvgl, 'src'))
shutil.copyfile(os.path.join(lvgl_path, 'lvgl.h'), os.path.join(temp_lvgl, 'lvgl.h'))
os.mkdir(int_lvgl_dir)
shutil.copytree(lvgl_src_dir, os.path.join(int_lvgl_dir, 'src'))
shutil.copyfile(os.path.join(lvgl_dir, 'lvgl.h'), os.path.join(int_lvgl_dir, 'lvgl.h'))
pp_file = os.path.join(temp_directory, target_header_base_name + '.pp')
pp_file = os.path.join(intermediate_dir, target_header_base_name + '.pp')
if lvgl_config_path is None:
lvgl_config_path = os.path.join(lvgl_path, 'lv_conf_template.h')
if lv_conf_file is None:
lv_conf_templ_file = os.path.join(lvgl_dir, 'lv_conf_template.h')
with open(lvgl_config_path, 'rb') as f:
data = f.read().decode('utf-8').split('\n')
with open(lv_conf_templ_file, 'rb') as f:
lines = f.read().decode('utf-8').split('\n')
for i, line in enumerate(data):
for i, line in enumerate(lines):
if line.startswith('#if 0'):
data[i] = '#if 1'
lines[i] = '#if 1'
else:
for item in (
'LV_USE_LOG',
@@ -75,17 +77,15 @@ def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_p
'LV_USE_FREETYPE'
):
if line.startswith(f'#define {item} '):
data[i] = f'#define {item} 1'
lines[i] = f'#define {item} 1'
break
with open(os.path.join(temp_directory, 'lv_conf.h'), 'wb') as f:
f.write('\n'.join(data).encode('utf-8'))
with open(lv_conf_dest_file, 'wb') as f:
f.write('\n'.join(lines).encode('utf-8'))
else:
src = lvgl_config_path
dst = os.path.join(temp_directory, 'lv_conf.h')
shutil.copyfile(src, dst)
shutil.copyfile(lv_conf_file, lv_conf_dest_file)
include_dirs = [temp_directory, project_path]
include_dirs = [intermediate_dir, project_dir]
if sys.platform.startswith('win'):
import get_sdl2
@@ -103,7 +103,7 @@ def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_p
env = pyMSVC.setup_environment() # NOQA
cpp_cmd = ['cl', '/std:c11', '/nologo', '/P']
output_pp = f'/Fi"{pp_file}"'
sdl2_include, _ = get_sdl2.get_sdl2(temp_directory)
sdl2_include, _ = get_sdl2.get_sdl2(intermediate_dir)
include_dirs.append(sdl2_include)
include_path_env_key = 'INCLUDE'
@@ -120,7 +120,7 @@ def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_p
]
output_pp = f' >> "{pp_file}"'
fake_libc_path = create_fake_lib_c.run(temp_directory)
fake_libc_path = create_fake_lib_c.run(intermediate_dir)
if include_path_env_key not in os.environ:
os.environ[include_path_env_key] = ''
@@ -178,12 +178,14 @@ def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_p
cparser = pycparser.CParser()
ast = cparser.parse(pp_data, target_header)
doxyfile_src_file = os.path.join(docs_path, doxyfile_filename)
ast.setup_docs(no_docstrings, temp_directory)
ast.setup_docs(no_docstrings, lvgl_src_dir,
intermediate_dir, doxyfile_src_file, output_to_stdout)
if not output_to_stdout and output_path is None:
if not DEVELOP:
shutil.rmtree(temp_directory)
shutil.rmtree(intermediate_dir)
return ast
@@ -260,9 +262,9 @@ def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_p
error = 0
if DEVELOP:
print('temporary file path:', temp_directory)
print('temporary file path:', intermediate_dir)
else:
shutil.rmtree(temp_directory)
shutil.rmtree(intermediate_dir)
sys.exit(error)
@@ -311,7 +313,7 @@ if __name__ == '__main__':
"using this feature."
),
action="store",
default=os.path.join(temp_directory, "lvgl", "lvgl.h")
default=os.path.join(intermediate_dir, "lvgl", "lvgl.h")
)
parser.add_argument(
'--filter-private',

View File

@@ -571,7 +571,7 @@ class FileAST(c_ast.FileAST):
super().__init__(*args, **kwargs)
self._parent = None
def setup_docs(self, no_docstrings, temp_directory): # NOQA
def setup_docs(self, no_docstrings, lvgl_src_dir, intermediate_dir, doxyfile_src_file, silent=False): # NOQA
global get_enum_item_docs
global get_enum_docs
global get_func_docs
@@ -601,22 +601,39 @@ class FileAST(c_ast.FileAST):
get_macros = dummy_list
else:
import doc_builder # NOQA
import doxygen_xml # NoQA
doc_builder.EMIT_WARNINGS = False
# doc_builder.DOXYGEN_OUTPUT = False
doxygen_xml.EMIT_WARNINGS = False
# doxygen_xml.DOXYGEN_OUTPUT = False
docs = doc_builder.XMLSearch(temp_directory)
# Instantiating a doxygen_xml.DoxygenXml object:
# - runs Doxygen in `temp_directory`
# - loads XML into `doxygen_xml.index` as a `xml.etree.ElementTree`
# - builds these dictionaries as direct children of `doxygen_xml`:
# = doxygen_xml.defines dictionary of doxygen_xml.DEFINE objects
# = doxygen_xml.enums dictionary of doxygen_xml.ENUM objects
# = doxygen_xml.variables dictionary of doxygen_xml.VARIABLE objects
# = doxygen_xml.namespaces dictionary of doxygen_xml.NAMESPACE objects
# = doxygen_xml.structures dictionary of doxygen_xml.STRUCT objects
# = doxygen_xml.typedefs dictionary of doxygen_xml.TYPEDEF objects
# = doxygen_xml.functions dictionary of doxygen_xml.FUNCTION objects
# = doxygen_xml.groups dictionary of doxygen_xml.GROUP objects
# = doxygen_xml.files dictionary of doxygen_xml.FILE objects
# = doxygen_xml.classes dictionary of doxygen_xml.CLASS objects
doxygen_xml = doxygen_xml.DoxygenXml(lvgl_src_dir,
intermediate_dir,
doxyfile_src_file,
silent_mode=True)
get_enum_item_docs = docs.get_enum_item
get_enum_docs = docs.get_enum
get_func_docs = docs.get_function
get_var_docs = docs.get_variable
get_union_docs = docs.get_union
get_struct_docs = docs.get_structure
get_typedef_docs = docs.get_typedef
get_macro_docs = docs.get_macro
get_macros = docs.get_macros
get_enum_item_docs = doxygen_xml.get_enum_item
get_enum_docs = doxygen_xml.get_enum
get_func_docs = doxygen_xml.get_function
get_var_docs = doxygen_xml.get_variable
get_union_docs = doxygen_xml.get_union
get_struct_docs = doxygen_xml.get_structure
get_typedef_docs = doxygen_xml.get_typedef
get_macro_docs = doxygen_xml.get_macro
get_macros = doxygen_xml.get_macros
@property
def name(self):