hgstyle.py revision 11404:72b399971cbc
1#! /usr/bin/env python
2# Copyright (c) 2014 ARM Limited
3# All rights reserved
4#
5# The license below extends only to copyright in the software and shall
6# not be construed as granting a license to any other intellectual
7# property including but not limited to intellectual property relating
8# to a hardware implementation of the functionality of the software
9# licensed hereunder.  You may use the software subject to the license
10# terms below provided that you ensure that this notice is replicated
11# unmodified and in its entirety in all distributions of the software,
12# modified or unmodified, in source code or in binary form.
13#
14# Copyright (c) 2006 The Regents of The University of Michigan
15# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
16# Copyright (c) 2016 Advanced Micro Devices, Inc.
17# All rights reserved.
18#
19# Redistribution and use in source and binary forms, with or without
20# modification, are permitted provided that the following conditions are
21# met: redistributions of source code must retain the above copyright
22# notice, this list of conditions and the following disclaimer;
23# redistributions in binary form must reproduce the above copyright
24# notice, this list of conditions and the following disclaimer in the
25# documentation and/or other materials provided with the distribution;
26# neither the name of the copyright holders nor the names of its
27# contributors may be used to endorse or promote products derived from
28# this software without specific prior written permission.
29#
30# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41#
42# Authors: Nathan Binkert
43#          Steve Reinhardt
44
45import sys
46import os
47from os.path import join as joinpath
48
49current_dir = os.path.dirname(__file__)
50sys.path.insert(0, current_dir)
51
52from style.verifiers import all_verifiers
53from style.validators import all_validators
54from style.file_types import lang_type
55from style.style import MercurialUI, check_ignores
56from style.region import *
57
58from mercurial import bdiff, mdiff, commands
59
60def modified_regions(old_data, new_data):
61    regions = Regions()
62    beg = None
63    for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data):
64        if beg is not None and beg != fbeg:
65            regions.append(beg, fbeg)
66        beg = fend
67    return regions
68
69def modregions(wctx, fname):
70    fctx = wctx.filectx(fname)
71    pctx = fctx.parents()
72
73    file_data = fctx.data()
74    lines = mdiff.splitnewlines(file_data)
75    if len(pctx) in (1, 2):
76        mod_regions = modified_regions(pctx[0].data(), file_data)
77        if len(pctx) == 2:
78            m2 = modified_regions(pctx[1].data(), file_data)
79            # only the lines that are new in both
80            mod_regions &= m2
81    else:
82        mod_regions = Regions()
83        mod_regions.append(0, len(lines))
84
85    return mod_regions
86
87
88def validate(filename, verbose, exit_code):
89    lang = lang_type(filename)
90    if lang not in ('C', 'C++'):
91        return
92
93    def bad():
94        if exit_code is not None:
95            sys.exit(exit_code)
96
97    try:
98        f = file(filename, 'r')
99    except OSError:
100        if verbose > 0:
101            print 'could not open file %s' % filename
102        bad()
103        return None
104
105    vals = [ v(filename, verbose=(verbose > 1), language=lang)
106             for v in all_validators ]
107
108    for i, line in enumerate(f):
109        line = line.rstrip('\n')
110        for v in vals:
111            v.validate_line(i, line)
112
113
114    return vals
115
116
117def _modified_regions(repo, patterns, **kwargs):
118    opt_all = kwargs.get('all', False)
119    opt_no_ignore = kwargs.get('no_ignore', False)
120
121    # Import the match (repository file name matching helper)
122    # function. Different versions of Mercurial keep it in different
123    # modules and implement them differently.
124    try:
125        from mercurial import scmutil
126        m = scmutil.match(repo[None], patterns, kwargs)
127    except ImportError:
128        from mercurial import cmdutil
129        m = cmdutil.match(repo, patterns, kwargs)
130
131    modified, added, removed, deleted, unknown, ignore, clean = \
132        repo.status(match=m, clean=opt_all)
133
134    if not opt_all:
135        try:
136            wctx = repo.workingctx()
137        except:
138            from mercurial import context
139            wctx = context.workingctx(repo)
140
141        files = [ (fn, all_regions) for fn in added ] + \
142            [ (fn,  modregions(wctx, fn)) for fn in modified ]
143    else:
144        files = [ (fn, all_regions) for fn in added + modified + clean ]
145
146    for fname, mod_regions in files:
147        if opt_no_ignore or not check_ignores(fname):
148            yield fname, mod_regions
149
150
151def do_check_style(hgui, repo, *pats, **opts):
152    """check files for proper m5 style guidelines
153
154    Without an argument, checks all modified and added files for gem5
155    coding style violations. A list of files can be specified to limit
156    the checker to a subset of the repository. The style rules are
157    normally applied on a diff of the repository state (i.e., added
158    files are checked in their entirety while only modifications of
159    modified files are checked).
160
161    The --all option can be specified to include clean files and check
162    modified files in their entirety.
163
164    The --fix-<check>, --ignore-<check>, and --skip-<check> options
165    can be used to control individual style checks:
166
167    --fix-<check> will perform the check and automatically attempt to
168      fix sny style error (printing a warning if unsuccessful)
169
170    --ignore-<check> will perform the check but ignore any errors
171      found (other than printing a message for each)
172
173    --skip-<check> will skip performing the check entirely
174
175    If none of these options are given, all checks will be performed
176    and the user will be prompted on how to handle each error.
177
178    --fix-all, --ignore-all, and --skip-all are equivalent to specifying
179    --fix-<check>, --ignore-<check>, or --skip-<check> for all checks,
180    respectively.  However, option settings for specific checks take
181    precedence.  Thus --skip-all --fix-white can be used to skip every
182    check other than whitespace errors, which will be checked and
183    automatically fixed.
184
185    The -v/--verbose flag will display the offending line(s) as well
186    as their location.
187    """
188
189    ui = MercurialUI(hgui, verbose=hgui.verbose)
190
191    # instantiate varifier objects
192    verifiers = [v(ui, opts, base=repo.root) for v in all_verifiers]
193
194    for fname, mod_regions in _modified_regions(repo, pats, **opts):
195        for verifier in verifiers:
196            if verifier.apply(joinpath(repo.root, fname), mod_regions):
197                return True
198
199    return False
200
201def do_check_format(hgui, repo, *pats, **opts):
202    """check files for gem5 code formatting violations
203
204    Without an argument, checks all modified and added files for gem5
205    code formatting violations. A list of files can be specified to
206    limit the checker to a subset of the repository. The style rules
207    are normally applied on a diff of the repository state (i.e.,
208    added files are checked in their entirety while only modifications
209    of modified files are checked).
210
211    The --all option can be specified to include clean files and check
212    modified files in their entirety.
213    """
214    ui = MercurialUI(hgui, hgui.verbose)
215
216    verbose = 0
217    for fname, mod_regions in _modified_regions(repo, pats, **opts):
218        vals = validate(joinpath(repo.root, fname), verbose, None)
219        if vals is None:
220            return True
221        elif any([not v for v in vals]):
222            print "%s:" % fname
223            for v in vals:
224                v.dump()
225            result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?",
226                               'ai', 'a')
227            if result == 'a':
228                return True
229
230    return False
231
232def check_hook(hooktype):
233    if hooktype not in ('pretxncommit', 'pre-qrefresh'):
234        raise AttributeError, \
235              "This hook is not meant for %s" % hooktype
236
237# This function provides a hook that is called before transaction
238# commit and on qrefresh
239def check_style(ui, repo, hooktype, **kwargs):
240    check_hook(hooktype)
241    args = {}
242
243    try:
244        return do_check_style(ui, repo, **args)
245    except Exception, e:
246        import traceback
247        traceback.print_exc()
248        return True
249
250def check_format(ui, repo, hooktype, **kwargs):
251    check_hook(hooktype)
252    args = {}
253
254    try:
255        return do_check_format(ui, repo, **args)
256    except Exception, e:
257        import traceback
258        traceback.print_exc()
259        return True
260
261try:
262    from mercurial.i18n import _
263except ImportError:
264    def _(arg):
265        return arg
266
267_common_region_options = [
268    ('a', 'all', False,
269     _("include clean files and unmodified parts of modified files")),
270    ('', 'no-ignore', False, _("ignore the style ignore list")),
271    ]
272
273
274fix_opts = [('f', 'fix-all', False, _("fix all style errors"))] + \
275           [('', 'fix-' + v.opt_name, False,
276             _('fix errors in ' + v.test_name)) for v in all_verifiers]
277ignore_opts = [('', 'ignore-all', False, _("ignore all style errors"))] + \
278              [('', 'ignore-' + v.opt_name, False,
279                _('ignore errors in ' + v.test_name)) for v in all_verifiers]
280skip_opts = [('', 'skip-all', False, _("skip all style error checks"))] + \
281            [('', 'skip-' + v.opt_name, False,
282              _('skip checking for ' + v.test_name)) for v in all_verifiers]
283all_opts = fix_opts + ignore_opts + skip_opts
284
285
286cmdtable = {
287    '^m5style' : (
288        do_check_style, all_opts + _common_region_options + commands.walkopts,
289        _('hg m5style [-a] [FILE]...')),
290    '^m5format' :
291    ( do_check_format, [
292            ] + _common_region_options + commands.walkopts,
293      _('hg m5format [FILE]...')),
294}
295
296if __name__ == '__main__':
297    import argparse
298
299    parser = argparse.ArgumentParser(
300        description="Check a file for style violations")
301
302    parser.add_argument("--verbose", "-v", action="count",
303                        help="Produce verbose output")
304
305    parser.add_argument("file", metavar="FILE", nargs="+",
306                        type=str,
307                        help="Source file to inspect")
308
309    args = parser.parse_args()
310
311    for filename in args.file:
312        vals = validate(filename, verbose=args.verbose,
313                        exit_code=1)
314
315        if args.verbose > 0 and vals is not None:
316            for v in vals:
317                v.dump()
318