diff options
Diffstat (limited to 'util/style/sort_includes.py')
-rw-r--r-- | util/style/sort_includes.py | 317 |
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()) |