xref: /OK3568_Linux_fs/kernel/Documentation/sphinx/kernel_abi.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# -*- coding: utf-8; mode: python -*-
2*4882a593Smuzhiyun# coding=utf-8
3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyunu"""
6*4882a593Smuzhiyun    kernel-abi
7*4882a593Smuzhiyun    ~~~~~~~~~~
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun    Implementation of the ``kernel-abi`` reST-directive.
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun    :copyright:  Copyright (C) 2016  Markus Heiser
12*4882a593Smuzhiyun    :copyright:  Copyright (C) 2016-2020  Mauro Carvalho Chehab
13*4882a593Smuzhiyun    :maintained-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
14*4882a593Smuzhiyun    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
15*4882a593Smuzhiyun
16*4882a593Smuzhiyun    The ``kernel-abi`` (:py:class:`KernelCmd`) directive calls the
17*4882a593Smuzhiyun    scripts/get_abi.pl script to parse the Kernel ABI files.
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun    Overview of directive's argument and options.
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun    .. code-block:: rst
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun        .. kernel-abi:: <ABI directory location>
24*4882a593Smuzhiyun            :debug:
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun    The argument ``<ABI directory location>`` is required. It contains the
27*4882a593Smuzhiyun    location of the ABI files to be parsed.
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun    ``debug``
30*4882a593Smuzhiyun      Inserts a code-block with the *raw* reST. Sometimes it is helpful to see
31*4882a593Smuzhiyun      what reST is generated.
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun"""
34*4882a593Smuzhiyun
35*4882a593Smuzhiyunimport codecs
36*4882a593Smuzhiyunimport os
37*4882a593Smuzhiyunimport subprocess
38*4882a593Smuzhiyunimport sys
39*4882a593Smuzhiyunimport re
40*4882a593Smuzhiyunimport kernellog
41*4882a593Smuzhiyun
42*4882a593Smuzhiyunfrom os import path
43*4882a593Smuzhiyun
44*4882a593Smuzhiyunfrom docutils import nodes, statemachine
45*4882a593Smuzhiyunfrom docutils.statemachine import ViewList
46*4882a593Smuzhiyunfrom docutils.parsers.rst import directives, Directive
47*4882a593Smuzhiyunfrom docutils.utils.error_reporting import ErrorString
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun#
50*4882a593Smuzhiyun# AutodocReporter is only good up to Sphinx 1.7
51*4882a593Smuzhiyun#
52*4882a593Smuzhiyunimport sphinx
53*4882a593Smuzhiyun
54*4882a593SmuzhiyunUse_SSI = sphinx.__version__[:3] >= '1.7'
55*4882a593Smuzhiyunif Use_SSI:
56*4882a593Smuzhiyun    from sphinx.util.docutils import switch_source_input
57*4882a593Smuzhiyunelse:
58*4882a593Smuzhiyun    from sphinx.ext.autodoc import AutodocReporter
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun__version__  = '1.0'
61*4882a593Smuzhiyun
62*4882a593Smuzhiyundef setup(app):
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun    app.add_directive("kernel-abi", KernelCmd)
65*4882a593Smuzhiyun    return dict(
66*4882a593Smuzhiyun        version = __version__
67*4882a593Smuzhiyun        , parallel_read_safe = True
68*4882a593Smuzhiyun        , parallel_write_safe = True
69*4882a593Smuzhiyun    )
70*4882a593Smuzhiyun
71*4882a593Smuzhiyunclass KernelCmd(Directive):
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun    u"""KernelABI (``kernel-abi``) directive"""
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun    required_arguments = 1
76*4882a593Smuzhiyun    optional_arguments = 2
77*4882a593Smuzhiyun    has_content = False
78*4882a593Smuzhiyun    final_argument_whitespace = True
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun    option_spec = {
81*4882a593Smuzhiyun        "debug"     : directives.flag,
82*4882a593Smuzhiyun        "rst"       : directives.unchanged
83*4882a593Smuzhiyun    }
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun    def run(self):
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun        doc = self.state.document
88*4882a593Smuzhiyun        if not doc.settings.file_insertion_enabled:
89*4882a593Smuzhiyun            raise self.warning("docutils: file insertion disabled")
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun        env = doc.settings.env
92*4882a593Smuzhiyun        cwd = path.dirname(doc.current_source)
93*4882a593Smuzhiyun        cmd = "get_abi.pl rest --enable-lineno --dir "
94*4882a593Smuzhiyun        cmd += self.arguments[0]
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun        if 'rst' in self.options:
97*4882a593Smuzhiyun            cmd += " --rst-source"
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun        srctree = path.abspath(os.environ["srctree"])
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun        fname = cmd
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun        # extend PATH with $(srctree)/scripts
104*4882a593Smuzhiyun        path_env = os.pathsep.join([
105*4882a593Smuzhiyun            srctree + os.sep + "scripts",
106*4882a593Smuzhiyun            os.environ["PATH"]
107*4882a593Smuzhiyun        ])
108*4882a593Smuzhiyun        shell_env = os.environ.copy()
109*4882a593Smuzhiyun        shell_env["PATH"]    = path_env
110*4882a593Smuzhiyun        shell_env["srctree"] = srctree
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun        lines = self.runCmd(cmd, shell=True, cwd=cwd, env=shell_env)
113*4882a593Smuzhiyun        nodeList = self.nestedParse(lines, self.arguments[0])
114*4882a593Smuzhiyun        return nodeList
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    def runCmd(self, cmd, **kwargs):
117*4882a593Smuzhiyun        u"""Run command ``cmd`` and return it's stdout as unicode."""
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun        try:
120*4882a593Smuzhiyun            proc = subprocess.Popen(
121*4882a593Smuzhiyun                cmd
122*4882a593Smuzhiyun                , stdout = subprocess.PIPE
123*4882a593Smuzhiyun                , stderr = subprocess.PIPE
124*4882a593Smuzhiyun                , **kwargs
125*4882a593Smuzhiyun            )
126*4882a593Smuzhiyun            out, err = proc.communicate()
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun            out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8')
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun            if proc.returncode != 0:
131*4882a593Smuzhiyun                raise self.severe(
132*4882a593Smuzhiyun                    u"command '%s' failed with return code %d"
133*4882a593Smuzhiyun                    % (cmd, proc.returncode)
134*4882a593Smuzhiyun                )
135*4882a593Smuzhiyun        except OSError as exc:
136*4882a593Smuzhiyun            raise self.severe(u"problems with '%s' directive: %s."
137*4882a593Smuzhiyun                              % (self.name, ErrorString(exc)))
138*4882a593Smuzhiyun        return out
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun    def nestedParse(self, lines, fname):
141*4882a593Smuzhiyun        content = ViewList()
142*4882a593Smuzhiyun        node = nodes.section()
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun        if "debug" in self.options:
145*4882a593Smuzhiyun            code_block = "\n\n.. code-block:: rst\n    :linenos:\n"
146*4882a593Smuzhiyun            for l in lines.split("\n"):
147*4882a593Smuzhiyun                code_block += "\n    " + l
148*4882a593Smuzhiyun            lines = code_block + "\n\n"
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun        line_regex = re.compile("^#define LINENO (\S+)\#([0-9]+)$")
151*4882a593Smuzhiyun        ln = 0
152*4882a593Smuzhiyun        n = 0
153*4882a593Smuzhiyun        f = fname
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun        for line in lines.split("\n"):
156*4882a593Smuzhiyun            n = n + 1
157*4882a593Smuzhiyun            match = line_regex.search(line)
158*4882a593Smuzhiyun            if match:
159*4882a593Smuzhiyun                new_f = match.group(1)
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun                # Sphinx parser is lazy: it stops parsing contents in the
162*4882a593Smuzhiyun                # middle, if it is too big. So, handle it per input file
163*4882a593Smuzhiyun                if new_f != f and content:
164*4882a593Smuzhiyun                    self.do_parse(content, node)
165*4882a593Smuzhiyun                    content = ViewList()
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun                f = new_f
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun                # sphinx counts lines from 0
170*4882a593Smuzhiyun                ln = int(match.group(2)) - 1
171*4882a593Smuzhiyun            else:
172*4882a593Smuzhiyun                content.append(line, f, ln)
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun        kernellog.info(self.state.document.settings.env.app, "%s: parsed %i lines" % (fname, n))
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun        if content:
177*4882a593Smuzhiyun            self.do_parse(content, node)
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun        return node.children
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun    def do_parse(self, content, node):
182*4882a593Smuzhiyun        if Use_SSI:
183*4882a593Smuzhiyun            with switch_source_input(self.state, content):
184*4882a593Smuzhiyun                self.state.nested_parse(content, 0, node, match_titles=1)
185*4882a593Smuzhiyun        else:
186*4882a593Smuzhiyun            buf  = self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun            self.state.memo.title_styles  = []
189*4882a593Smuzhiyun            self.state.memo.section_level = 0
190*4882a593Smuzhiyun            self.state.memo.reporter      = AutodocReporter(content, self.state.memo.reporter)
191*4882a593Smuzhiyun            try:
192*4882a593Smuzhiyun                self.state.nested_parse(content, 0, node, match_titles=1)
193*4882a593Smuzhiyun            finally:
194*4882a593Smuzhiyun                self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter = buf
195