PageRenderTime 1055ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/util/style/sort_includes.py

https://gitlab.com/pranith/gem5
Python | 317 lines | 258 code | 9 blank | 50 comment | 4 complexity | 24b6fc3d40f2d62880cd14f153c98727 MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2014-2015 ARM Limited
  4. # All rights reserved
  5. #
  6. # The license below extends only to copyright in the software and shall
  7. # not be construed as granting a license to any other intellectual
  8. # property including but not limited to intellectual property relating
  9. # to a hardware implementation of the functionality of the software
  10. # licensed hereunder. You may use the software subject to the license
  11. # terms below provided that you ensure that this notice is replicated
  12. # unmodified and in its entirety in all distributions of the software,
  13. # modified or unmodified, in source code or in binary form.
  14. #
  15. # Copyright (c) 2011 The Hewlett-Packard Development Company
  16. # All rights reserved.
  17. #
  18. # Redistribution and use in source and binary forms, with or without
  19. # modification, are permitted provided that the following conditions are
  20. # met: redistributions of source code must retain the above copyright
  21. # notice, this list of conditions and the following disclaimer;
  22. # redistributions in binary form must reproduce the above copyright
  23. # notice, this list of conditions and the following disclaimer in the
  24. # documentation and/or other materials provided with the distribution;
  25. # neither the name of the copyright holders nor the names of its
  26. # contributors may be used to endorse or promote products derived from
  27. # this software without specific prior written permission.
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  32. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  33. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  34. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  35. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  36. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  37. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  38. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  39. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40. #
  41. # Authors: Nathan Binkert
  42. # Andreas Sandberg
  43. import os
  44. import re
  45. import sys
  46. from file_types import *
  47. cpp_c_headers = {
  48. 'assert.h' : 'cassert',
  49. 'ctype.h' : 'cctype',
  50. 'errno.h' : 'cerrno',
  51. 'float.h' : 'cfloat',
  52. 'limits.h' : 'climits',
  53. 'locale.h' : 'clocale',
  54. 'math.h' : 'cmath',
  55. 'setjmp.h' : 'csetjmp',
  56. 'signal.h' : 'csignal',
  57. 'stdarg.h' : 'cstdarg',
  58. 'stddef.h' : 'cstddef',
  59. 'stdio.h' : 'cstdio',
  60. 'stdlib.h' : 'cstdlib',
  61. 'string.h' : 'cstring',
  62. 'time.h' : 'ctime',
  63. 'wchar.h' : 'cwchar',
  64. 'wctype.h' : 'cwctype',
  65. }
  66. include_re = re.compile(r'([#%])(include|import).*[<"](.*)[">]')
  67. def include_key(line):
  68. '''Mark directories with a leading space so directories
  69. are sorted before files'''
  70. match = include_re.match(line)
  71. assert match, line
  72. keyword = match.group(2)
  73. include = match.group(3)
  74. # Everything but the file part needs to have a space prepended
  75. parts = include.split('/')
  76. if len(parts) == 2 and parts[0] == 'dnet':
  77. # Don't sort the dnet includes with respect to each other, but
  78. # make them sorted with respect to non dnet includes. Python
  79. # guarantees that sorting is stable, so just clear the
  80. # basename part of the filename.
  81. parts[1] = ' '
  82. parts[0:-1] = [ ' ' + s for s in parts[0:-1] ]
  83. key = '/'.join(parts)
  84. return key
  85. def _include_matcher(keyword="#include", delim="<>"):
  86. """Match an include statement and return a (keyword, file, extra)
  87. duple, or a touple of None values if there isn't a match."""
  88. rex = re.compile(r'^(%s)\s*%s(.*)%s(.*)$' % (keyword, delim[0], delim[1]))
  89. def matcher(context, line):
  90. m = rex.match(line)
  91. return m.groups() if m else (None, ) * 3
  92. return matcher
  93. def _include_matcher_fname(fname, **kwargs):
  94. """Match an include of a specific file name. Any keyword arguments
  95. are forwarded to _include_matcher, which is used to match the
  96. actual include line."""
  97. rex = re.compile(fname)
  98. base_matcher = _include_matcher(**kwargs)
  99. def matcher(context, line):
  100. (keyword, fname, extra) = base_matcher(context, line)
  101. if fname and rex.match(fname):
  102. return (keyword, fname, extra)
  103. else:
  104. return (None, ) * 3
  105. return matcher
  106. def _include_matcher_main():
  107. """Match a C/C++ source file's primary header (i.e., a file with
  108. the same base name, but a header extension)."""
  109. base_matcher = _include_matcher(delim='""')
  110. rex = re.compile(r"^src/(.*)\.([^.]+)$")
  111. header_map = {
  112. "c" : "h",
  113. "cc" : "hh",
  114. "cpp" : "hh",
  115. }
  116. def matcher(context, line):
  117. m = rex.match(context["filename"])
  118. if not m:
  119. return (None, ) * 3
  120. base, ext = m.groups()
  121. (keyword, fname, extra) = base_matcher(context, line)
  122. try:
  123. if fname == "%s.%s" % (base, header_map[ext]):
  124. return (keyword, fname, extra)
  125. except KeyError:
  126. pass
  127. return (None, ) * 3
  128. return matcher
  129. class SortIncludes(object):
  130. # different types of includes for different sorting of headers
  131. # <Python.h> - Python header needs to be first if it exists
  132. # <*.h> - system headers (directories before files)
  133. # <*> - STL headers
  134. # <*.(hh|hxx|hpp|H)> - C++ Headers (directories before files)
  135. # "*" - M5 headers (directories before files)
  136. includes_re = (
  137. ('main', '""', _include_matcher_main()),
  138. ('python', '<>', _include_matcher_fname("^Python\.h$")),
  139. ('c', '<>', _include_matcher_fname("^.*\.h$")),
  140. ('stl', '<>', _include_matcher_fname("^\w+$")),
  141. ('cc', '<>', _include_matcher_fname("^.*\.(hh|hxx|hpp|H)$")),
  142. ('m5header', '""', _include_matcher_fname("^.*\.h{1,2}$", delim='""')),
  143. ('swig0', '<>', _include_matcher(keyword="%import")),
  144. ('swig1', '<>', _include_matcher(keyword="%include")),
  145. ('swig2', '""', _include_matcher(keyword="%import", delim='""')),
  146. ('swig3', '""', _include_matcher(keyword="%include", delim='""')),
  147. )
  148. block_order = (
  149. ('main', ),
  150. ('python', ),
  151. ('c', ),
  152. ('stl', ),
  153. ('cc', ),
  154. ('m5header', ),
  155. ('swig0', 'swig1', 'swig2', 'swig3', ),
  156. )
  157. def __init__(self):
  158. self.block_priority = {}
  159. for prio, keys in enumerate(self.block_order):
  160. for key in keys:
  161. self.block_priority[key] = prio
  162. def reset(self):
  163. # clear all stored headers
  164. self.includes = {}
  165. def dump_blocks(self, block_types):
  166. """Merge includes of from several block types into one large
  167. block of sorted includes. This is useful when we have multiple
  168. include block types (e.g., swig includes) with the same
  169. priority."""
  170. includes = []
  171. for block_type in block_types:
  172. try:
  173. includes += self.includes[block_type]
  174. except KeyError:
  175. pass
  176. return sorted(set(includes))
  177. def dump_includes(self):
  178. includes = []
  179. for types in self.block_order:
  180. block = self.dump_blocks(types)
  181. if includes and block:
  182. includes.append("")
  183. includes += block
  184. self.reset()
  185. return includes
  186. def __call__(self, lines, filename, language):
  187. self.reset()
  188. context = {
  189. "filename" : filename,
  190. "language" : language,
  191. }
  192. def match_line(line):
  193. if not line:
  194. return (None, line)
  195. for include_type, (ldelim, rdelim), matcher in self.includes_re:
  196. keyword, include, extra = matcher(context, line)
  197. if keyword:
  198. # if we've got a match, clean up the #include line,
  199. # fix up stl headers and store it in the proper category
  200. if include_type == 'c' and language == 'C++':
  201. stl_inc = cpp_c_headers.get(include, None)
  202. if stl_inc:
  203. include = stl_inc
  204. include_type = 'stl'
  205. return (include_type,
  206. keyword + ' ' + ldelim + include + rdelim + extra)
  207. return (None, line)
  208. processing_includes = False
  209. for line in lines:
  210. include_type, line = match_line(line)
  211. if include_type:
  212. try:
  213. self.includes[include_type].append(line)
  214. except KeyError:
  215. self.includes[include_type] = [ line ]
  216. processing_includes = True
  217. elif processing_includes and not line.strip():
  218. # Skip empty lines while processing includes
  219. pass
  220. elif processing_includes:
  221. # We are now exiting an include block
  222. processing_includes = False
  223. # Output pending includes, a new line between, and the
  224. # current l.
  225. for include in self.dump_includes():
  226. yield include
  227. yield ''
  228. yield line
  229. else:
  230. # We are not in an include block, so just emit the line
  231. yield line
  232. # We've reached EOF, so dump any pending includes
  233. if processing_includes:
  234. for include in self.dump_includes():
  235. yield include
  236. # default language types to try to apply our sorting rules to
  237. default_languages = frozenset(('C', 'C++', 'isa', 'python', 'scons', 'swig'))
  238. def options():
  239. import optparse
  240. options = optparse.OptionParser()
  241. add_option = options.add_option
  242. add_option('-d', '--dir_ignore', metavar="DIR[,DIR]", type='string',
  243. default=','.join(default_dir_ignore),
  244. help="ignore directories")
  245. add_option('-f', '--file_ignore', metavar="FILE[,FILE]", type='string',
  246. default=','.join(default_file_ignore),
  247. help="ignore files")
  248. add_option('-l', '--languages', metavar="LANG[,LANG]", type='string',
  249. default=','.join(default_languages),
  250. help="languages")
  251. add_option('-n', '--dry-run', action='store_true',
  252. help="don't overwrite files")
  253. return options
  254. def parse_args(parser):
  255. opts,args = parser.parse_args()
  256. opts.dir_ignore = frozenset(opts.dir_ignore.split(','))
  257. opts.file_ignore = frozenset(opts.file_ignore.split(','))
  258. opts.languages = frozenset(opts.languages.split(','))
  259. return opts,args
  260. if __name__ == '__main__':
  261. parser = options()
  262. opts, args = parse_args(parser)
  263. for base in args:
  264. for filename,language in find_files(base, languages=opts.languages,
  265. file_ignore=opts.file_ignore, dir_ignore=opts.dir_ignore):
  266. if opts.dry_run:
  267. print "%s: %s" % (filename, language)
  268. else:
  269. update_file(filename, filename, language, SortIncludes())