| # 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} |