kernel_abi.py 5.6 KB

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