113540Sandrea.mondelli@ucf.edu#!/usr/bin/env python2.7
211398SN/A#
311398SN/A# Copyright (c) 2014-2015 ARM Limited
411398SN/A# All rights reserved
511398SN/A#
611398SN/A# The license below extends only to copyright in the software and shall
711398SN/A# not be construed as granting a license to any other intellectual
811398SN/A# property including but not limited to intellectual property relating
911398SN/A# to a hardware implementation of the functionality of the software
1011398SN/A# licensed hereunder.  You may use the software subject to the license
1111398SN/A# terms below provided that you ensure that this notice is replicated
1211398SN/A# unmodified and in its entirety in all distributions of the software,
1311398SN/A# modified or unmodified, in source code or in binary form.
1411398SN/A#
1511397SN/A# Copyright (c) 2011 The Hewlett-Packard Development Company
1611397SN/A# All rights reserved.
1711397SN/A#
1811397SN/A# Redistribution and use in source and binary forms, with or without
1911397SN/A# modification, are permitted provided that the following conditions are
2011397SN/A# met: redistributions of source code must retain the above copyright
2111397SN/A# notice, this list of conditions and the following disclaimer;
2211397SN/A# redistributions in binary form must reproduce the above copyright
2311397SN/A# notice, this list of conditions and the following disclaimer in the
2411397SN/A# documentation and/or other materials provided with the distribution;
2511397SN/A# neither the name of the copyright holders nor the names of its
2611397SN/A# contributors may be used to endorse or promote products derived from
2711397SN/A# this software without specific prior written permission.
2811397SN/A#
2911397SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
3011397SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
3111397SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3211397SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3311397SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3411397SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3511397SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3611397SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3711397SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3811397SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3911397SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4011397SN/A#
4111397SN/A# Authors: Nathan Binkert
4211398SN/A#          Andreas Sandberg
4311397SN/A
448225SN/Aimport os
458225SN/Aimport re
468225SN/Aimport sys
478225SN/A
488225SN/Afrom file_types import *
498225SN/A
508225SN/Acpp_c_headers = {
518225SN/A    'assert.h' : 'cassert',
528225SN/A    'ctype.h'  : 'cctype',
538225SN/A    'errno.h'  : 'cerrno',
548225SN/A    'float.h'  : 'cfloat',
558225SN/A    'limits.h' : 'climits',
568225SN/A    'locale.h' : 'clocale',
578225SN/A    'math.h'   : 'cmath',
588225SN/A    'setjmp.h' : 'csetjmp',
598225SN/A    'signal.h' : 'csignal',
608225SN/A    'stdarg.h' : 'cstdarg',
618225SN/A    'stddef.h' : 'cstddef',
628225SN/A    'stdio.h'  : 'cstdio',
638225SN/A    'stdlib.h' : 'cstdlib',
648225SN/A    'string.h' : 'cstring',
658225SN/A    'time.h'   : 'ctime',
668225SN/A    'wchar.h'  : 'cwchar',
678225SN/A    'wctype.h' : 'cwctype',
688225SN/A}
698225SN/A
708225SN/Ainclude_re = re.compile(r'([#%])(include|import).*[<"](.*)[">]')
718225SN/Adef include_key(line):
728225SN/A    '''Mark directories with a leading space so directories
738225SN/A    are sorted before files'''
748225SN/A
758225SN/A    match = include_re.match(line)
768225SN/A    assert match, line
778225SN/A    keyword = match.group(2)
788225SN/A    include = match.group(3)
798225SN/A
808225SN/A    # Everything but the file part needs to have a space prepended
818225SN/A    parts = include.split('/')
828225SN/A    if len(parts) == 2 and parts[0] == 'dnet':
838225SN/A        # Don't sort the dnet includes with respect to each other, but
848225SN/A        # make them sorted with respect to non dnet includes.  Python
858225SN/A        # guarantees that sorting is stable, so just clear the
868225SN/A        # basename part of the filename.
878225SN/A        parts[1] = ' '
888225SN/A    parts[0:-1] = [ ' ' + s for s in parts[0:-1] ]
898225SN/A    key = '/'.join(parts)
908225SN/A
918225SN/A    return key
928225SN/A
9310674SN/A
9410674SN/Adef _include_matcher(keyword="#include", delim="<>"):
9510674SN/A    """Match an include statement and return a (keyword, file, extra)
9610674SN/A    duple, or a touple of None values if there isn't a match."""
9710674SN/A
9810674SN/A    rex = re.compile(r'^(%s)\s*%s(.*)%s(.*)$' % (keyword, delim[0], delim[1]))
9910674SN/A
10010674SN/A    def matcher(context, line):
10110674SN/A        m = rex.match(line)
10210674SN/A        return m.groups() if m else (None, ) * 3
10310674SN/A
10410674SN/A    return matcher
10510674SN/A
10610674SN/Adef _include_matcher_fname(fname, **kwargs):
10710674SN/A    """Match an include of a specific file name. Any keyword arguments
10810674SN/A    are forwarded to _include_matcher, which is used to match the
10910674SN/A    actual include line."""
11010674SN/A
11110674SN/A    rex = re.compile(fname)
11210674SN/A    base_matcher = _include_matcher(**kwargs)
11310674SN/A
11410674SN/A    def matcher(context, line):
11510674SN/A        (keyword, fname, extra) = base_matcher(context, line)
11610674SN/A        if fname and rex.match(fname):
11710674SN/A            return (keyword, fname, extra)
11810674SN/A        else:
11910674SN/A            return (None, ) * 3
12010674SN/A
12110674SN/A    return matcher
12210674SN/A
12310674SN/A
12410674SN/Adef _include_matcher_main():
12510674SN/A    """Match a C/C++ source file's primary header (i.e., a file with
12610674SN/A    the same base name, but a header extension)."""
12710674SN/A
12810674SN/A    base_matcher = _include_matcher(delim='""')
12910674SN/A    rex = re.compile(r"^src/(.*)\.([^.]+)$")
13010674SN/A    header_map = {
13110674SN/A        "c" : "h",
13210674SN/A        "cc" : "hh",
13310674SN/A        "cpp" : "hh",
13410674SN/A        }
13510674SN/A    def matcher(context, line):
13610674SN/A        m = rex.match(context["filename"])
13710674SN/A        if not m:
13810674SN/A            return (None, ) * 3
13910674SN/A        base, ext = m.groups()
14010674SN/A        (keyword, fname, extra) = base_matcher(context, line)
14110674SN/A        try:
14210674SN/A            if fname == "%s.%s" % (base, header_map[ext]):
14310674SN/A                return (keyword, fname, extra)
14410674SN/A        except KeyError:
14510674SN/A            pass
14610674SN/A
14710674SN/A        return (None, ) * 3
14810674SN/A
14910674SN/A    return matcher
15010674SN/A
1518225SN/Aclass SortIncludes(object):
1528225SN/A    # different types of includes for different sorting of headers
1538225SN/A    # <Python.h>         - Python header needs to be first if it exists
1548225SN/A    # <*.h>              - system headers (directories before files)
1558225SN/A    # <*>                - STL headers
1568225SN/A    # <*.(hh|hxx|hpp|H)> - C++ Headers (directories before files)
1578225SN/A    # "*"                - M5 headers (directories before files)
1588225SN/A    includes_re = (
15910674SN/A        ('main', '""', _include_matcher_main()),
16010674SN/A        ('python', '<>', _include_matcher_fname("^Python\.h$")),
16112009Sandreas.sandberg@arm.com        ('pybind', '""', _include_matcher_fname("^pybind11/.*\.h$",
16212009Sandreas.sandberg@arm.com                                                delim='""')),
16312162Sandreas.sandberg@arm.com        ('m5shared', '<>', _include_matcher_fname("^gem5/")),
16410674SN/A        ('c', '<>', _include_matcher_fname("^.*\.h$")),
16510674SN/A        ('stl', '<>', _include_matcher_fname("^\w+$")),
16610674SN/A        ('cc', '<>', _include_matcher_fname("^.*\.(hh|hxx|hpp|H)$")),
16710674SN/A        ('m5header', '""', _include_matcher_fname("^.*\.h{1,2}$", delim='""')),
16810674SN/A        ('swig0', '<>', _include_matcher(keyword="%import")),
16910674SN/A        ('swig1', '<>', _include_matcher(keyword="%include")),
17010674SN/A        ('swig2', '""', _include_matcher(keyword="%import", delim='""')),
17110674SN/A        ('swig3', '""', _include_matcher(keyword="%include", delim='""')),
1728225SN/A        )
1738225SN/A
17410674SN/A    block_order = (
17511808Sandreas.sandberg@arm.com        ('python', ),
17612009Sandreas.sandberg@arm.com        ('pybind', ),
17710674SN/A        ('main', ),
17810674SN/A        ('c', ),
17910674SN/A        ('stl', ),
18010674SN/A        ('cc', ),
18112162Sandreas.sandberg@arm.com        ('m5shared', ),
18210674SN/A        ('m5header', ),
18310674SN/A        ('swig0', 'swig1', 'swig2', 'swig3', ),
18410674SN/A        )
1858225SN/A
1868225SN/A    def __init__(self):
18710674SN/A        self.block_priority = {}
18810674SN/A        for prio, keys in enumerate(self.block_order):
18910674SN/A            for key in keys:
19010674SN/A                self.block_priority[key] = prio
1918225SN/A
1928225SN/A    def reset(self):
1938225SN/A        # clear all stored headers
1948225SN/A        self.includes = {}
1958225SN/A
19610674SN/A    def dump_blocks(self, block_types):
19710674SN/A        """Merge includes of from several block types into one large
19810674SN/A        block of sorted includes. This is useful when we have multiple
19910674SN/A        include block types (e.g., swig includes) with the same
20010674SN/A        priority."""
2018225SN/A
20210674SN/A        includes = []
20310674SN/A        for block_type in block_types:
20410674SN/A            try:
20510674SN/A                includes += self.includes[block_type]
20610674SN/A            except KeyError:
20710674SN/A                pass
2088225SN/A
20910674SN/A        return sorted(set(includes))
21010674SN/A
21110674SN/A    def dump_includes(self):
21211402SN/A        includes = []
21310674SN/A        for types in self.block_order:
21411402SN/A            block = self.dump_blocks(types)
21511402SN/A            if includes and block:
21611402SN/A                includes.append("")
21711402SN/A            includes += block
21810674SN/A
21910674SN/A        self.reset()
22011402SN/A        return includes
2218225SN/A
2228225SN/A    def __call__(self, lines, filename, language):
22310275SN/A        self.reset()
2248225SN/A
22510674SN/A        context = {
22610674SN/A            "filename" : filename,
22710674SN/A            "language" : language,
22810674SN/A            }
22910674SN/A
23010674SN/A        def match_line(line):
23110674SN/A            if not line:
23210674SN/A                return (None, line)
23310674SN/A
23410674SN/A            for include_type, (ldelim, rdelim), matcher in self.includes_re:
23510674SN/A                keyword, include, extra = matcher(context, line)
23610674SN/A                if keyword:
23710674SN/A                    # if we've got a match, clean up the #include line,
23810674SN/A                    # fix up stl headers and store it in the proper category
23910674SN/A                    if include_type == 'c' and language == 'C++':
24010674SN/A                        stl_inc = cpp_c_headers.get(include, None)
24110674SN/A                        if stl_inc:
24210674SN/A                            include = stl_inc
24310674SN/A                            include_type = 'stl'
24410674SN/A
24510674SN/A                    return (include_type,
24610674SN/A                            keyword + ' ' + ldelim + include + rdelim + extra)
24710674SN/A
24810674SN/A            return (None, line)
24910674SN/A
25010674SN/A        processing_includes = False
2518225SN/A        for line in lines:
25210674SN/A            include_type, line = match_line(line)
25310674SN/A            if include_type:
25410674SN/A                try:
25510674SN/A                    self.includes[include_type].append(line)
25610674SN/A                except KeyError:
25710674SN/A                    self.includes[include_type] = [ line ]
2588225SN/A
25910674SN/A                processing_includes = True
26010674SN/A            elif processing_includes and not line.strip():
26110674SN/A                # Skip empty lines while processing includes
26210674SN/A                pass
26310674SN/A            elif processing_includes:
26410674SN/A                # We are now exiting an include block
26510674SN/A                processing_includes = False
2668225SN/A
26710674SN/A                # Output pending includes, a new line between, and the
26810674SN/A                # current l.
26911402SN/A                for include in self.dump_includes():
27011402SN/A                    yield include
27110674SN/A                yield ''
27210674SN/A                yield line
2738225SN/A            else:
27410674SN/A                # We are not in an include block, so just emit the line
2758225SN/A                yield line
2768225SN/A
27710674SN/A        # We've reached EOF, so dump any pending includes
27810674SN/A        if processing_includes:
27911402SN/A            for include in self.dump_includes():
28011402SN/A                yield include
2818225SN/A
2828225SN/A# default language types to try to apply our sorting rules to
2838225SN/Adefault_languages = frozenset(('C', 'C++', 'isa', 'python', 'scons', 'swig'))
2848225SN/A
2858225SN/Adef options():
2868225SN/A    import optparse
2878225SN/A    options = optparse.OptionParser()
2888225SN/A    add_option = options.add_option
2898225SN/A    add_option('-d', '--dir_ignore', metavar="DIR[,DIR]", type='string',
2908225SN/A               default=','.join(default_dir_ignore),
2918225SN/A               help="ignore directories")
2928225SN/A    add_option('-f', '--file_ignore', metavar="FILE[,FILE]", type='string',
2938225SN/A               default=','.join(default_file_ignore),
2948225SN/A               help="ignore files")
2958225SN/A    add_option('-l', '--languages', metavar="LANG[,LANG]", type='string',
2968225SN/A               default=','.join(default_languages),
2978225SN/A               help="languages")
2988225SN/A    add_option('-n', '--dry-run', action='store_true',
2998225SN/A               help="don't overwrite files")
3008225SN/A
3018225SN/A    return options
3028225SN/A
3038225SN/Adef parse_args(parser):
3048225SN/A    opts,args = parser.parse_args()
3058225SN/A
3068225SN/A    opts.dir_ignore = frozenset(opts.dir_ignore.split(','))
3078225SN/A    opts.file_ignore = frozenset(opts.file_ignore.split(','))
3088225SN/A    opts.languages = frozenset(opts.languages.split(','))
3098225SN/A
3108225SN/A    return opts,args
3118225SN/A
3128225SN/Aif __name__ == '__main__':
3138225SN/A    parser = options()
3148225SN/A    opts, args = parse_args(parser)
3158225SN/A
3168225SN/A    for base in args:
3178225SN/A        for filename,language in find_files(base, languages=opts.languages,
3188225SN/A                file_ignore=opts.file_ignore, dir_ignore=opts.dir_ignore):
3198225SN/A            if opts.dry_run:
3208225SN/A                print "%s: %s" % (filename, language)
3218225SN/A            else:
3228225SN/A                update_file(filename, filename, language, SortIncludes())
323