xref: /linux/Documentation/sphinx/kernel_abi.py (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
1# -*- coding: utf-8; mode: python -*-
2# coding=utf-8
3# SPDX-License-Identifier: GPL-2.0
4#
5"""
6    kernel-abi
7    ~~~~~~~~~~
8
9    Implementation of the ``kernel-abi`` reST-directive.
10
11    :copyright:  Copyright (C) 2016  Markus Heiser
12    :copyright:  Copyright (C) 2016-2020  Mauro Carvalho Chehab
13    :maintained-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
14    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
15
16    The ``kernel-abi`` (:py:class:`KernelCmd`) directive calls the
17    scripts/get_abi.py script to parse the Kernel ABI files.
18
19    Overview of directive's argument and options.
20
21    .. code-block:: rst
22
23        .. kernel-abi:: <ABI directory location>
24            :debug:
25
26    The argument ``<ABI directory location>`` is required. It contains the
27    location of the ABI files to be parsed.
28
29    ``debug``
30      Inserts a code-block with the *raw* reST. Sometimes it is helpful to see
31      what reST is generated.
32
33"""
34
35import os
36import re
37import sys
38
39from docutils import nodes, statemachine
40from docutils.statemachine import ViewList
41from docutils.parsers.rst import directives, Directive
42from sphinx.util.docutils import switch_source_input
43from sphinx.util import logging
44
45srctree = os.path.abspath(os.environ["srctree"])
46sys.path.insert(0, os.path.join(srctree, "scripts/lib/abi"))
47
48from abi_parser import AbiParser
49
50__version__ = "1.0"
51
52logger = logging.getLogger('kernel_abi')
53path = os.path.join(srctree, "Documentation/ABI")
54
55_kernel_abi = None
56
57def get_kernel_abi():
58    """
59    Initialize kernel_abi global var, if not initialized yet.
60
61    This is needed to avoid warnings during Sphinx module initialization.
62    """
63    global _kernel_abi
64
65    if not _kernel_abi:
66        # Parse ABI symbols only once
67        _kernel_abi = AbiParser(path, logger=logger)
68        _kernel_abi.parse_abi()
69        _kernel_abi.check_issues()
70
71    return _kernel_abi
72
73def setup(app):
74
75    app.add_directive("kernel-abi", KernelCmd)
76    return {
77        "version": __version__,
78        "parallel_read_safe": True,
79        "parallel_write_safe": True
80    }
81
82
83class KernelCmd(Directive):
84    """KernelABI (``kernel-abi``) directive"""
85
86    required_arguments = 1
87    optional_arguments = 3
88    has_content = False
89    final_argument_whitespace = True
90    parser = None
91
92    option_spec = {
93        "debug": directives.flag,
94        "no-symbols": directives.flag,
95        "no-files":  directives.flag,
96    }
97
98    def run(self):
99        kernel_abi = get_kernel_abi()
100
101        doc = self.state.document
102        if not doc.settings.file_insertion_enabled:
103            raise self.warning("docutils: file insertion disabled")
104
105        env = self.state.document.settings.env
106        content = ViewList()
107        node = nodes.section()
108
109        abi_type = self.arguments[0]
110
111        if "no-symbols" in self.options:
112            show_symbols = False
113        else:
114            show_symbols = True
115
116        if "no-files" in self.options:
117            show_file = False
118        else:
119            show_file = True
120
121        tab_width = self.options.get('tab-width',
122                                     self.state.document.settings.tab_width)
123
124        old_f = None
125        n = 0
126        n_sym = 0
127        for msg, f, ln in kernel_abi.doc(show_file=show_file,
128                                            show_symbols=show_symbols,
129                                            filter_path=abi_type):
130            n_sym += 1
131            msg_list = statemachine.string2lines(msg, tab_width,
132                                                 convert_whitespace=True)
133            if "debug" in self.options:
134                lines = [
135                    "", "",  ".. code-block:: rst",
136                    "    :linenos:", ""
137                ]
138                for m in msg_list:
139                    lines.append("    " + m)
140            else:
141                lines = msg_list
142
143            for line in lines:
144                # sphinx counts lines from 0
145                content.append(line, f, ln - 1)
146                n += 1
147
148            if f != old_f:
149                # Add the file to Sphinx build dependencies if the file exists
150                fname = os.path.join(srctree, f)
151                if os.path.isfile(fname):
152                    env.note_dependency(fname)
153
154                old_f = f
155
156            # Sphinx doesn't like to parse big messages. So, let's
157            # add content symbol by symbol
158            if content:
159                self.do_parse(content, node)
160                content = ViewList()
161
162        if show_symbols and not show_file:
163            logger.verbose("%s ABI: %i symbols (%i ReST lines)" % (abi_type, n_sym, n))
164        elif not show_symbols and show_file:
165            logger.verbose("%s ABI: %i files (%i ReST lines)" % (abi_type, n_sym, n))
166        else:
167            logger.verbose("%s ABI: %i data (%i ReST lines)" % (abi_type, n_sym, n))
168
169        return node.children
170
171    def do_parse(self, content, node):
172        with switch_source_input(self.state, content):
173            self.state.nested_parse(content, 0, node, match_titles=1)
174