208 lines
6.2 KiB
Python
208 lines
6.2 KiB
Python
# SPDX-License-Identifier: GPL-2.0+
|
|
# Copyright (c) 2026 Simon Glass <sjg@chromium.org>
|
|
#
|
|
"""Sphinx extension to auto-generate binman entry and bintool documentation.
|
|
|
|
This parses etype and btool source files using the ast module to extract
|
|
class docstrings, avoiding the need to import binman modules (which have
|
|
dependencies like libfdt that may not be available in the doc-build
|
|
environment).
|
|
|
|
The generated files are written to doc/develop/package/ (alongside
|
|
binman.rst) and included via toctree directives. They are .gitignore'd
|
|
since they are always regenerated during the build.
|
|
|
|
To use, add 'binman_docs' to the extensions list in conf.py.
|
|
"""
|
|
|
|
import ast
|
|
import os
|
|
|
|
|
|
def get_entry_docstring(source_file):
|
|
"""Extract the Entry_ class docstring from an etype source file.
|
|
|
|
Some files contain helper classes before the Entry_ class, so we look
|
|
specifically for a class whose name starts with 'Entry_'.
|
|
|
|
Args:
|
|
source_file: Path to the source file
|
|
|
|
Returns:
|
|
The docstring of the Entry_ class, or None
|
|
"""
|
|
with open(source_file) as inf:
|
|
tree = ast.parse(inf.read())
|
|
for node in ast.iter_child_nodes(tree):
|
|
if isinstance(node, ast.ClassDef) and node.name.startswith('Entry_'):
|
|
return ast.get_docstring(node, clean=False)
|
|
return None
|
|
|
|
|
|
def get_bintool_docstring(source_file):
|
|
"""Extract the Bintool class docstring from a btool source file.
|
|
|
|
Args:
|
|
source_file: Path to the source file
|
|
|
|
Returns:
|
|
The docstring of the Bintool class, or None
|
|
"""
|
|
with open(source_file) as inf:
|
|
tree = ast.parse(inf.read())
|
|
for node in ast.iter_child_nodes(tree):
|
|
if isinstance(node, ast.ClassDef) and node.name.startswith('Bintool'):
|
|
return ast.get_docstring(node, clean=False)
|
|
return None
|
|
|
|
|
|
def generate_entry_docs(srcdir):
|
|
"""Generate entries.rst content from etype source files.
|
|
|
|
Args:
|
|
srcdir: Root of the U-Boot source tree
|
|
|
|
Returns:
|
|
String containing RST content
|
|
"""
|
|
etype_dir = os.path.join(srcdir, 'tools', 'binman', 'etype')
|
|
modules = sorted([
|
|
os.path.splitext(f)[0] for f in os.listdir(etype_dir)
|
|
if f.endswith('.py') and not f.startswith('_') and f != '__init__.py'
|
|
])
|
|
|
|
parts = ['''\
|
|
Binman Entry Documentation
|
|
==========================
|
|
|
|
This file describes the entry types supported by binman. These entry types can
|
|
be placed in an image one by one to build up a final firmware image. It is
|
|
fairly easy to create new entry types. Just add a new file to the 'etype'
|
|
directory. You can use the existing entries as examples.
|
|
|
|
Note that some entries are subclasses of others, using and extending their
|
|
features to produce new behaviours.
|
|
|
|
|
|
''']
|
|
|
|
missing = []
|
|
for name in modules:
|
|
source = os.path.join(etype_dir, name + '.py')
|
|
docs = get_entry_docstring(source)
|
|
if docs:
|
|
lines = docs.splitlines()
|
|
first_line = lines[0]
|
|
rest = [line[4:] for line in lines[1:]]
|
|
hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
|
|
|
|
ref_name = 'etype_%s' % name
|
|
parts.append('.. _%s:' % ref_name)
|
|
parts.append('')
|
|
parts.append(hdr)
|
|
parts.append('-' * len(hdr))
|
|
parts.append('\n'.join(rest))
|
|
parts.append('')
|
|
parts.append('')
|
|
else:
|
|
missing.append(name)
|
|
|
|
if missing:
|
|
raise ValueError('Documentation is missing for modules: %s' %
|
|
', '.join(missing))
|
|
|
|
return '\n'.join(parts)
|
|
|
|
|
|
def generate_bintool_docs(srcdir):
|
|
"""Generate bintools.rst content from btool source files.
|
|
|
|
Args:
|
|
srcdir: Root of the U-Boot source tree
|
|
|
|
Returns:
|
|
String containing RST content
|
|
"""
|
|
btool_dir = os.path.join(srcdir, 'tools', 'binman', 'btool')
|
|
fnames = [
|
|
f for f in os.listdir(btool_dir)
|
|
if f.endswith('.py') and not f.startswith('_') and f != '__init__.py'
|
|
]
|
|
|
|
def tool_sort_name(fname):
|
|
name = os.path.splitext(fname)[0]
|
|
if name.startswith('btool_'):
|
|
name = name[6:]
|
|
return name
|
|
|
|
fnames.sort(key=tool_sort_name)
|
|
|
|
parts = ['''\
|
|
.. SPDX-License-Identifier: GPL-2.0+
|
|
|
|
Binman bintool Documentation
|
|
============================
|
|
|
|
This file describes the bintools (binary tools) supported by binman. Bintools
|
|
are binman's name for external executables that it runs to generate or process
|
|
binaries. It is fairly easy to create new bintools. Just add a new file to the
|
|
'btool' directory. You can use existing bintools as examples.
|
|
|
|
|
|
''']
|
|
|
|
missing = []
|
|
for fname in fnames:
|
|
name = os.path.splitext(fname)[0]
|
|
# Strip btool_ prefix used for modules that conflict with Python libs
|
|
if name.startswith('btool_'):
|
|
name = name[6:]
|
|
source = os.path.join(btool_dir, fname)
|
|
docs = get_bintool_docstring(source)
|
|
if docs:
|
|
lines = docs.splitlines()
|
|
first_line = lines[0]
|
|
rest = [line[4:] for line in lines[1:]]
|
|
hdr = 'Bintool: %s: %s' % (name, first_line)
|
|
parts.append(hdr)
|
|
parts.append('-' * len(hdr))
|
|
parts.append('\n'.join(rest))
|
|
parts.append('')
|
|
parts.append('')
|
|
else:
|
|
missing.append(name)
|
|
|
|
if missing:
|
|
raise ValueError('Documentation is missing for modules: %s' %
|
|
', '.join(missing))
|
|
|
|
return '\n'.join(parts)
|
|
|
|
|
|
def generate_docs(app):
|
|
"""Generate binman documentation RST files.
|
|
|
|
Called by Sphinx during the builder-inited event, before any RST files
|
|
are read.
|
|
|
|
Args:
|
|
app: The Sphinx application object
|
|
"""
|
|
srcdir = os.path.abspath(os.path.join(app.srcdir, '..'))
|
|
outdir = os.path.join(app.srcdir, 'develop', 'package')
|
|
|
|
entries_rst = os.path.join(outdir, 'entries.rst')
|
|
content = generate_entry_docs(srcdir)
|
|
with open(entries_rst, 'w') as outf:
|
|
outf.write(content)
|
|
|
|
bintools_rst = os.path.join(outdir, 'bintools.rst')
|
|
content = generate_bintool_docs(srcdir)
|
|
with open(bintools_rst, 'w') as outf:
|
|
outf.write(content)
|
|
|
|
|
|
def setup(app):
|
|
app.connect('builder-inited', generate_docs)
|
|
return {'version': '1.0', 'parallel_read_safe': True}
|