mirror of
https://github.com/lvgl/lvgl.git
synced 2026-02-06 06:02:10 +08:00
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
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:
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
build/
|
||||
doxygen/
|
||||
intermediate/
|
||||
src/_static/built_lv_examples
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
80
docs/announce.py
Normal 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
770
docs/api_doc_builder.py
Normal file
File diff suppressed because it is too large
Load Diff
207
docs/build.py
207
docs/build.py
@@ -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
429
docs/doxygen_config.py
Normal 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
@@ -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
|
||||
|
||||
@@ -18,4 +18,3 @@ Details
|
||||
../contributing/index
|
||||
../CHANGELOG
|
||||
../API/index
|
||||
../ROADMAP
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user