blob: 32c50e496b5fc708ad089c0120261a7609c7a5d4 [file] [log] [blame]
Heinrich Schuchardtc8e0f202020-12-31 23:16:46 +01001# -*- coding: utf-8; mode: python -*-
2# coding=utf-8
3# SPDX-License-Identifier: GPL-2.0
4#
5u"""
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.pl 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 codecs
36import os
37import subprocess
38import sys
39import re
40import kernellog
41
42from os import path
43
44from docutils import nodes, statemachine
45from docutils.statemachine import ViewList
46from docutils.parsers.rst import directives, Directive
47from docutils.utils.error_reporting import ErrorString
48
49#
50# AutodocReporter is only good up to Sphinx 1.7
51#
52import sphinx
53
54Use_SSI = sphinx.__version__[:3] >= '1.7'
55if Use_SSI:
56 from sphinx.util.docutils import switch_source_input
57else:
58 from sphinx.ext.autodoc import AutodocReporter
59
60__version__ = '1.0'
61
62def setup(app):
63
64 app.add_directive("kernel-abi", KernelCmd)
65 return dict(
66 version = __version__
67 , parallel_read_safe = True
68 , parallel_write_safe = True
69 )
70
71class KernelCmd(Directive):
72
73 u"""KernelABI (``kernel-abi``) directive"""
74
75 required_arguments = 1
76 optional_arguments = 2
77 has_content = False
78 final_argument_whitespace = True
79
80 option_spec = {
81 "debug" : directives.flag,
82 "rst" : directives.unchanged
83 }
84
85 def run(self):
86
87 doc = self.state.document
88 if not doc.settings.file_insertion_enabled:
89 raise self.warning("docutils: file insertion disabled")
90
91 env = doc.settings.env
92 cwd = path.dirname(doc.current_source)
93 cmd = "get_abi.pl rest --enable-lineno --dir "
94 cmd += self.arguments[0]
95
96 if 'rst' in self.options:
97 cmd += " --rst-source"
98
99 srctree = path.abspath(os.environ["srctree"])
100
101 fname = cmd
102
103 # extend PATH with $(srctree)/scripts
104 path_env = os.pathsep.join([
105 srctree + os.sep + "scripts",
106 os.environ["PATH"]
107 ])
108 shell_env = os.environ.copy()
109 shell_env["PATH"] = path_env
110 shell_env["srctree"] = srctree
111
112 lines = self.runCmd(cmd, shell=True, cwd=cwd, env=shell_env)
113 nodeList = self.nestedParse(lines, self.arguments[0])
114 return nodeList
115
116 def runCmd(self, cmd, **kwargs):
117 u"""Run command ``cmd`` and return it's stdout as unicode."""
118
119 try:
120 proc = subprocess.Popen(
121 cmd
122 , stdout = subprocess.PIPE
123 , stderr = subprocess.PIPE
124 , **kwargs
125 )
126 out, err = proc.communicate()
127
128 out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8')
129
130 if proc.returncode != 0:
131 raise self.severe(
132 u"command '%s' failed with return code %d"
133 % (cmd, proc.returncode)
134 )
135 except OSError as exc:
136 raise self.severe(u"problems with '%s' directive: %s."
137 % (self.name, ErrorString(exc)))
138 return out
139
140 def nestedParse(self, lines, fname):
141 content = ViewList()
142 node = nodes.section()
143
144 if "debug" in self.options:
145 code_block = "\n\n.. code-block:: rst\n :linenos:\n"
146 for l in lines.split("\n"):
147 code_block += "\n " + l
148 lines = code_block + "\n\n"
149
Benjamin Gray0690c9b2024-03-05 19:52:03 +0100150 line_regex = re.compile(r"^#define LINENO (\S+)\#([0-9]+)$")
Heinrich Schuchardtc8e0f202020-12-31 23:16:46 +0100151 ln = 0
152 n = 0
153 f = fname
154
155 for line in lines.split("\n"):
156 n = n + 1
157 match = line_regex.search(line)
158 if match:
159 new_f = match.group(1)
160
161 # Sphinx parser is lazy: it stops parsing contents in the
162 # middle, if it is too big. So, handle it per input file
163 if new_f != f and content:
164 self.do_parse(content, node)
165 content = ViewList()
166
167 f = new_f
168
169 # sphinx counts lines from 0
170 ln = int(match.group(2)) - 1
171 else:
172 content.append(line, f, ln)
173
174 kernellog.info(self.state.document.settings.env.app, "%s: parsed %i lines" % (fname, n))
175
176 if content:
177 self.do_parse(content, node)
178
179 return node.children
180
181 def do_parse(self, content, node):
182 if Use_SSI:
183 with switch_source_input(self.state, content):
184 self.state.nested_parse(content, 0, node, match_titles=1)
185 else:
186 buf = self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter
187
188 self.state.memo.title_styles = []
189 self.state.memo.section_level = 0
190 self.state.memo.reporter = AutodocReporter(content, self.state.memo.reporter)
191 try:
192 self.state.nested_parse(content, 0, node, match_titles=1)
193 finally:
194 self.state.memo.title_styles, self.state.memo.section_level, self.state.memo.reporter = buf