]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
binman: Add Sphinx extension to auto-generate entry and bintool docs
authorSimon Glass <sjg@chromium.org>
Wed, 18 Mar 2026 13:23:56 +0000 (07:23 -0600)
committerHeinrich Schuchardt <heinrich.schuchardt@canonical.com>
Fri, 17 Apr 2026 05:12:07 +0000 (07:12 +0200)
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@chromium.org>
doc/.gitignore
doc/conf.py
doc/sphinx/binman_docs.py [new file with mode: 0644]

index 53752db253e3b4b5e3f17633c0f358b6d7cdd166..7eeafcbf9fdadef41249b2992a0b0aa79a84f64e 100644 (file)
@@ -1 +1,3 @@
 output
+develop/package/entries.rst
+develop/package/bintools.rst
index bf60fe14315a32b24dddbd0a7cb91ac53fdf41a3..0d56e4f7c3c634669f97e2e9df450a78b0187776 100644 (file)
@@ -50,7 +50,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 (file)
index 0000000..34d12f1
--- /dev/null
@@ -0,0 +1,207 @@
+# 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}