[PATCH 1/2] binman: Add Sphinx extension to auto-generate entry and bintool docs
Simon Glass
sjg at chromium.org
Wed Mar 18 14:23:56 CET 2026
Currently entries.rst and bintools.rst are generated manually by running
'binman entry-docs' and 'binman bintool-docs', then committed to the
repo. This means the docs can drift out of date when docstrings are
updated but the RST files are not regenerated.
Add a Sphinx extension (binman_docs) that provides two custom
directives:
.. binman-entry-docs::
.. binman-bintool-docs::
These parse the etype and btool source files using the ast module to
extract class docstrings, then insert the documentation directly into
the document tree. This avoids the need to import binman modules (which
have dependencies like libfdt that are not available in the ReadTheDocs
build environment) and avoids writing any intermediate files.
Signed-off-by: Simon Glass <sjg at chromium.org>
---
doc/.gitignore | 2 +
doc/conf.py | 3 +-
doc/sphinx/binman_docs.py | 207 ++++++++++++++++++++++++++++++++++++++
3 files changed, 211 insertions(+), 1 deletion(-)
create mode 100644 doc/sphinx/binman_docs.py
diff --git a/doc/.gitignore b/doc/.gitignore
index 53752db253e..7eeafcbf9fd 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -1 +1,3 @@
output
+develop/package/entries.rst
+develop/package/bintools.rst
diff --git a/doc/conf.py b/doc/conf.py
index 84d028feda8..dfe053b169e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,7 +48,8 @@ extensions = ['kerneldoc', 'rstFlatTable', 'kernel_include',
'kfigure', 'sphinx.ext.ifconfig', # 'automarkup',
'maintainers_include', 'sphinx.ext.autosectionlabel',
'kernel_abi', 'kernel_feat', 'sphinx-prompt',
- 'sphinx_reredirects', 'sphinx.ext.autodoc' ]
+ 'sphinx_reredirects', 'sphinx.ext.autodoc',
+ 'binman_docs' ]
#
# cdomain is badly broken in Sphinx 3+. Leaving it out generates *most*
diff --git a/doc/sphinx/binman_docs.py b/doc/sphinx/binman_docs.py
new file mode 100644
index 00000000000..34d12f1b7d5
--- /dev/null
+++ b/doc/sphinx/binman_docs.py
@@ -0,0 +1,207 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2026 Simon Glass <sjg at 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}
--
2.43.0
More information about the U-Boot
mailing list