blob: 7e4d065112d4b7d56042eec08b586f48ab17d601 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
#
# Usage:
# run './build_docs.py' to generate the documentation and keep it updated
# 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:1313/path/to/doc',
# e.g. http://localhost:1313/hw/ip/uart/doc
import argparse
import io
import logging
import os
import platform
import re
import shutil
import subprocess
from pathlib import Path
import hjson
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 reggen.gen_selfdoc as reggen_selfdoc
import testplanner.testplan_utils as testplan_utils
import tlgen
USAGE = """
build_docs [options]
"""
# Version of hugo extended to be used to build the docs
HUGO_EXTENDED_VERSION = "0.59.0"
# Configurations
# TODO: Move to config.yaml
SRCTREE_TOP = Path(__file__).parent.joinpath('..').resolve()
config = {
# Toplevel source directory
"topdir":
SRCTREE_TOP,
# 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/entropy_src/data/entropy_src.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/nmi_gen/data/nmi_gen.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/aes/data/aes_testplan.hjson",
"hw/ip/alert_handler/data/alert_handler_testplan.hjson",
"hw/ip/gpio/data/gpio_testplan.hjson",
"hw/ip/hmac/data/hmac_testplan.hjson",
"hw/ip/i2c/data/i2c_testplan.hjson",
"hw/ip/rv_plic/data/rv_plic_fpv_testplan.hjson",
"hw/ip/rv_timer/data/rv_timer_testplan.hjson",
"hw/ip/spi_device/data/spi_device_testplan.hjson",
"hw/ip/uart/data/uart_testplan.hjson",
"hw/ip/usbdev/data/usbdev_testplan.hjson",
"hw/ip/tlul/data/tlul_testplan.hjson",
"hw/top_earlgrey/data/standalone_sw_testplan.hjson",
"util/testplanner/examples/foo_testplan.hjson",
],
# Pre-generated utility selfdoc
"selfdoc_tools": ["tlgen", "reggen"],
# Output directory for documents
"outdir":
SRCTREE_TOP.joinpath('build', 'docs'),
"outdir-generated":
SRCTREE_TOP.joinpath('build', 'docs-generated'),
"verbose":
False,
}
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(str(dashboard_path), mode='w')
for hjson_path in hjson_paths:
gen_dashboard_entry.gen_dashboard_html(hjson_path, dashboard_html)
dashboard_html.close()
def generate_hardware_blocks():
for hardware in config["hardware_definitions"]:
hardware_file = open(str(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(str(base_path.parent.joinpath(base_path.name +
'.registers')),
mode='w')
gen_html.gen_html(regs, regs_html)
regs_html.close()
hwcfg_html = open(str(base_path.parent.joinpath(base_path.name + '.hwcfg')),
mode='w')
gen_cfg_html.gen_cfg_html(regs, hwcfg_html)
hwcfg_html.close()
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(str(plan_path), mode='w')
testplan_utils.gen_html_testplan_table(plan, testplan_html)
testplan_html.close()
def generate_selfdocs():
"""Generate documents for the tools in `util/` if `--doc` option exists.
Each tool creates selfdoc differently. Manually invoked.
"""
for tool in config["selfdoc_tools"]:
selfdoc_path = config["outdir-generated"].joinpath(tool + '.selfdoc')
selfdoc_path.parent.mkdir(parents=True, exist_ok=True)
with open(str(selfdoc_path), mode='w') as fout:
if tool == "reggen":
reggen_selfdoc.document(fout)
elif tool == "tlgen":
fout.write(tlgen.selfdoc(heading=3, cmd='tlgen.py --doc'))
def is_hugo_extended():
args = ["hugo", "version"]
process = subprocess.run(args,
universal_newlines=True,
stdout=subprocess.PIPE,
check=True,
cwd=str(SRCTREE_TOP))
# Hugo version string example:
# Hugo Static Site Generator v0.59.0-1DD0C69C/extended linux/amd64 BuildDate: 2019-10-21T09:45:38Z
return bool(re.search("v\d+\.\d+\.\d+-.*/extended", process.stdout))
def install_hugo(install_dir):
"""Download and "install" hugo into |install_dir|
install_dir is created if it doesn't exist yet.
Limitations:
Currently only 64 bit Linux is supported."""
# TODO: Support more configurations
if platform.system() != 'Linux' or platform.machine() != 'x86_64':
logging.fatal(
"Auto-install of hugo only supported for 64 bit Linux "
"currently. Manually install hugo and re-run this script.")
return False
download_url = 'https://github.com/gohugoio/hugo/releases/download/v{version}/hugo_extended_{version}_Linux-64bit.tar.gz'.format(
version=HUGO_EXTENDED_VERSION)
install_dir.mkdir(exist_ok=True, parents=True)
hugo_bin_path = install_dir / 'hugo'
# TODO: Investigate the use of Python builtins for downloading. Extracting
# the archive will probably will be a call to tar.
cmd = 'curl -sL {download_url} | tar -xzO --overwrite hugo > {hugo_bin_file}'.format(
hugo_bin_file=str(hugo_bin_path), download_url=download_url)
logging.info("Calling %s to download hugo.", cmd)
subprocess.run(cmd, shell=True, check=True, cwd=str(SRCTREE_TOP))
hugo_bin_path.chmod(0o755)
return True
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=str(SRCTREE_TOP))
def main():
logging.basicConfig(level=logging.INFO,
format="%(asctime)s - %(message)s",
datefmt="%Y-%m-%d %H:%M")
parser = argparse.ArgumentParser(
prog="build_docs",
formatter_class=argparse.RawDescriptionHelpFormatter,
usage=USAGE)
parser.add_argument(
'--preview',
action='store_true',
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()
generate_hardware_blocks()
generate_dashboards()
generate_testplans()
generate_selfdocs()
hugo_localinstall_dir = SRCTREE_TOP / 'build' / 'docs-hugo'
os.environ["PATH"] += os.pathsep + str(hugo_localinstall_dir)
try:
if not is_hugo_extended():
logging.info(
"Hugo extended (with SASS support) is required, but only non-extended version found. "
"Installing local copy and re-trying.")
install_hugo(hugo_localinstall_dir)
invoke_hugo(args.preview)
except FileNotFoundError:
logging.info("Hugo not found. Installing local copy and re-trying.")
install_hugo(hugo_localinstall_dir)
invoke_hugo(args.preview)
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()