[doc] Completely replace docgen with hugo
This change completely replaces docgen and replaces or removes
docgen-specific markdown in documentation. It also does the following:
* Updates all local links to use hugo relative references so that a
broken link is a broken build.
* Uses upstream wavedrom, which breaks at least one page that depends
on local modifications.
* Renames most hw/ip/**/ip_name.doc and dv_plan documents for a more
aesthetic document tree layout.
* Moves some doc/ pages into their own page bundle.
* Updates util/build_docs.py to pre-generate registers, hwcfg, and
dashboard fragments and invoke hugo.
diff --git a/util/build_docs.py b/util/build_docs.py
index 00389b0..cea7709 100755
--- a/util/build_docs.py
+++ b/util/build_docs.py
@@ -3,130 +3,154 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
-# pip3 install --user livereload
# Usage:
# run './build_docs.py' to generate the documentation and keep it updated
-# open 'http://localhost:5500/' to check live update (this opens the top
+# open 'http://localhost:1313/' to check live update (this opens the top
# level index page). you can also directly access a specific document by
-# accessing 'http://localhost:5500/path/to/doc.html',
-# e.g. http://localhost:5500/hw/ip/uart/doc/uart.html
+# accessing 'http://localhost:1313/path/to/doc',
+# e.g. http://localhost:1313/hw/ip/uart/doc
import argparse
+import io
import logging
import os
import shutil
+import subprocess
from pathlib import Path
-import livereload
+import hjson
-import docgen.generate
+import dashboard.gen_dashboard_entry as gen_dashboard_entry
+import reggen.gen_cfg_html as gen_cfg_html
+import reggen.gen_html as gen_html
+import reggen.validate as validate
+import testplanner.testplan_utils as testplan_utils
USAGE = """
build_docs [options]
"""
-MARKDOWN_EXTENSIONS = [
- '.md',
- '.mkd',
-]
-STATIC_ASSET_EXTENSIONS = [
- '.svg',
- '.png',
- '.jpg',
- '.css',
-]
-HJSON_EXTENSIONS = ['.hjson']
-
# Configurations
# TODO: Move to config.yaml
SRCTREE_TOP = Path(__file__).parent.joinpath('..').resolve()
config = {
# Toplevel source directory
- "topdir": SRCTREE_TOP,
+ "topdir":
+ SRCTREE_TOP,
- # A list of directories containing documentation within topdir. To ensure
- # the top-level sitemap doesn't have broken links, this should be kept
- # in-sync with the doctree tag in sitemap.md.
- "incdirs": ['./doc', './hw', './sw', './util'],
+ # Pre-generate register and hwcfg fragments from these files.
+ "hardware_definitions": [
+ "hw/ip/aes/data/aes.hjson",
+ "hw/ip/alert_handler/data/alert_handler.hjson",
+ "hw/ip/flash_ctrl/data/flash_ctrl.hjson",
+ "hw/ip/gpio/data/gpio.hjson",
+ "hw/ip/hmac/data/hmac.hjson",
+ "hw/ip/i2c/data/i2c.hjson",
+ "hw/ip/padctrl/data/padctrl.hjson",
+ "hw/ip/pinmux/data/pinmux.hjson",
+ "hw/ip/rv_plic/data/rv_plic.hjson",
+ "hw/ip/rv_timer/data/rv_timer.hjson",
+ "hw/ip/spi_device/data/spi_device.hjson",
+ "hw/ip/uart/data/uart.hjson",
+ "hw/ip/usbdev/data/usbdev.hjson",
+ "hw/ip/usbuart/data/usbuart.hjson",
+ ],
+
+ # Pre-generate dashboard fragments from these directories.
+ "dashboard_definitions": [
+ "hw/ip",
+ ],
+
+ # Pre-generate testplan fragments from these files.
+ "testplan_definitions": [
+ "hw/ip/gpio/data/gpio_testplan.hjson",
+ "hw/ip/hmac/data/hmac_testplan.hjson",
+ "hw/ip/i2c/data/i2c_testplan.hjson",
+ "hw/ip/rv_timer/data/rv_timer_testplan.hjson",
+ "hw/ip/uart/data/uart_testplan.hjson",
+ "util/testplanner/examples/foo_testplan.hjson",
+ ],
# Output directory for documents
- "outdir": SRCTREE_TOP.joinpath('opentitan-docs'),
- "verbose": False,
+ "outdir":
+ SRCTREE_TOP.joinpath('build', 'docs'),
+ "outdir-generated":
+ SRCTREE_TOP.joinpath('build', 'docs-generated'),
+ "verbose":
+ False,
}
-def get_doc_files(extensions=MARKDOWN_EXTENSIONS + STATIC_ASSET_EXTENSIONS):
- """Get the absolute path of files containing documentation
- """
- file_list = []
- # doc files on toplevel
- for ext in extensions:
- file_list += config["topdir"].glob('*' + ext)
- # doc files in include dirs
- for incdir in config['incdirs']:
- for ext in extensions:
- file_list += config["topdir"].joinpath(incdir).rglob('*' + ext)
- return file_list
+def generate_dashboards():
+ for dashboard in config["dashboard_definitions"]:
+ hjson_paths = []
+ hjson_paths.extend(
+ sorted(SRCTREE_TOP.joinpath(dashboard).rglob('*.prj.hjson')))
+
+ dashboard_path = config["outdir-generated"].joinpath(
+ dashboard, 'dashboard')
+ dashboard_html = open(dashboard_path, mode='w')
+ for hjson_path in hjson_paths:
+ gen_dashboard_entry.gen_dashboard_html(hjson_path, dashboard_html)
+ dashboard_html.close()
-def ensure_dest_dir(dest_pathname):
- os.makedirs(dest_pathname.parent, exist_ok=True)
+def generate_hardware_blocks():
+ for hardware in config["hardware_definitions"]:
+ hardware_file = open(SRCTREE_TOP.joinpath(hardware))
+ regs = hjson.load(hardware_file,
+ use_decimal=True,
+ object_pairs_hook=validate.checking_dict)
+ if validate.validate(regs) == 0:
+ logging.info("Parsed %s" % (hardware))
+ else:
+ logging.fatal("Failed to parse %s" % (hardware))
+
+ base_path = config["outdir-generated"].joinpath(hardware)
+ base_path.parent.mkdir(parents=True, exist_ok=True)
+
+ regs_html = open(base_path.parent.joinpath(base_path.name +
+ '.registers'),
+ mode='w')
+ gen_html.gen_html(regs, regs_html)
+ regs_html.close()
+
+ hwcfg_html = open(base_path.parent.joinpath(base_path.name + '.hwcfg'),
+ mode='w')
+ gen_cfg_html.gen_cfg_html(regs, hwcfg_html)
+ hwcfg_html.close()
-def path_src_to_dest(src_pathname, dest_filename_suffix=None):
- """Get the destination pathname from a source pathname
- """
- src_relpath = Path(src_pathname).relative_to(config["topdir"])
- dest_pathname = Path(config["outdir"]).joinpath(src_relpath)
- if dest_filename_suffix:
- dest_pathname = dest_pathname.with_suffix(dest_filename_suffix)
- return dest_pathname
+def generate_testplans():
+ for testplan in config["testplan_definitions"]:
+ plan = testplan_utils.parse_testplan(SRCTREE_TOP.joinpath(testplan))
+
+ plan_path = config["outdir-generated"].joinpath(testplan + '.testplan')
+ plan_path.parent.mkdir(parents=True, exist_ok=True)
+
+ testplan_html = open(plan_path, mode='w')
+ testplan_utils.gen_html_testplan_table(plan, testplan_html)
+ testplan_html.close()
-def process_file_markdown(src_pathname):
- """Process a markdown file and copy it to the destination
- """
- dest_pathname = path_src_to_dest(src_pathname, '.html')
-
- logging.info("Processing Markdown file: %s -> %s" %
- (str(src_pathname), str(dest_pathname)))
-
- ensure_dest_dir(dest_pathname)
-
- with open(dest_pathname, 'w', encoding='UTF-8') as f:
- outstr = docgen.generate.generate_doc(str(src_pathname),
- verbose=config['verbose'],
- inlinecss=True,
- inlinewave=True,
- asdiv=False)
- f.write(outstr)
-
- return dest_pathname
-
-
-def process_file_copytodest(src_pathname):
- """Copy a file to the destination directory with no further processing
- """
- dest_pathname = path_src_to_dest(src_pathname)
-
- logging.info("Copying %s -> %s" % (str(src_pathname), str(dest_pathname)))
-
- ensure_dest_dir(dest_pathname)
- shutil.copy(src_pathname, dest_pathname)
-
-
-def process_all_files():
- """Process all files
-
- The specific processing action depends on the file type.
- """
- src_files = get_doc_files()
-
- for src_pathname in src_files:
- if src_pathname.suffix in MARKDOWN_EXTENSIONS:
- process_file_markdown(src_pathname)
- elif src_pathname.suffix in STATIC_ASSET_EXTENSIONS:
- process_file_copytodest(src_pathname)
+def invoke_hugo(preview):
+ site_docs = SRCTREE_TOP.joinpath('site', 'docs')
+ config_file = str(site_docs.joinpath('config.toml'))
+ layout_dir = str(site_docs.joinpath('layouts'))
+ args = [
+ "hugo",
+ "--config",
+ config_file,
+ "--destination",
+ str(config["outdir"]),
+ "--contentDir",
+ str(SRCTREE_TOP),
+ "--layoutDir",
+ layout_dir,
+ ]
+ if preview:
+ args += ["server"]
+ subprocess.run(args, check=True, cwd=SRCTREE_TOP)
def main():
@@ -144,21 +168,14 @@
help="""starts a local server with live reload (updates triggered upon
changes in the documentation files). this feature is intended
to preview the documentation locally.""")
+ parser.add_argument('--hugo', help="""TODO""")
args = parser.parse_args()
- # Initial processing of all files
- process_all_files()
-
- if args.preview:
- # Setup livereload watcher
- server = livereload.Server()
- exts_to_watch = MARKDOWN_EXTENSIONS + \
- STATIC_ASSET_EXTENSIONS + \
- HJSON_EXTENSIONS
- for src_pathname in get_doc_files(exts_to_watch):
- server.watch(str(src_pathname), process_all_files)
- server.serve(root=config['topdir'].joinpath(config['outdir']))
+ generate_hardware_blocks()
+ generate_dashboards()
+ generate_testplans()
+ invoke_hugo(args.preview)
if __name__ == "__main__":