summaryrefslogtreecommitdiff
path: root/util/style/sort_includes.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/style/sort_includes.py')
-rw-r--r--util/style/sort_includes.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/util/style/sort_includes.py b/util/style/sort_includes.py
new file mode 100644
index 000000000..334d9e29e
--- /dev/null
+++ b/util/style/sort_includes.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2014-2015 ARM Limited
+# All rights reserved
+#
+# The license below extends only to copyright in the software and shall
+# not be construed as granting a license to any other intellectual
+# property including but not limited to intellectual property relating
+# to a hardware implementation of the functionality of the software
+# licensed hereunder. You may use the software subject to the license
+# terms below provided that you ensure that this notice is replicated
+# unmodified and in its entirety in all distributions of the software,
+# modified or unmodified, in source code or in binary form.
+#
+# Copyright (c) 2011 The Hewlett-Packard Development Company
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Authors: Nathan Binkert
+# Andreas Sandberg
+
+import os
+import re
+import sys
+
+from file_types import *
+
+cpp_c_headers = {
+ 'assert.h' : 'cassert',
+ 'ctype.h' : 'cctype',
+ 'errno.h' : 'cerrno',
+ 'float.h' : 'cfloat',
+ 'limits.h' : 'climits',
+ 'locale.h' : 'clocale',
+ 'math.h' : 'cmath',
+ 'setjmp.h' : 'csetjmp',
+ 'signal.h' : 'csignal',
+ 'stdarg.h' : 'cstdarg',
+ 'stddef.h' : 'cstddef',
+ 'stdio.h' : 'cstdio',
+ 'stdlib.h' : 'cstdlib',
+ 'string.h' : 'cstring',
+ 'time.h' : 'ctime',
+ 'wchar.h' : 'cwchar',
+ 'wctype.h' : 'cwctype',
+}
+
+include_re = re.compile(r'([#%])(include|import).*[<"](.*)[">]')
+def include_key(line):
+ '''Mark directories with a leading space so directories
+ are sorted before files'''
+
+ match = include_re.match(line)
+ assert match, line
+ keyword = match.group(2)
+ include = match.group(3)
+
+ # Everything but the file part needs to have a space prepended
+ parts = include.split('/')
+ if len(parts) == 2 and parts[0] == 'dnet':
+ # Don't sort the dnet includes with respect to each other, but
+ # make them sorted with respect to non dnet includes. Python
+ # guarantees that sorting is stable, so just clear the
+ # basename part of the filename.
+ parts[1] = ' '
+ parts[0:-1] = [ ' ' + s for s in parts[0:-1] ]
+ key = '/'.join(parts)
+
+ return key
+
+
+def _include_matcher(keyword="#include", delim="<>"):
+ """Match an include statement and return a (keyword, file, extra)
+ duple, or a touple of None values if there isn't a match."""
+
+ rex = re.compile(r'^(%s)\s*%s(.*)%s(.*)$' % (keyword, delim[0], delim[1]))
+
+ def matcher(context, line):
+ m = rex.match(line)
+ return m.groups() if m else (None, ) * 3
+
+ return matcher
+
+def _include_matcher_fname(fname, **kwargs):
+ """Match an include of a specific file name. Any keyword arguments
+ are forwarded to _include_matcher, which is used to match the
+ actual include line."""
+
+ rex = re.compile(fname)
+ base_matcher = _include_matcher(**kwargs)
+
+ def matcher(context, line):
+ (keyword, fname, extra) = base_matcher(context, line)
+ if fname and rex.match(fname):
+ return (keyword, fname, extra)
+ else:
+ return (None, ) * 3
+
+ return matcher
+
+
+def _include_matcher_main():
+ """Match a C/C++ source file's primary header (i.e., a file with
+ the same base name, but a header extension)."""
+
+ base_matcher = _include_matcher(delim='""')
+ rex = re.compile(r"^src/(.*)\.([^.]+)$")
+ header_map = {
+ "c" : "h",
+ "cc" : "hh",
+ "cpp" : "hh",
+ }
+ def matcher(context, line):
+ m = rex.match(context["filename"])
+ if not m:
+ return (None, ) * 3
+ base, ext = m.groups()
+ (keyword, fname, extra) = base_matcher(context, line)
+ try:
+ if fname == "%s.%s" % (base, header_map[ext]):
+ return (keyword, fname, extra)
+ except KeyError:
+ pass
+
+ return (None, ) * 3
+
+ return matcher
+
+class SortIncludes(object):
+ # different types of includes for different sorting of headers
+ # <Python.h> - Python header needs to be first if it exists
+ # <*.h> - system headers (directories before files)
+ # <*> - STL headers
+ # <*.(hh|hxx|hpp|H)> - C++ Headers (directories before files)
+ # "*" - M5 headers (directories before files)
+ includes_re = (
+ ('main', '""', _include_matcher_main()),
+ ('python', '<>', _include_matcher_fname("^Python\.h$")),
+ ('c', '<>', _include_matcher_fname("^.*\.h$")),
+ ('stl', '<>', _include_matcher_fname("^\w+$")),
+ ('cc', '<>', _include_matcher_fname("^.*\.(hh|hxx|hpp|H)$")),
+ ('m5header', '""', _include_matcher_fname("^.*\.h{1,2}$", delim='""')),
+ ('swig0', '<>', _include_matcher(keyword="%import")),
+ ('swig1', '<>', _include_matcher(keyword="%include")),
+ ('swig2', '""', _include_matcher(keyword="%import", delim='""')),
+ ('swig3', '""', _include_matcher(keyword="%include", delim='""')),
+ )
+
+ block_order = (
+ ('main', ),
+ ('python', ),
+ ('c', ),
+ ('stl', ),
+ ('cc', ),
+ ('m5header', ),
+ ('swig0', 'swig1', 'swig2', 'swig3', ),
+ )
+
+ def __init__(self):
+ self.block_priority = {}
+ for prio, keys in enumerate(self.block_order):
+ for key in keys:
+ self.block_priority[key] = prio
+
+ def reset(self):
+ # clear all stored headers
+ self.includes = {}
+
+ def dump_blocks(self, block_types):
+ """Merge includes of from several block types into one large
+ block of sorted includes. This is useful when we have multiple
+ include block types (e.g., swig includes) with the same
+ priority."""
+
+ includes = []
+ for block_type in block_types:
+ try:
+ includes += self.includes[block_type]
+ except KeyError:
+ pass
+
+ return sorted(set(includes))
+
+ def dump_includes(self):
+ includes = []
+ for types in self.block_order:
+ block = self.dump_blocks(types)
+ if includes and block:
+ includes.append("")
+ includes += block
+
+ self.reset()
+ return includes
+
+ def __call__(self, lines, filename, language):
+ self.reset()
+
+ context = {
+ "filename" : filename,
+ "language" : language,
+ }
+
+ def match_line(line):
+ if not line:
+ return (None, line)
+
+ for include_type, (ldelim, rdelim), matcher in self.includes_re:
+ keyword, include, extra = matcher(context, line)
+ if keyword:
+ # if we've got a match, clean up the #include line,
+ # fix up stl headers and store it in the proper category
+ if include_type == 'c' and language == 'C++':
+ stl_inc = cpp_c_headers.get(include, None)
+ if stl_inc:
+ include = stl_inc
+ include_type = 'stl'
+
+ return (include_type,
+ keyword + ' ' + ldelim + include + rdelim + extra)
+
+ return (None, line)
+
+ processing_includes = False
+ for line in lines:
+ include_type, line = match_line(line)
+ if include_type:
+ try:
+ self.includes[include_type].append(line)
+ except KeyError:
+ self.includes[include_type] = [ line ]
+
+ processing_includes = True
+ elif processing_includes and not line.strip():
+ # Skip empty lines while processing includes
+ pass
+ elif processing_includes:
+ # We are now exiting an include block
+ processing_includes = False
+
+ # Output pending includes, a new line between, and the
+ # current l.
+ for include in self.dump_includes():
+ yield include
+ yield ''
+ yield line
+ else:
+ # We are not in an include block, so just emit the line
+ yield line
+
+ # We've reached EOF, so dump any pending includes
+ if processing_includes:
+ for include in self.dump_includes():
+ yield include
+
+# default language types to try to apply our sorting rules to
+default_languages = frozenset(('C', 'C++', 'isa', 'python', 'scons', 'swig'))
+
+def options():
+ import optparse
+ options = optparse.OptionParser()
+ add_option = options.add_option
+ add_option('-d', '--dir_ignore', metavar="DIR[,DIR]", type='string',
+ default=','.join(default_dir_ignore),
+ help="ignore directories")
+ add_option('-f', '--file_ignore', metavar="FILE[,FILE]", type='string',
+ default=','.join(default_file_ignore),
+ help="ignore files")
+ add_option('-l', '--languages', metavar="LANG[,LANG]", type='string',
+ default=','.join(default_languages),
+ help="languages")
+ add_option('-n', '--dry-run', action='store_true',
+ help="don't overwrite files")
+
+ return options
+
+def parse_args(parser):
+ opts,args = parser.parse_args()
+
+ opts.dir_ignore = frozenset(opts.dir_ignore.split(','))
+ opts.file_ignore = frozenset(opts.file_ignore.split(','))
+ opts.languages = frozenset(opts.languages.split(','))
+
+ return opts,args
+
+if __name__ == '__main__':
+ parser = options()
+ opts, args = parse_args(parser)
+
+ for base in args:
+ for filename,language in find_files(base, languages=opts.languages,
+ file_ignore=opts.file_ignore, dir_ignore=opts.dir_ignore):
+ if opts.dry_run:
+ print "%s: %s" % (filename, language)
+ else:
+ update_file(filename, filename, language, SortIncludes())