feat(docs): new docs-build paradigm... (#7597)

This commit is contained in:
Victor Wheeler
2025-02-18 18:51:47 -07:00
committed by GitHub
parent 97aa6b8629
commit 1d0bb3e8c6
9 changed files with 518 additions and 423 deletions

View File

@@ -64,9 +64,9 @@ jobs:
- name: Build examples (with cache) - name: Build examples (with cache)
run: scripts/build_html_examples.sh run: scripts/build_html_examples.sh
- name: Build docs - name: Build docs
run: docs/build.py skip_latex run: docs/build.py html
- name: Remove .doctrees - name: Remove .doctrees
run: rm -rf out_html/.doctrees run: rm -rf docs/output/html/.doctrees
- name: Retrieve version - name: Retrieve version
run: | run: |
echo "::set-output name=VERSION_NAME::$(scripts/find_version.sh)" echo "::set-output name=VERSION_NAME::$(scripts/find_version.sh)"
@@ -77,7 +77,7 @@ jobs:
token: ${{ secrets.LVGL_BOT_TOKEN }} token: ${{ secrets.LVGL_BOT_TOKEN }}
repository-name: lvgl/docs repository-name: lvgl/docs
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: out_html # The folder the action should deploy. folder: docs/output/html # The folder the action should deploy.
target-folder: ${{ steps.version.outputs.VERSION_NAME }} target-folder: ${{ steps.version.outputs.VERSION_NAME }}
git-config-name: lvgl-bot git-config-name: lvgl-bot
git-config-email: lvgl-bot@users.noreply.github.com git-config-email: lvgl-bot@users.noreply.github.com
@@ -89,7 +89,7 @@ jobs:
token: ${{ secrets.LVGL_BOT_TOKEN }} token: ${{ secrets.LVGL_BOT_TOKEN }}
repository-name: lvgl/docs repository-name: lvgl/docs
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: out_html # The folder the action should deploy. folder: docs/output/html # The folder the action should deploy.
target-folder: master target-folder: master
git-config-name: lvgl-bot git-config-name: lvgl-bot
git-config-email: lvgl-bot@users.noreply.github.com git-config-email: lvgl-bot@users.noreply.github.com

8
.gitignore vendored
View File

@@ -11,13 +11,13 @@ scripts/built_in_font/lv_font_*
docs/doxygen_html docs/doxygen_html
docs/xml docs/xml
docs/examples.md docs/examples.md
docs/out_latex docs/tmp/
docs/output/
docs/_static/built_lv_examples docs/_static/built_lv_examples
docs/LVGL.pdf docs/LVGL.pdf
docs/env docs/env
/docs/examples.rst docs/examples.rst
/docs/API/ docs/API/
out_html
__pycache__ __pycache__
/emscripten_builder /emscripten_builder
test_screenshot_error.h test_screenshot_error.h

View File

@@ -733,14 +733,14 @@ QUIET = YES
# Tip: Turn warnings on while writing the documentation. # Tip: Turn warnings on while writing the documentation.
# The default value is: YES. # The default value is: YES.
WARNINGS = NO WARNINGS = YES
# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled. # will automatically be disabled.
# The default value is: YES. # The default value is: YES.
WARN_IF_UNDOCUMENTED = NO WARN_IF_UNDOCUMENTED = YES
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters # potential errors in the documentation, such as not documenting some parameters
@@ -748,7 +748,7 @@ WARN_IF_UNDOCUMENTED = NO
# markup commands wrongly. # markup commands wrongly.
# The default value is: YES. # The default value is: YES.
WARN_IF_DOC_ERROR = NO WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return # are documented, but have no documentation for their parameters or return
@@ -778,7 +778,7 @@ WARN_FORMAT = "WARNING: $file:$line: $text"
# messages should be written. If left blank the output is written to standard # messages should be written. If left blank the output is written to standard
# error (stderr). # error (stderr).
WARN_LOGFILE = WARN_LOGFILE = doxygen_warnings.txt
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the input files # Configuration options related to the input files
@@ -790,7 +790,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched. # 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 # 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 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -850,8 +850,15 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to # 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 all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = */libs/thorvg/rapidjson/* \ EXCLUDE_PATTERNS = */libs/barcode/code* \
*/libs/thorvg/tvg* */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 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the # (namespaces, classes, functions, etc.) that should be excluded from the
@@ -1203,7 +1210,7 @@ HTML_COLORSTYLE_GAMMA = 80
# The default value is: NO. # The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = NO # HTML_TIMESTAMP = NO # Obsolete
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the # documentation will contain sections that can be hidden and shown after the
@@ -1788,7 +1795,7 @@ LATEX_BIB_STYLE = plain
# The default value is: NO. # The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES. # This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO # LATEX_TIMESTAMP = NO # Obsolete
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the RTF output # Configuration options related to the RTF output
@@ -2061,7 +2068,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator. # recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. # 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_PATH>>"
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # 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 # tag can be used to specify a list of macro names that should be expanded. The

View File

@@ -1,6 +1,112 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" Generate LVGL documentation using Doxygen and Sphinx. """ build.py -- Generate LVGL documentation using Doxygen and Sphinx + Breathe.
Synopsis
--------
- $ python build.py html [ skip_api ] [ fresh_env ]
- $ python build.py latex [ skip_api ] [ fresh_env ]
- $ python build.py temp [ skip_api ]
- $ python build.py clean
- $ python build.py clean_temp
- $ python build.py clean_html
- $ python build.py clean_latex
Build Arguments and Clean Arguments can be used one at a time
or be freely mixed and combined.
Description
-----------
Source files are copied to a temporary directory and modified there before
doc generation occurs. If a full rebuild is being done (e.g. after a `clean`)
Doxygen is run on LVGL's source files to generate intermediate API information
in XML format, example documents are generated, API documents are generated
for Breathe's consumption, and API links are added to the end of some documents.
From there, Sphinx with Breathe extension uses the resulting set of source
files to generate the desired output.
It is only during this first build that the `skip_api` option has meaning.
After the first build, no further actions is taken regarding API pages since
they are not regenerated after the first build.
The temporary directory has a fixed location (overridable by
`LVGL_DOC_BUILD_TEMP_DIR` environment variable) and by default this
script attempts to rebuild only those documents whose path, name or
modification date has changed since the last build.
Caution:
The document build meant for end-user consumption should ONLY be done after a
`clean` unless you know that no API documentation and no code examples have changed.
A `sphinx-build` will do a full doc rebuild any time:
- the temporary directory doesn't exist or is empty (since the new files in
the temporary directory will have modification times after the generated
HTML or Latex files, even if nothing changed),
- the targeted output directory doesn't exist or is empty, or
- Sphinx determines that a full rebuild is necessary. This happens when:
- temporary directory (Sphinx's source-file path) has changed,
- any options on the `sphinx-build` command line have changed,
- `conf.py` modification date has changed, or
- `fresh_env` argument is included (runs `sphinx-build` with -E option).
Typical run time:
Full build: 22.5 min
skip_api : 1.9 min (applies to first build only)
Options
-------
help
Print usage note and exit with status 0.
html [ skip_api ] [ fresh_env ]
Build HTML output.
`skip_api` only has effect on first build after a `clean` or `clean_temp`.
latex [ skip_api ] [ fresh_env ]
Build Latex/PDF output (on hold pending removal of non-ASCII characters from input files).
`skip_api` only has effect on first build after a `clean` or `clean_temp`.
temp [ skip_api ]
Generate temporary directory contents (ready to build output formats).
If they already exist, they are removed and re-generated.
skip_api (with `html` and/or `latex` and/or `temp` options)
Skip API pages and links when temp directory contents are being generated
(saving about 91% of build time). Note: they are not thereafter regenerated
unless requested by `temp` argument or the temp directory does not exist.
This is intended to be used only during doc development to speed up
turn-around time between doc modifications and seeing final results.
fresh_env (with `html` and/or `latex` options)
Run `sphinx-build` with -E command-line argument, which makes it regenerate its
"environment" (memory of what was built previously, forcing a full rebuild).
clean
Remove all generated files.
clean_temp
Remove temporary directory.
clean_html
Remove HTML output directory.
clean_latex
Remove Latex output directory.
Unrecognized arguments print error message, usage note, and exit with status 1.
Python Package Requirements
---------------------------
The list of Python package requirements are in `requirements.txt`.
Install them by:
$ pip install -r requirements.txt
History
-------
The first version of this file (Apr 2021) discovered the name of The first version of this file (Apr 2021) discovered the name of
the current branch (e.g. 'master', 'release/v8.4', etc.) to support the current branch (e.g. 'master', 'release/v8.4', etc.) to support
different versions of the documentation by establishing the base URL different versions of the documentation by establishing the base URL
@@ -25,7 +131,7 @@ Since then its duties have grown to include:
- Supporting additional command-line options. - Supporting additional command-line options.
- Generating a temporary `./docs/lv_conf.h` for Doxygen to use - Generating a temporary `./docs/lv_conf.h` for Doxygen to use
(config_builder.py). (via config_builder.py).
- Supporting multiple execution platforms (which then required tokenizing - Supporting multiple execution platforms (which then required tokenizing
Doxygen's INPUT path in `Doxyfile` and re-writing portions that used Doxygen's INPUT path in `Doxyfile` and re-writing portions that used
@@ -33,263 +139,84 @@ Since then its duties have grown to include:
- Adding translation and API links (requiring generating docs in a - Adding translation and API links (requiring generating docs in a
temporary directory so that the links could be programmatically temporary directory so that the links could be programmatically
added to each document before Sphinx was run). added to each document before Sphinx was run). Note: translation link
are now done manually since they are only on the main landing page.
- Generating EXAMPLES page + sub-examples where applicable to individual - Generating EXAMPLES page + sub-examples where applicable to individual
documents, e.g. to widget-, style-, layout-pages, etc. documents, e.g. to widget-, style-, layout-pages, etc..
- Building PDF via latex (when working). - Building PDF via latex (when working).
- Shifting doc-generation paradigm to behave more like `make`.
Command-Line Arguments
----------------------
Command-line arguments have been broken down to give the user the
ability to control each individual major variation in behavior of
this script. These were added to speed up long doc-development
tasks by shortening the turn-around time between doc modification
and seeing the final .html results in a local development environment.
Finally, this script can now be used in a way such that Sphinx will
only modify changed documents, and reduce an average ~22-minute
run time to a run time that is workable for rapidly repeating doc
generation to see Sphinx formatting results quickly.
Normal Usage
------------
This is the way this script is used for normal (full) docs generation.
$ python build.py skip_latex
Docs-Dev Initial Docs Generation Usage
--------------------------------------
1. Set `LVGL_FIXED_TEMP_DIR` environment variable to path to
the temporary directory you will use over and over during
document editing, without trailing directory separator.
Initially directory should not exist.
2. $ python build.py skip_latex preserve fixed_tmp_dir
This takes typically ~22 minutes.
Docs-Dev Update-Only Generation Usage
-------------------------------------
After the above has been run through once, you can thereafter
run the following until docs-development task is complete.
$ python build.py skip_latex docs_dev update
Generation time depends on the number of `.rst` files that
have been updated:
+--------------+------------+---------------------------------+
| Docs Changed | Time | Typical Time to Browser Refresh |
+==============+============+=================================+
| 0 | 6 seconds | n/a |
+--------------+------------+---------------------------------+
| 1 | 19 seconds | 12 seconds |
+--------------+------------+---------------------------------+
| 5 | 28 seconds | 21 seconds |
+--------------+------------+---------------------------------+
| 20 | 59 seconds | 52 seconds |
+--------------+------------+---------------------------------+
Sphinx Doc-Regeneration Criteria
--------------------------------
Sphinx uses the following to determine what documents get updated:
- source-doc modification date
- Change the modification date and `sphinx-build` will re-build it.
- full (absolute) path to the source document, including its file name
- Change the path or filename and `sphinx-build` will re-build it.
- whether the -E option is on the `sphinx-build` command line
- -E forces `sphinx-build` to do a full re-build.
Argument Descriptions
---------------------
- skip_latex
The meaning of this argument has not changed: it simply skips
attempting to generate Latex and subsequent PDF generation.
- skip_api
Skips generating API pages (this saves about 70% of build time).
This is intended to be used only during doc development to speed up
turn-around time between doc modifications and seeing final results.
- no_fresh_env
Excludes -E command-line argument to `sphinx-build`, which, when present,
forces it to generate a whole new environment (memory of what was built
previously, forcing a full rebuild). "no_fresh_env" enables a rebuild
of only docs that got updated -- Sphinx's default behavior.
- preserve (previously "develop")
Leaves temporary directory intact for docs development purposes.
- fixed_tmp_dir
If (fixed_tmp_dir and 'LVGL_FIXED_TEMP_DIR' in `os.environ`),
then this script uses the value of that that environment variable
to populate `temp_directory` instead of the normal (randomly-named)
temporary directory. This is important when getting `sphinx-build`
to ONLY rebuild updated documents, since changing the directory
from which they are generated (normally the randomly-named temp
dir) will force Sphinx to do a full-rebuild because it remembers
the doc paths from which the build was last performed.
- skip_trans
Skips adding translation links. This allows direct copying of
`.rst` files to `temp_directory` when they are updated to save time
during re-build. Final build must not include this option so that
the translation links are added at the top of each intended page.
- no_copy
Skips copying ./docs/ directory tree to `temp_directory`.
This is only honored if:
- fixed_tmp_dir == True, and
- the doc files were previously copied to the temporary directory
and thus are already present there.
- docs_dev
This is a command-line shortcut to combining these command-line args:
- no_fresh_env
- preserve
- fixed_tmp_dir
- no_copy
- update
When no_copy is active, check modification dates on `.rst` files
and re-copy the updated `./docs/` files to the temporary directory
that have later modification dates, thus updating what Sphinx uses
as input.
Warning: this wipes out translation links and API-page links that
were added in the first pass, so should only be used for doc
development -- not for final doc generation.
""" """
# **************************************************************************** # ****************************************************************************
# IMPORTANT: If you are getting a PDF-lexer error for an example, check # IMPORTANT: If you are getting a PDF-lexer error for an example, check
# for extra lines at the end of the file. Only a single empty line # for extra lines at the end of the file. Only a single empty line
# is allowed!!! Ask me how long it took me to figure this out. # is allowed!!! Ask me how long it took me to figure this out.
# -- @kdschlosser
# **************************************************************************** # ****************************************************************************
# Python Library
def run():
# Python Library Imports
import sys import sys
import os import os
import re import re
import subprocess import subprocess
import shutil import shutil
import tempfile
import dirsync import dirsync
from datetime import datetime from datetime import datetime
# LVGL Custom Imports # LVGL Custom
import example_list as ex import example_list
import doc_builder import doc_builder
import config_builder import config_builder
import add_translation import add_translation
# --------------------------------------------------------------------- # -------------------------------------------------------------------------
# Start. # Configuration
# --------------------------------------------------------------------- # -------------------------------------------------------------------------
t1 = datetime.now() # These are relative paths from the ./docs/ directory.
print('Current time: ' + str(t1)) cfg_temp_dir = 'tmp'
cfg_output_dir = 'output'
# --------------------------------------------------------------------- # Filename generated in `cfg_latex_output_dir` and copied to `cfg_pdf_output_dir`.
# Process args. cfg_pdf_filename = 'LVGL.pdf'
#
# With argument `docs_dev`, Sphinx will generate docs from a fixed
# temporary directory that can be then used later using the same
# command line to get Sphinx to ONLY rebuild changed documents.
# This saves a huge amount of time during long document projects.
# ---------------------------------------------------------------------
# Set defaults.
clean = False
skip_latex = False
skip_api = False
fresh_sphinx_env = True
preserve = False
fixed_tmp_dir = False
skip_trans = False
no_copy = False
docs_dev = False
update = False
args = sys.argv[1:]
for arg in args:
# We use chained `if-elif-else` instead of `match` for those on Linux # -------------------------------------------------------------------------
# systems that will not have the required version 3.10 of Python yet. # Print usage note.
if arg == "clean": # -------------------------------------------------------------------------
clean = True def print_usage_note():
elif arg == "skip_latex": print('Usage:')
skip_latex = True print(' $ python build.py [optional_arg ...]')
elif arg == 'skip_api': print()
skip_api = True print(' where `optional_arg` can be any of these:')
elif arg == 'no_fresh_env': print(' html [ skip_api ] [ fresh_env ]')
fresh_sphinx_env = False print(' latex [ skip_api ] [ fresh_env ]')
elif arg == 'preserve': print(' temp [ skip_api ]')
preserve = True print(' clean')
elif arg == 'fixed_tmp_dir': print(' clean_temp')
fixed_tmp_dir = True print(' clean_html')
elif arg == 'skip_trans': print(' clean_latex')
skip_trans = True print(' help')
elif arg == 'no_copy':
no_copy = True
elif arg == 'docs_dev': # -------------------------------------------------------------------------
docs_dev = True # Remove directory `tgt_dir`.
elif arg == 'update': # -------------------------------------------------------------------------
update = True def remove_dir(tgt_dir):
if os.path.isdir(tgt_dir):
print(f'Removing {tgt_dir}...')
shutil.rmtree(tgt_dir)
else: else:
print(f'Argument [{arg}] not recognized.') print(f'{tgt_dir} already removed...')
exit(1)
# Arg ramifications:
# docs_dev implies no_fresh_env, preserve, fixed_tmp_dir, and no_copy.
if docs_dev:
fresh_sphinx_env = False
preserve = True
fixed_tmp_dir = True
no_copy = True
# --------------------------------------------------------------------- # -------------------------------------------------------------------------
# Due to the modifications that take place to the documentation files # Run external command and abort build on error.
# when the documentation builds it is better to copy the source files to a # -------------------------------------------------------------------------
# temporary folder and modify the copies. Not setting it up this way makes it def cmd(s, start_dir=None, exit_on_error=True):
# a real headache when making alterations that need to be committed as the
# alterations trigger the files as changed. Also, this keeps maintenance
# effort to a minimum as adding a new language translation only needs to be
# done in 2 places (add_translation.py and ./docs/_ext/link_roles.py) rather
# than once for each .rst file.
#
# The html and PDF output locations are going to remain the same as they were.
# it's just the source documentation files that are going to be copied.
# ---------------------------------------------------------------------
if fixed_tmp_dir and 'LVGL_FIXED_TEMP_DIR' in os.environ:
temp_directory = os.environ['LVGL_FIXED_TEMP_DIR']
else:
temp_directory = tempfile.mkdtemp(suffix='.lvgl_docs')
print(f'Using temp directory: [{temp_directory}]')
# ---------------------------------------------------------------------
# Set up paths.
# ---------------------------------------------------------------------
base_path = os.path.abspath(os.path.dirname(__file__))
project_path = os.path.abspath(os.path.join(base_path, '..'))
examples_path = os.path.join(project_path, 'examples')
lvgl_src_path = os.path.join(project_path, 'src')
latex_output_path = os.path.join(temp_directory, 'out_latex')
pdf_src_file = os.path.join(latex_output_path, 'LVGL.pdf')
pdf_dst_file = os.path.join(temp_directory, 'LVGL.pdf')
html_src_path = temp_directory
html_dst_path = os.path.join(project_path, 'out_html')
# ---------------------------------------------------------------------
# Change to script directory for consistency.
# ---------------------------------------------------------------------
os.chdir(base_path)
# ---------------------------------------------------------------------
# Provide a way to run an external command and abort build on error.
# ---------------------------------------------------------------------
def cmd(s, start_dir=None):
if start_dir is None: if start_dir is None:
start_dir = os.getcwd() start_dir = os.getcwd()
@@ -301,10 +228,205 @@ def run():
result = os.system(s) result = os.system(s)
os.chdir(saved_dir) os.chdir(saved_dir)
if result != 0: if result != 0 and exit_on_error:
print("Exiting build due to previous error.") print("Exiting build due to previous error.")
sys.exit(result) sys.exit(result)
# -------------------------------------------------------------------------
# Build and return LVGL version string from `lv_version.h`. Updated to be
# multi-platform compatible and resilient to changes in file in compliance
# with C macro syntax.
# -------------------------------------------------------------------------
def get_version(_verfile):
major = ''
minor = ''
with open(_verfile, 'r') as file:
major_re = re.compile(r'define\s+LVGL_VERSION_MAJOR\s+(\d+)')
minor_re = re.compile(r'define\s+LVGL_VERSION_MINOR\s+(\d+)')
for line in file.readlines():
# Skip if line not long enough to match.
if len(line) < 28:
continue
match = major_re.search(line)
if match is not None:
major = match[1]
else:
match = minor_re.search(line)
if match is not None:
minor = match[1]
# Exit early if we have both values.
if len(major) > 0 and len(minor) > 0:
break
return f'{major}.{minor}'
# -------------------------------------------------------------------------
# Provide answer to question: Can we have reasonable confidence that
# the contents of `temp_directory` already exists?
# -------------------------------------------------------------------------
def temp_dir_contents_exists(_tmpdir):
result = False
c1 = os.path.isdir(_tmpdir)
if c1:
temp_path = os.path.join(_tmpdir, 'CHANGELOG.rst')
c2 = os.path.exists(temp_path)
temp_path = os.path.join(_tmpdir, 'CODING_STYLE.rst')
c3 = os.path.exists(temp_path)
temp_path = os.path.join(_tmpdir, 'CONTRIBUTING.rst')
c4 = os.path.exists(temp_path)
temp_path = os.path.join(_tmpdir, '_ext')
c5 = os.path.isdir(temp_path)
temp_path = os.path.join(_tmpdir, '_static')
c6 = os.path.isdir(temp_path)
temp_path = os.path.join(_tmpdir, 'details')
c7 = os.path.isdir(temp_path)
temp_path = os.path.join(_tmpdir, 'intro')
c8 = os.path.isdir(temp_path)
temp_path = os.path.join(_tmpdir, 'examples')
c9 = os.path.isdir(temp_path)
result = c2 and c3 and c4 and c5 and c6 and c7 and c8 and c9
return result
# -------------------------------------------------------------------------
# Perform doc-build function(s) requested.
# -------------------------------------------------------------------------
def run():
# ---------------------------------------------------------------------
# Process args.
#
# With argument `docs_dev`, Sphinx will generate docs from a fixed
# temporary directory that can be then used later using the same
# command line to get Sphinx to ONLY rebuild changed documents.
# This saves a huge amount of time during long document projects.
# ---------------------------------------------------------------------
# Set defaults.
clean_temp = False
clean_html = False
clean_latex = False
clean_all = False
build_html = False
build_latex = False
build_temp = False
skip_api = False
fresh_sphinx_env = False
args = sys.argv[1:]
# No args means print usage note and exit with status 0.
if len(args) == 0:
print_usage_note()
exit(0)
# Some args are present. Interpret them in loop here.
# Unrecognized arg means print error, print usage note, exit with status 1.
for arg in args:
# We use chained `if-elif-else` instead of `match` for those on Linux
# systems that will not have the required version 3.10 of Python yet.
if arg == 'help':
print_usage_note()
exit(0)
elif arg == "temp":
build_temp = True
elif arg == "html":
build_html = True
elif arg == "latex":
build_latex = True
elif arg == 'skip_api':
skip_api = True
elif arg == 'fresh_env':
fresh_sphinx_env = True
elif arg == "clean":
clean_all = True
clean_temp = True
clean_html = True
clean_latex = True
elif arg == "clean_temp":
clean_temp = True
elif arg == "clean_html":
clean_html = True
elif arg == "clean_latex":
clean_latex = True
else:
print(f'Argument [{arg}] not recognized.')
print()
print_usage_note()
exit(1)
# ---------------------------------------------------------------------
# Start.
# ---------------------------------------------------------------------
t0 = datetime.now()
# ---------------------------------------------------------------------
# Set up paths.
# ---------------------------------------------------------------------
base_path = os.path.abspath(os.path.dirname(__file__))
project_path = os.path.abspath(os.path.join(base_path, '..'))
examples_path = os.path.join(project_path, 'examples')
lvgl_src_path = os.path.join(project_path, 'src')
output_path = os.path.join(base_path, cfg_output_dir)
html_output_path = os.path.join(output_path, 'html')
latex_output_path = os.path.join(output_path, 'latex')
pdf_src_file = os.path.join(latex_output_path, cfg_pdf_filename)
pdf_dst_file = os.path.join(base_path, cfg_pdf_filename)
version_source_path = os.path.join(project_path, 'lv_version.h')
# Establish temporary directory. The presence of environment variable
# `LVGL_DOC_BUILD_TEMP_DIR` overrides default in `cfg_temp_dir`.
#
# Temporary directory is used as Sphinx source directory -- some source
# files are edited, some are fully generated. Adding translation
# links can be done with by adding temp in:
# - ./docs/add_translation.py, and
# - ./docs/_ext/link_roles.py.
# Currently, translation-link editing is being suppressed since there
# is only 1 file that gets the link: top-level landing page.
if 'LVGL_DOC_BUILD_TEMP_DIR' in os.environ:
temp_directory = os.environ['LVGL_DOC_BUILD_TEMP_DIR']
else:
temp_directory = os.path.join(base_path, cfg_temp_dir)
html_src_path = temp_directory
print(f'Temporary directory: [{temp_directory}]')
# ---------------------------------------------------------------------
# Change to script directory for consistency.
# ---------------------------------------------------------------------
os.chdir(base_path)
# ---------------------------------------------------------------------
# Clean? If so, clean (like `make clean`), but do not exit.
# ---------------------------------------------------------------------
some_cleaning_to_be_done = clean_temp or clean_html or clean_latex or clean_all \
or (os.path.isdir(temp_directory) and build_temp)
if some_cleaning_to_be_done:
print("****************")
print("Cleaning...")
print("****************")
if clean_temp:
remove_dir(temp_directory)
if clean_html:
remove_dir(html_output_path)
if clean_latex:
remove_dir(latex_output_path)
if clean_all:
remove_dir(output_path)
if os.path.isdir(temp_directory) and build_temp:
remove_dir(temp_directory)
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Populate LVGL_URLPATH and LVGL_GITCOMMIT environment variables: # Populate LVGL_URLPATH and LVGL_GITCOMMIT environment variables:
# - LVGL_URLPATH <= 'master' or '8.4' '9.2' etc. # - LVGL_URLPATH <= 'master' or '8.4' '9.2' etc.
@@ -361,107 +483,74 @@ def run():
os.environ['LVGL_URLPATH'] = branch os.environ['LVGL_URLPATH'] = branch
os.environ['LVGL_GITCOMMIT'] = branch os.environ['LVGL_GITCOMMIT'] = branch
# ---------------------------------------------------------------------
# Start doc-build process.
# ---------------------------------------------------------------------
print("")
print("****************")
print("Building")
print("****************")
# Remove all previous output files if 'clean' on command line.
if clean:
print('Removing previous output files...')
# The below commented-out code below is being preserved
# for docs-generation development purposes.
# api_path = os.path.join(temp_directory, 'API')
# xml_path = os.path.join(temp_directory, 'xml')
# doxy_path = os.path.join(temp_directory, 'doxygen_html')
# if os.path.exists(api_path):
# shutil.rmtree(api_path)
# lang = 'en'
# if os.path.exists(lang):
# shutil.rmtree(lang)
if os.path.exists(html_dst_path):
shutil.rmtree(html_dst_path)
# if os.path.exists(xml_path):
# shutil.rmtree(xml_path)
#
# if os.path.exists(doxy_path):
# shutil.rmtree(doxy_path)
# os.mkdir(api_path)
# os.mkdir(lang)
# ---------------------------------------------------------------------
# Build local lv_conf.h from lv_conf_template.h for this build only.
# ---------------------------------------------------------------------
config_builder.run()
# ---------------------------------------------------------------------
# Provide answer to question: Can we have reasonable confidence that
# the contents of `temp_directory` already exists?
# ---------------------------------------------------------------------
def temp_dir_contents_exists():
result = False
c1 = os.path.exists(temp_directory)
if c1:
temp_path = os.path.join(temp_directory, 'CHANGELOG.rst')
c2 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, 'CODING_STYLE.rst')
c3 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, 'CONTRIBUTING.rst')
c4 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, '_ext')
c5 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, '_static')
c6 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, 'details')
c7 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, 'intro')
c8 = os.path.exists(temp_path)
temp_path = os.path.join(temp_directory, 'examples')
c9 = os.path.exists(temp_path)
result = c2 and c3 and c4 and c5 and c6 and c7 and c8 and c9
return result
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Copy files to 'temp_directory' where they will be edited (translation # Copy files to 'temp_directory' where they will be edited (translation
# link and API links) before being used to generate new docs. # link(s) and API links) before being used to generate new docs.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
doc_files_copied = False # dirsync `exclude_list` = list of regex patterns to exclude.
if no_copy and fixed_tmp_dir and temp_dir_contents_exists(): exclude_list = [r'lv_conf\.h', r'^tmp.*', r'^output.*']
if update:
exclude_list = ['lv_conf.h'] if temp_dir_contents_exists(temp_directory):
# We are just doing an update of the temp_directory contents.
print("****************")
print("Updating temp directory...")
print("****************")
exclude_list.append(r'examples.*')
options = { options = {
'verbose': True, 'verbose': True, # Report files copied.
'create': True, 'create': True, # Create directories if they don't exist.
'twoway': False, # False means data flow only src => tgt.
'purge': False, # False means DO NOT remove orphan files/dirs in tgt dir (preserving examples/ dir).
'exclude': exclude_list 'exclude': exclude_list
} }
dirsync.sync('.', temp_directory, 'update', **options) # action == 'sync' means copy files even when they do not already exist in tgt dir.
else: # action == 'update' means DO NOT copy files when they do not already exist in tgt dir.
print("Skipping copying ./docs/ directory as requested.") dirsync.sync('.', temp_directory, 'sync', **options)
else: dirsync.sync(examples_path, os.path.join(temp_directory, 'examples'), 'sync', **options)
shutil.copytree('.', temp_directory, dirs_exist_ok=True) elif build_temp or build_html or build_latex:
shutil.copytree(examples_path, os.path.join(temp_directory, 'examples'), dirs_exist_ok=True) # We are having to create the temp_directory contents by copying.
doc_files_copied = True print("****************")
print("Building temp directory...")
print("****************")
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('.', temp_directory, ignore=ignore_func, dirs_exist_ok=True)
print('Copying examples...')
shutil.copytree(examples_path, os.path.join(temp_directory, 'examples'), dirs_exist_ok=True)
else:
# --------- Method 1:
options = {
'create': True, # Create directories if they don't exist.
'exclude': exclude_list
}
# 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('.', temp_directory, 'sync', **options)
print('Copying examples...')
dirsync.sync(examples_path, os.path.join(temp_directory, 'examples'), 'sync', **options)
# -----------------------------------------------------------------
# Build Example docs, Doxygen output, API docs, and API links.
# -----------------------------------------------------------------
t1 = datetime.now()
# Build local lv_conf.h from lv_conf_template.h for this build only.
config_builder.run()
# ---------------------------------------------------------------------
# Replace tokens in Doxyfile in 'temp_directory' with data from this run. # Replace tokens in Doxyfile in 'temp_directory' with data from this run.
# ---------------------------------------------------------------------
if doc_files_copied:
with open(os.path.join(temp_directory, 'Doxyfile'), 'rb') as f: with open(os.path.join(temp_directory, 'Doxyfile'), 'rb') as f:
data = f.read().decode('utf-8') data = f.read().decode('utf-8')
data = data.replace('#*#*LV_CONF_PATH*#*#', os.path.join(base_path, 'lv_conf.h')) data = data.replace('<<LV_CONF_PATH>>', os.path.join(base_path, 'lv_conf.h'))
data = data.replace('*#*#SRC#*#*', '"{0}"'.format(lvgl_src_path)) data = data.replace('<<SRC>>', '"{0}"'.format(lvgl_src_path))
with open(os.path.join(temp_directory, 'Doxyfile'), 'wb') as f: with open(os.path.join(temp_directory, 'Doxyfile'), 'wb') as f:
f.write(data.encode('utf-8')) f.write(data.encode('utf-8'))
@@ -471,13 +560,15 @@ def run():
# in individual documents where applicable. # in individual documents where applicable.
# ----------------------------------------------------------------- # -----------------------------------------------------------------
print("Generating examples...") print("Generating examples...")
ex.exec(temp_directory) example_list.exec(temp_directory)
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# Add translation links. # Add translation links.
# This is being skipped in favor of a manually-placed
# translation link at the top of `./docs/index.rst`.
# ----------------------------------------------------------------- # -----------------------------------------------------------------
if skip_trans: if True:
print("Skipping translation links as requested.") print("Skipping adding translation links.")
else: else:
print("Adding translation links...") print("Adding translation links...")
add_translation.exec(temp_directory) add_translation.exec(temp_directory)
@@ -491,9 +582,11 @@ def run():
print("Running Doxygen...") print("Running Doxygen...")
cmd('doxygen Doxyfile', temp_directory) cmd('doxygen Doxyfile', temp_directory)
print("API page and link processing...")
doc_builder.EMIT_WARNINGS = False doc_builder.EMIT_WARNINGS = False
# Create .RST files for API pages. # Create .RST files for API pages, plus
# add API hyperlinks to .RST files in the directories in passed array.
doc_builder.run( doc_builder.run(
project_path, project_path,
temp_directory, temp_directory,
@@ -518,18 +611,26 @@ def run():
os.path.join(temp_directory, 'details', 'integration', 'renderers'), os.path.join(temp_directory, 'details', 'integration', 'renderers'),
os.path.join(temp_directory, 'details', 'libs'), os.path.join(temp_directory, 'details', 'libs'),
os.path.join(temp_directory, 'details', 'main-components'), os.path.join(temp_directory, 'details', 'main-components'),
# Note: details/main-components/display omitted intentionally,
# since API links for those .RST files have been added manually.
os.path.join(temp_directory, 'details', 'other-components'), os.path.join(temp_directory, 'details', 'other-components'),
os.path.join(temp_directory, 'details', 'widgets') os.path.join(temp_directory, 'details', 'widgets')
) )
print('Reading Doxygen output...') t2 = datetime.now()
print('Example/API processing run time: ' + str(t2 - t1))
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# BUILD PDF # Build PDF
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
if skip_latex: if not build_latex:
print("Skipping latex build as requested.") print("Skipping Latex build.")
else: else:
t1 = datetime.now()
print("****************")
print("Building Latex output...")
print("****************")
# Remove PDF link so PDF does not have a link to itself. # Remove PDF link so PDF does not have a link to itself.
index_path = os.path.join(temp_directory, 'index.rst') index_path = os.path.join(temp_directory, 'index.rst')
@@ -548,14 +649,17 @@ def run():
# PDF download link in the PDF # PDF download link in the PDF
# cmd("cp -f " + lang +"/latex/LVGL.pdf LVGL.pdf | true") # cmd("cp -f " + lang +"/latex/LVGL.pdf LVGL.pdf | true")
src = temp_directory src = temp_directory
dst = latex_output_path dst = output_path
cpu = os.cpu_count() cpu = os.cpu_count()
cmd_line = f'sphinx-build -b latex "{src}" "{dst}" -j {cpu}' cmd_line = f'sphinx-build -M latex "{src}" "{dst}" -j {cpu}'
cmd(cmd_line) cmd(cmd_line)
# Generate PDF. # Generate PDF.
print("****************")
print("Building PDF...")
print("****************")
cmd_line = 'latexmk -pdf "LVGL.tex"' cmd_line = 'latexmk -pdf "LVGL.tex"'
cmd(cmd_line, latex_output_path) cmd(cmd_line, latex_output_path, True)
# Copy the result PDF to the main directory to make # Copy the result PDF to the main directory to make
# it available for the HTML build. # it available for the HTML build.
@@ -567,37 +671,20 @@ def run():
with open(index_path, 'wb') as f: with open(index_path, 'wb') as f:
f.write(index_data.encode('utf-8')) f.write(index_data.encode('utf-8'))
t2 = datetime.now()
print('PDF : ' + pdf_dst_file)
print('Latex gen run time: ' + str(t2 - t1))
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# BUILD HTML # Build HTML
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# This version of get_version() works correctly under both Linux and Windows. if not build_html:
# Updated to be resilient to changes in `lv_version.h` compliant with C macro syntax. print("Skipping HTML build.")
def get_version():
path = os.path.join(project_path, 'lv_version.h')
major = ''
minor = ''
with open(path, 'r') as file:
major_re = re.compile(r'define\s+LVGL_VERSION_MAJOR\s+(\d+)')
minor_re = re.compile(r'define\s+LVGL_VERSION_MINOR\s+(\d+)')
for line in file.readlines():
# Skip if line not long enough to match.
if len(line) < 28:
continue
match = major_re.search(line)
if match is not None:
major = match[1]
else: else:
match = minor_re.search(line) t1 = datetime.now()
if match is not None: print("****************")
minor = match[1] print("Building HTML output...")
# Exit early if we have both values. print("****************")
if len(major) > 0 and len(minor) > 0:
break
return f'{major}.{minor}'
# Note: While it can be done (e.g. if one needs to set a stop point # Note: While it can be done (e.g. if one needs to set a stop point
# in Sphinx code for development purposes), it is NOT a good idea to # in Sphinx code for development purposes), it is NOT a good idea to
@@ -618,44 +705,31 @@ def run():
print("Regenerating only updated files...") print("Regenerating only updated files...")
env_opt = '' env_opt = ''
ver = get_version() ver = get_version(version_source_path)
src = html_src_path src = html_src_path
dst = html_dst_path dst = output_path
cpu = os.cpu_count() cpu = os.cpu_count()
cmd_line = f'sphinx-build -b html "{src}" "{dst}" -D version="{ver}" {env_opt} -j {cpu}' cmd_line = f'sphinx-build -M html "{src}" "{dst}" -D version="{ver}" {env_opt} -j {cpu}'
t2 = datetime.now()
print('Current time: ' + str(t2))
cmd(cmd_line) cmd(cmd_line)
t3 = datetime.now() t2 = datetime.now()
print('Current time: ' + str(t3)) print('HTML gen time : ' + str(t2 - t1))
print('Sphinx run time: ' + str(t3 - t2))
# ---------------------------------------------------------------------
# Cleanup.
# ---------------------------------------------------------------------
if preserve:
print('Temp directory: ', temp_directory)
else:
print('Removing temporary files...', temp_directory)
if os.path.exists(temp_directory):
shutil.rmtree(temp_directory)
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Remove temporary `lv_conf.h` created for this build. # Remove temporary `lv_conf.h` created for this build.
# Do this even when `lv_conf.h` was not generated in case a prior run got interrupted.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
config_builder.cleanup() config_builder.cleanup()
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Indicate results. # Indicate results.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
t4 = datetime.now() t_end = datetime.now()
print('Total run time: ' + str(t4 - t1)) print('Total run time: ' + str(t_end - t0))
print('Output path: ', html_dst_path)
print() print()
print('Note: warnings about `/details/index.rst` and `/intro/index.rst`') print('Note: warnings about `/details/index.rst` and `/intro/index.rst`')
print(' "not being in any toctree" are expected and intentional.') print(' "not being in any toctree" are expected and intentional.')
print() print()
print('Finished.') print('Done.')
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------

View File

@@ -33,6 +33,9 @@ from sphinx.builders.html import StandaloneHTMLBuilder
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
#
# As of 6-Jan-2025, `link_roles` is being commented out because it is being
# replaced by a manually-installed translation link in ./docs/index.rst.
extensions = [ extensions = [
'sphinx_rtd_theme', 'sphinx_rtd_theme',
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
@@ -43,7 +46,7 @@ extensions = [
'lv_example', 'lv_example',
'sphinx_design', 'sphinx_design',
'sphinx_rtd_dark_mode', 'sphinx_rtd_dark_mode',
'link_roles', # 'link_roles',
'sphinxcontrib.mermaid', 'sphinxcontrib.mermaid',
'sphinx_reredirects' 'sphinx_reredirects'
] ]

View File

@@ -1,8 +1,8 @@
.. _reference: .. _reference:
========= =======
Reference Details
========= =======
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@@ -192,7 +192,7 @@ you can set it like this:
.. _animation_direction .. _animation_direction:
Animating in Both Directions Animating in Both Directions
**************************** ****************************

View File

@@ -1,6 +1,17 @@
.. raw:: html
<p style="text-align: right;">
<a class="reference external" href="https://lvgl.100ask.net/master/index.html">
[&#x04E2D;&#x02F42;] Chinese Translation
</a>
</p>
.. _lvgl_landing_page:
=========================================== ===========================================
LVGL: Light and Versatile Graphics Library LVGL: Light and Versatile Graphics Library
=========================================== ===========================================
Create beautiful UIs for any MCU, MPU and display type. Create beautiful UIs for any MCU, MPU and display type.
******************************************************* *******************************************************