| #!/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()) |