diff --git a/.github/workflows/compile_docs.yml b/.github/workflows/compile_docs.yml index 46380b265..404c02bf7 100644 --- a/.github/workflows/compile_docs.yml +++ b/.github/workflows/compile_docs.yml @@ -8,7 +8,7 @@ on: # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency # Ensure that only one commit will be running tests at a time on each PR concurrency: - group: ${{ github.ref }}-${{ github.workflow }} + group: ${{ github.ref }}-${{ github.workflow }} cancel-in-progress: true env: @@ -64,9 +64,9 @@ jobs: - name: Build examples (with cache) run: scripts/build_html_examples.sh - name: Build docs - run: docs/build.py skip_latex + run: docs/build.py html - name: Remove .doctrees - run: rm -rf out_html/.doctrees + run: rm -rf docs/output/html/.doctrees - name: Retrieve version run: | echo "::set-output name=VERSION_NAME::$(scripts/find_version.sh)" @@ -77,7 +77,7 @@ jobs: token: ${{ secrets.LVGL_BOT_TOKEN }} repository-name: lvgl/docs 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 }} git-config-name: lvgl-bot git-config-email: lvgl-bot@users.noreply.github.com @@ -89,7 +89,7 @@ jobs: token: ${{ secrets.LVGL_BOT_TOKEN }} repository-name: lvgl/docs 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 git-config-name: lvgl-bot git-config-email: lvgl-bot@users.noreply.github.com diff --git a/.gitignore b/.gitignore index 47d79044d..cbf758023 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,13 @@ scripts/built_in_font/lv_font_* docs/doxygen_html docs/xml docs/examples.md -docs/out_latex +docs/tmp/ +docs/output/ docs/_static/built_lv_examples docs/LVGL.pdf docs/env -/docs/examples.rst -/docs/API/ -out_html +docs/examples.rst +docs/API/ __pycache__ /emscripten_builder test_screenshot_error.h diff --git a/docs/Doxyfile b/docs/Doxyfile index f9db29cae..1eaca9456 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -733,14 +733,14 @@ QUIET = YES # Tip: Turn warnings on while writing the documentation. # The default value is: YES. -WARNINGS = NO +WARNINGS = YES # 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 # will automatically be disabled. # 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 # potential errors in the documentation, such as not documenting some parameters @@ -748,7 +748,7 @@ WARN_IF_UNDOCUMENTED = NO # markup commands wrongly. # 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 # 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 # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = doxygen_warnings.txt #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -790,7 +790,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = *#*#SRC#*#* +INPUT = <> # 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 @@ -850,8 +850,15 @@ 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/thorvg/rapidjson/* \ - */libs/thorvg/tvg* +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 # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1203,7 +1210,7 @@ HTML_COLORSTYLE_GAMMA = 80 # The default value is: NO. # 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 # 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. # 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 @@ -2061,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="<>" # 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 diff --git a/docs/build.py b/docs/build.py index 173e97d10..89501c242 100755 --- a/docs/build.py +++ b/docs/build.py @@ -1,197 +1,304 @@ #!/usr/bin/env python3 -""" Generate LVGL documentation using Doxygen and Sphinx. +""" build.py -- Generate LVGL documentation using Doxygen and Sphinx + Breathe. -The first version of this file (Apr 2021) discovered the name of -the current branch (e.g. 'master', 'release/v8.4', etc.) to support -different versions of the documentation by establishing the base URL -(used in `conf.py` and in [Edit on GitHub] links), and then ran: +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 -- Doxygen (to generate LVGL API XML), then -- Sphinx + Build Arguments and Clean Arguments can be used one at a time + or be freely mixed and combined. -to generate the LVGL document tree. Internally, Sphinx uses `breathe` -(a Sphinx extension) to provide a bridge between Doxygen XML output and -Sphinx documentation. It also supported a command-line option `clean` -to remove generated files before starting (eliminates orphan files, -for docs that have moved or changed). +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. -Since then its duties have grown to include: + 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. -- Using environment variables to convey branch names to several more - places where they are used in the docs-generating process (instead - of re-writing writing `conf.py` and a `header.rst` each time docs - were generated). These are documented where they generated below. + 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. -- Supporting additional command-line options. + Caution: -- Generating a temporary `./docs/lv_conf.h` for Doxygen to use - (config_builder.py). + 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. -- Supporting multiple execution platforms (which then required tokenizing - Doxygen's INPUT path in `Doxyfile` and re-writing portions that used - `sed` to generate input or modify files). + A `sphinx-build` will do a full doc rebuild any time: -- Adding translation and API links (requiring generating docs in a - temporary directory so that the links could be programmatically - added to each document before Sphinx was run). + - 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). -- Generating EXAMPLES page + sub-examples where applicable to individual - documents, e.g. to widget-, style-, layout-pages, etc. + Typical run time: -- Building PDF via latex (when working). + 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. -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. + 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`. -Normal Usage ------------- -This is the way this script is used for normal (full) docs generation. + temp [ skip_api ] + Generate temporary directory contents (ready to build output formats). + If they already exist, they are removed and re-generated. - $ python build.py skip_latex + 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). -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. + clean + Remove all generated files. -2. $ python build.py skip_latex preserve fixed_tmp_dir + clean_temp + Remove temporary directory. -This takes typically ~22 minutes. + clean_html + Remove HTML output directory. + clean_latex + Remove Latex output directory. -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. + Unrecognized arguments print error message, usage note, and exit with status 1. - $ python build.py skip_latex docs_dev update +Python Package Requirements +--------------------------- + The list of Python package requirements are in `requirements.txt`. -Generation time depends on the number of `.rst` files that -have been updated: + Install them by: -+--------------+------------+---------------------------------+ -| 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 | -+--------------+------------+---------------------------------+ + $ pip install -r requirements.txt +History +------- + The first version of this file (Apr 2021) discovered the name of + the current branch (e.g. 'master', 'release/v8.4', etc.) to support + different versions of the documentation by establishing the base URL + (used in `conf.py` and in [Edit on GitHub] links), and then ran: -Sphinx Doc-Regeneration Criteria --------------------------------- -Sphinx uses the following to determine what documents get updated: + - Doxygen (to generate LVGL API XML), then + - Sphinx -- source-doc modification date - - Change the modification date and `sphinx-build` will re-build it. + to generate the LVGL document tree. Internally, Sphinx uses `breathe` + (a Sphinx extension) to provide a bridge between Doxygen XML output and + Sphinx documentation. It also supported a command-line option `clean` + to remove generated files before starting (eliminates orphan files, + for docs that have moved or changed). -- full (absolute) path to the source document, including its file name - - Change the path or filename and `sphinx-build` will re-build it. + Since then its duties have grown to include: -- whether the -E option is on the `sphinx-build` command line - - -E forces `sphinx-build` to do a full re-build. + - Using environment variables to convey branch names to several more + places where they are used in the docs-generating process (instead + of re-writing writing `conf.py` and a `header.rst` each time docs + were generated). These are documented where they generated below. + - Supporting additional command-line options. + + - Generating a temporary `./docs/lv_conf.h` for Doxygen to use + (via config_builder.py). + + - Supporting multiple execution platforms (which then required tokenizing + Doxygen's INPUT path in `Doxyfile` and re-writing portions that used + `sed` to generate input or modify files). + + - Adding translation and API links (requiring generating docs in a + temporary directory so that the links could be programmatically + 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 + documents, e.g. to widget-, style-, layout-pages, etc.. + + - Building PDF via latex (when working). + + - Shifting doc-generation paradigm to behave more like `make`. -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 # 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. +# -- @kdschlosser # **************************************************************************** +# Python Library +import sys +import os +import re +import subprocess +import shutil +import dirsync +from datetime import datetime +# LVGL Custom +import example_list +import doc_builder +import config_builder +import add_translation + +# ------------------------------------------------------------------------- +# Configuration +# ------------------------------------------------------------------------- +# These are relative paths from the ./docs/ directory. +cfg_temp_dir = 'tmp' +cfg_output_dir = 'output' + +# Filename generated in `cfg_latex_output_dir` and copied to `cfg_pdf_output_dir`. +cfg_pdf_filename = 'LVGL.pdf' + + +# ------------------------------------------------------------------------- +# Print usage note. +# ------------------------------------------------------------------------- +def print_usage_note(): + print('Usage:') + print(' $ python build.py [optional_arg ...]') + print() + print(' where `optional_arg` can be any of these:') + print(' html [ skip_api ] [ fresh_env ]') + print(' latex [ skip_api ] [ fresh_env ]') + print(' temp [ skip_api ]') + print(' clean') + print(' clean_temp') + print(' clean_html') + print(' clean_latex') + print(' help') + + +# ------------------------------------------------------------------------- +# Remove directory `tgt_dir`. +# ------------------------------------------------------------------------- +def remove_dir(tgt_dir): + if os.path.isdir(tgt_dir): + print(f'Removing {tgt_dir}...') + shutil.rmtree(tgt_dir) + else: + print(f'{tgt_dir} already removed...') + + +# ------------------------------------------------------------------------- +# Run external command and abort build on error. +# ------------------------------------------------------------------------- +def cmd(s, start_dir=None, exit_on_error=True): + if start_dir is None: + start_dir = os.getcwd() + + saved_dir = os.getcwd() + os.chdir(start_dir) + print("") + print(s) + print("-------------------------------------") + result = os.system(s) + os.chdir(saved_dir) + + if result != 0 and exit_on_error: + print("Exiting build due to previous error.") + 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(): - # Python Library Imports - import sys - import os - import re - import subprocess - import shutil - import tempfile - import dirsync - from datetime import datetime - - # LVGL Custom Imports - import example_list as ex - import doc_builder - import config_builder - import add_translation - - # --------------------------------------------------------------------- - # Start. - # --------------------------------------------------------------------- - t1 = datetime.now() - print('Current time: ' + str(t1)) - # --------------------------------------------------------------------- # Process args. # @@ -201,72 +308,61 @@ def run(): # This saves a huge amount of time during long document projects. # --------------------------------------------------------------------- # Set defaults. - clean = False - skip_latex = False + 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 = True - preserve = False - fixed_tmp_dir = False - skip_trans = False - no_copy = False - docs_dev = False - update = 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 == "clean": - clean = True - elif arg == "skip_latex": - skip_latex = True + 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 == 'no_fresh_env': - fresh_sphinx_env = False - elif arg == 'preserve': - preserve = True - elif arg == 'fixed_tmp_dir': - fixed_tmp_dir = True - elif arg == 'skip_trans': - skip_trans = True - elif arg == 'no_copy': - no_copy = True - elif arg == 'docs_dev': - docs_dev = True - elif arg == 'update': - update = 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) - # 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 - # 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 - # 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. + # Start. # --------------------------------------------------------------------- - 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}]') + t0 = datetime.now() # --------------------------------------------------------------------- # Set up paths. @@ -275,11 +371,30 @@ def run(): 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') + 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 - html_dst_path = os.path.join(project_path, 'out_html') + print(f'Temporary directory: [{temp_directory}]') # --------------------------------------------------------------------- # Change to script directory for consistency. @@ -287,23 +402,30 @@ def run(): os.chdir(base_path) # --------------------------------------------------------------------- - # Provide a way to run an external command and abort build on error. + # Clean? If so, clean (like `make clean`), but do not exit. # --------------------------------------------------------------------- - def cmd(s, start_dir=None): - if start_dir is None: - start_dir = os.getcwd() + 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) - saved_dir = os.getcwd() - os.chdir(start_dir) - print("") - print(s) - print("-------------------------------------") - result = os.system(s) - os.chdir(saved_dir) + if some_cleaning_to_be_done: + print("****************") + print("Cleaning...") + print("****************") - if result != 0: - print("Exiting build due to previous error.") - sys.exit(result) + 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: @@ -361,107 +483,74 @@ def run(): os.environ['LVGL_URLPATH'] = 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 - # 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 - if no_copy and fixed_tmp_dir and temp_dir_contents_exists(): - if update: - exclude_list = ['lv_conf.h'] - options = { - 'verbose': True, - 'create': True, - 'exclude': exclude_list - } - dirsync.sync('.', temp_directory, 'update', **options) - else: - print("Skipping copying ./docs/ directory as requested.") - else: - shutil.copytree('.', temp_directory, dirs_exist_ok=True) - shutil.copytree(examples_path, os.path.join(temp_directory, 'examples'), dirs_exist_ok=True) - doc_files_copied = True + # dirsync `exclude_list` = list of regex patterns to exclude. + exclude_list = [r'lv_conf\.h', r'^tmp.*', r'^output.*'] - # --------------------------------------------------------------------- - # Replace tokens in Doxyfile in 'temp_directory' with data from this run. - # --------------------------------------------------------------------- - if doc_files_copied: + 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 = { + 'verbose': True, # Report files copied. + '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 + } + # 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('.', temp_directory, 'sync', **options) + dirsync.sync(examples_path, os.path.join(temp_directory, 'examples'), 'sync', **options) + elif build_temp or build_html or build_latex: + # We are having to create the temp_directory contents by copying. + 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. with open(os.path.join(temp_directory, 'Doxyfile'), 'rb') as f: data = f.read().decode('utf-8') - 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('<>', os.path.join(base_path, 'lv_conf.h')) + data = data.replace('<>', '"{0}"'.format(lvgl_src_path)) with open(os.path.join(temp_directory, 'Doxyfile'), 'wb') as f: f.write(data.encode('utf-8')) @@ -471,13 +560,15 @@ def run(): # in individual documents where applicable. # ----------------------------------------------------------------- print("Generating examples...") - ex.exec(temp_directory) + example_list.exec(temp_directory) # ----------------------------------------------------------------- # 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: - print("Skipping translation links as requested.") + if True: + print("Skipping adding translation links.") else: print("Adding translation links...") add_translation.exec(temp_directory) @@ -491,9 +582,11 @@ def run(): print("Running Doxygen...") cmd('doxygen Doxyfile', temp_directory) + print("API page and link processing...") 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( project_path, temp_directory, @@ -518,18 +611,26 @@ def run(): os.path.join(temp_directory, 'details', 'integration', 'renderers'), os.path.join(temp_directory, 'details', 'libs'), 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', 'widgets') ) - print('Reading Doxygen output...') + t2 = datetime.now() + print('Example/API processing run time: ' + str(t2 - t1)) # --------------------------------------------------------------------- - # BUILD PDF + # Build PDF # --------------------------------------------------------------------- - if skip_latex: - print("Skipping latex build as requested.") + if not build_latex: + print("Skipping Latex build.") else: + t1 = datetime.now() + print("****************") + print("Building Latex output...") + print("****************") + # Remove PDF link so PDF does not have a link to itself. index_path = os.path.join(temp_directory, 'index.rst') @@ -548,14 +649,17 @@ def run(): # PDF download link in the PDF # cmd("cp -f " + lang +"/latex/LVGL.pdf LVGL.pdf | true") src = temp_directory - dst = latex_output_path + dst = output_path 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) # Generate PDF. + print("****************") + print("Building PDF...") + print("****************") 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 # it available for the HTML build. @@ -567,95 +671,65 @@ def run(): with open(index_path, 'wb') as f: 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. - # Updated to be resilient to changes in `lv_version.h` compliant with C macro syntax. - 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: - 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}' - - # 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 - # run Sphinx from script as - # from sphinx.cmd.build import main as sphinx_build - # sphinx_args = [...] - # sphinx_build(sphinx_args) - # because it takes ~10X longer to run than `sphinx_build` executable. - # Literally > 3 hours. - - # '-E' option forces Sphinx to rebuild its environment so all docs are - # fully regenerated, even if not changed. - # Note: Sphinx runs in ./docs/, but uses `temp_directory` for input. - if fresh_sphinx_env: - print("Regenerating all files...") - env_opt = '-E' + if not build_html: + print("Skipping HTML build.") else: - print("Regenerating only updated files...") - env_opt = '' + t1 = datetime.now() + print("****************") + print("Building HTML output...") + print("****************") - ver = get_version() - src = html_src_path - dst = html_dst_path - cpu = os.cpu_count() - cmd_line = f'sphinx-build -b html "{src}" "{dst}" -D version="{ver}" {env_opt} -j {cpu}' - t2 = datetime.now() - print('Current time: ' + str(t2)) - cmd(cmd_line) - t3 = datetime.now() - print('Current time: ' + str(t3)) - print('Sphinx run time: ' + str(t3 - t2)) + # 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 + # run Sphinx from script as + # from sphinx.cmd.build import main as sphinx_build + # sphinx_args = [...] + # sphinx_build(sphinx_args) + # because it takes ~10X longer to run than `sphinx_build` executable. + # Literally > 3 hours. - # --------------------------------------------------------------------- - # 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) + # '-E' option forces Sphinx to rebuild its environment so all docs are + # fully regenerated, even if not changed. + # Note: Sphinx runs in ./docs/, but uses `temp_directory` for input. + if fresh_sphinx_env: + print("Regenerating all files...") + env_opt = '-E' + else: + print("Regenerating only updated files...") + env_opt = '' + + ver = get_version(version_source_path) + src = html_src_path + dst = output_path + cpu = os.cpu_count() + 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)) # --------------------------------------------------------------------- # 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() # --------------------------------------------------------------------- # Indicate results. # --------------------------------------------------------------------- - t4 = datetime.now() - print('Total run time: ' + str(t4 - t1)) - print('Output path: ', html_dst_path) + t_end = datetime.now() + print('Total run time: ' + str(t_end - t0)) print() print('Note: warnings about `/details/index.rst` and `/intro/index.rst`') print(' "not being in any toctree" are expected and intentional.') print() - print('Finished.') + print('Done.') # ------------------------------------------------------------------------- diff --git a/docs/conf.py b/docs/conf.py index ea2b50960..d314ff98f 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,6 +33,9 @@ from sphinx.builders.html import StandaloneHTMLBuilder # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # 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 = [ 'sphinx_rtd_theme', 'sphinx.ext.autodoc', @@ -43,7 +46,7 @@ extensions = [ 'lv_example', 'sphinx_design', 'sphinx_rtd_dark_mode', - 'link_roles', + # 'link_roles', 'sphinxcontrib.mermaid', 'sphinx_reredirects' ] diff --git a/docs/details/index.rst b/docs/details/index.rst index 3f450aa6a..ceb542c80 100644 --- a/docs/details/index.rst +++ b/docs/details/index.rst @@ -1,8 +1,8 @@ .. _reference: -========= -Reference -========= +======= +Details +======= .. toctree:: :maxdepth: 2 diff --git a/docs/details/main-components/animation.rst b/docs/details/main-components/animation.rst index 2d538f464..44fd4122e 100644 --- a/docs/details/main-components/animation.rst +++ b/docs/details/main-components/animation.rst @@ -192,7 +192,7 @@ you can set it like this: -.. _animation_direction +.. _animation_direction: Animating in Both Directions **************************** diff --git a/docs/index.rst b/docs/index.rst index 9caf5f8aa..8e1454935 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,17 @@ +.. raw:: html + +

+ + [中⽂] Chinese Translation + +

+ +.. _lvgl_landing_page: + =========================================== LVGL: Light and Versatile Graphics Library =========================================== + Create beautiful UIs for any MCU, MPU and display type. ******************************************************* diff --git a/examples/widgets/canvas/index.rst b/examples/widgets/canvas/index.rst index 13e93ab95..ac345b7ed 100644 --- a/examples/widgets/canvas/index.rst +++ b/examples/widgets/canvas/index.rst @@ -56,10 +56,10 @@ Draw Fancy Letter Effects ------------------------- .. lv_example:: widgets/canvas/lv_example_canvas_9 -:language: c + :language: c Draw Fancy Letter Effects 2 --------------------------- .. lv_example:: widgets/canvas/lv_example_canvas_10 -:language: c + :language: c