1# -*- 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 150 line_regex = re.compile("^#define LINENO (\S+)\#([0-9]+)$") 151 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 195