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 48from sphinx.util.docutils import switch_source_input 49 50__version__ = '1.0' 51 52def setup(app): 53 54 app.add_directive("kernel-abi", KernelCmd) 55 return dict( 56 version = __version__ 57 , parallel_read_safe = True 58 , parallel_write_safe = True 59 ) 60 61class KernelCmd(Directive): 62 63 u"""KernelABI (``kernel-abi``) directive""" 64 65 required_arguments = 1 66 optional_arguments = 2 67 has_content = False 68 final_argument_whitespace = True 69 70 option_spec = { 71 "debug" : directives.flag, 72 "rst" : directives.unchanged 73 } 74 75 def run(self): 76 77 doc = self.state.document 78 if not doc.settings.file_insertion_enabled: 79 raise self.warning("docutils: file insertion disabled") 80 81 env = doc.settings.env 82 cwd = path.dirname(doc.current_source) 83 cmd = "get_abi.pl rest --enable-lineno --dir " 84 cmd += self.arguments[0] 85 86 if 'rst' in self.options: 87 cmd += " --rst-source" 88 89 srctree = path.abspath(os.environ["srctree"]) 90 91 fname = cmd 92 93 # extend PATH with $(srctree)/scripts 94 path_env = os.pathsep.join([ 95 srctree + os.sep + "scripts", 96 os.environ["PATH"] 97 ]) 98 shell_env = os.environ.copy() 99 shell_env["PATH"] = path_env 100 shell_env["srctree"] = srctree 101 102 lines = self.runCmd(cmd, shell=True, cwd=cwd, env=shell_env) 103 nodeList = self.nestedParse(lines, self.arguments[0]) 104 return nodeList 105 106 def runCmd(self, cmd, **kwargs): 107 u"""Run command ``cmd`` and return its stdout as unicode.""" 108 109 try: 110 proc = subprocess.Popen( 111 cmd 112 , stdout = subprocess.PIPE 113 , stderr = subprocess.PIPE 114 , **kwargs 115 ) 116 out, err = proc.communicate() 117 118 out, err = codecs.decode(out, 'utf-8'), codecs.decode(err, 'utf-8') 119 120 if proc.returncode != 0: 121 raise self.severe( 122 u"command '%s' failed with return code %d" 123 % (cmd, proc.returncode) 124 ) 125 except OSError as exc: 126 raise self.severe(u"problems with '%s' directive: %s." 127 % (self.name, ErrorString(exc))) 128 return out 129 130 def nestedParse(self, lines, fname): 131 env = self.state.document.settings.env 132 content = ViewList() 133 node = nodes.section() 134 135 if "debug" in self.options: 136 code_block = "\n\n.. code-block:: rst\n :linenos:\n" 137 for l in lines.split("\n"): 138 code_block += "\n " + l 139 lines = code_block + "\n\n" 140 141 line_regex = re.compile(r"^\.\. LINENO (\S+)\#([0-9]+)$") 142 ln = 0 143 n = 0 144 f = fname 145 146 for line in lines.split("\n"): 147 n = n + 1 148 match = line_regex.search(line) 149 if match: 150 new_f = match.group(1) 151 152 # Sphinx parser is lazy: it stops parsing contents in the 153 # middle, if it is too big. So, handle it per input file 154 if new_f != f and content: 155 self.do_parse(content, node) 156 content = ViewList() 157 158 # Add the file to Sphinx build dependencies 159 env.note_dependency(os.path.abspath(f)) 160 161 f = new_f 162 163 # sphinx counts lines from 0 164 ln = int(match.group(2)) - 1 165 else: 166 content.append(line, f, ln) 167 168 kernellog.info(self.state.document.settings.env.app, "%s: parsed %i lines" % (fname, n)) 169 170 if content: 171 self.do_parse(content, node) 172 173 return node.children 174 175 def do_parse(self, content, node): 176 with switch_source_input(self.state, content): 177 self.state.nested_parse(content, 0, node, match_titles=1) 178