| #!/usr/bin/env python2.7 | |
| # | |
| # 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$")), | |
| ('pybind', '""', _include_matcher_fname("^pybind11/.*\.h$", | |
| delim='""')), | |
| ('m5shared', '<>', _include_matcher_fname("^gem5/")), | |
| ('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 = ( | |
| ('python', ), | |
| ('pybind', ), | |
| ('main', ), | |
| ('c', ), | |
| ('stl', ), | |
| ('cc', ), | |
| ('m5shared', ), | |
| ('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()) |