summaryrefslogtreecommitdiff
path: root/doc/sphinx/binman_docs.py
blob: 34d12f1b7d5bacc4100b5d6be6f128a668d47f75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2026 Simon Glass <[email protected]>
#
"""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}