# SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2026 Simon Glass # """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}