2 # Copyright (c) 2014 ARM Limited
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.
14 # Copyright (c) 2006 The Regents of The University of Michigan
15 # Copyright (c) 2007,2011 The Hewlett-Packard Development Company
16 # All rights reserved.
18 # Redistribution and use in source and binary forms, with or without
19 # modification, are permitted provided that the following conditions are
20 # met: redistributions of source code must retain the above copyright
21 # notice, this list of conditions and the following disclaimer;
22 # redistributions in binary form must reproduce the above copyright
23 # notice, this list of conditions and the following disclaimer in the
24 # documentation and/or other materials provided with the distribution;
25 # neither the name of the copyright holders nor the names of its
26 # contributors may be used to endorse or promote products derived from
27 # this software without specific prior written permission.
29 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 # Authors: Nathan Binkert
48 from os
.path
import dirname
, join
as joinpath
49 from itertools
import count
50 from mercurial
import bdiff
, mdiff
, commands
52 current_dir
= dirname(__file__
)
53 sys
.path
.insert(0, current_dir
)
54 sys
.path
.insert(1, joinpath(dirname(current_dir
), 'src', 'python'))
56 from m5
.util
import neg_inf
, pos_inf
, Region
, Regions
58 from file_types
import lang_type
60 all_regions
= Regions(Region(neg_inf
, pos_inf
))
63 lead
= re
.compile(r
'^([ \t]+)')
64 trail
= re
.compile(r
'([ \t]+)$')
65 any_control
= re
.compile(r
'\b(if|while|for)[ \t]*[(]')
66 good_control
= re
.compile(r
'\b(if|while|for) [(]')
68 format_types
= set(('C', 'C++'))
72 """Helper function to create regular expression ignore file
75 rex
= re
.compile(expr
)
77 return rex
.match(fname
)
80 # This list contains a list of functions that are called to determine
81 # if a file should be excluded from the style matching rules or
82 # not. The functions are called with the file name relative to the
83 # repository root (without a leading slash) as their argument. A file
84 # is excluded if any function in the list returns true.
86 # Ignore external projects as they are unlikely to follow the gem5
91 def check_ignores(fname
):
92 """Check if a file name matches any of the ignore rules"""
94 for rule
in style_ignores
:
101 def modified_regions(old_data
, new_data
):
104 for pbeg
, pend
, fbeg
, fend
in bdiff
.blocks(old_data
, new_data
):
105 if beg
is not None and beg
!= fbeg
:
106 regions
.append(beg
, fbeg
)
110 def modregions(wctx
, fname
):
111 fctx
= wctx
.filectx(fname
)
112 pctx
= fctx
.parents()
114 file_data
= fctx
.data()
115 lines
= mdiff
.splitnewlines(file_data
)
116 if len(pctx
) in (1, 2):
117 mod_regions
= modified_regions(pctx
[0].data(), file_data
)
119 m2
= modified_regions(pctx
[1].data(), file_data
)
120 # only the lines that are new in both
123 mod_regions
= Regions()
124 mod_regions
.append(0, len(lines
))
128 class UserInterface(object):
129 def __init__(self
, verbose
=False):
130 self
.verbose
= verbose
132 def prompt(self
, prompt
, results
, default
):
134 result
= self
.do_prompt(prompt
, results
, default
)
135 if result
in results
:
138 class MercurialUI(UserInterface
):
139 def __init__(self
, ui
, *args
, **kwargs
):
140 super(MercurialUI
, self
).__init
__(*args
, **kwargs
)
143 def do_prompt(self
, prompt
, results
, default
):
144 return self
.ui
.prompt(prompt
, default
=default
)
146 def write(self
, string
):
147 self
.ui
.write(string
)
149 class StdioUI(UserInterface
):
150 def do_prompt(self
, prompt
, results
, default
):
151 return raw_input(prompt
) or default
153 def write(self
, string
):
154 sys
.stdout
.write(string
)
156 class Verifier(object):
157 def __init__(self
, ui
, repo
):
161 def __getattr__(self
, attr
):
162 if attr
in ('prompt', 'write'):
163 return getattr(self
.ui
, attr
)
167 wctx
= repo
.workingctx()
169 from mercurial
import context
170 wctx
= context
.workingctx(repo
)
176 def open(self
, filename
, mode
):
177 filename
= self
.repo
.wjoin(filename
)
180 f
= file(filename
, mode
)
182 print 'could not open file %s: %s' % (filename
, msg
)
187 def skip(self
, filename
):
188 filename
= self
.repo
.wjoin(filename
)
190 # We never want to handle symlinks, so always skip them: If the location
191 # pointed to is a directory, skip it. If the location is a file inside
192 # the gem5 directory, it will be checked as a file, so symlink can be
193 # skipped. If the location is a file outside gem5, we don't want to
195 if os
.path
.islink(filename
):
197 return lang_type(filename
) not in self
.languages
199 def check(self
, filename
, regions
=all_regions
):
200 f
= self
.open(filename
, 'r')
203 for num
,line
in enumerate(f
):
204 if num
not in regions
:
206 if not self
.check_line(line
):
207 self
.write("invalid %s in %s:%d\n" % \
208 (self
.test_name
, filename
, num
+ 1))
210 self
.write(">>%s<<\n" % line
[-1])
214 def fix(self
, filename
, regions
=all_regions
):
215 f
= self
.open(filename
, 'r+')
222 for i
,line
in enumerate(lines
):
224 line
= self
.fix_line(line
)
229 def apply(self
, filename
, prompt
, regions
=all_regions
):
230 if not self
.skip(filename
):
231 errors
= self
.check(filename
, regions
)
233 if prompt(filename
, self
.fix
, regions
):
238 class Whitespace(Verifier
):
239 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
240 test_name
= 'whitespace'
241 def check_line(self
, line
):
242 match
= lead
.search(line
)
243 if match
and match
.group(1).find('\t') != -1:
246 match
= trail
.search(line
)
252 def fix_line(self
, line
):
253 if lead
.search(line
):
255 for i
,c
in enumerate(line
):
259 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
266 return line
.rstrip() + '\n'
268 class SortedIncludes(Verifier
):
269 languages
= sort_includes
.default_languages
270 def __init__(self
, *args
, **kwargs
):
271 super(SortedIncludes
, self
).__init
__(*args
, **kwargs
)
272 self
.sort_includes
= sort_includes
.SortIncludes()
274 def check(self
, filename
, regions
=all_regions
):
275 f
= self
.open(filename
, 'r')
277 lines
= [ l
.rstrip('\n') for l
in f
.xreadlines() ]
278 old
= ''.join(line
+ '\n' for line
in lines
)
284 language
= lang_type(filename
, lines
[0])
285 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
286 new
= ''.join(line
+ '\n' for line
in sort_lines
)
288 mod
= modified_regions(old
, new
)
289 modified
= mod
& regions
292 self
.write("invalid sorting of includes in %s\n" % (filename
))
294 for start
, end
in modified
.regions
:
295 self
.write("bad region [%d, %d)\n" % (start
, end
))
300 def fix(self
, filename
, regions
=all_regions
):
301 f
= self
.open(filename
, 'r+')
304 lines
= [ l
.rstrip('\n') for l
in old
]
305 language
= lang_type(filename
, lines
[0])
306 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
307 new
= ''.join(line
+ '\n' for line
in sort_lines
)
312 for i
,line
in enumerate(sort_lines
):
318 tabs
= line
.count('\t')
325 count
+= tabsize
- count
% tabsize
331 class ValidationStats(object):
342 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
343 %d cases of whitespace at the end of a line.
344 %d cases of tabs to indent.
345 %d bad parens after if/while/for.
346 %d carriage returns found.
347 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
348 self
.badcontrol
, self
.cret
)
350 def __nonzero__(self
):
351 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
352 self
.trailwhite
or self
.badcontrol
or self
.cret
354 def validate(filename
, stats
, verbose
, exit_code
):
355 lang
= lang_type(filename
)
356 if lang
not in format_types
:
359 def msg(lineno
, line
, message
):
360 print '%s:%d>' % (filename
, lineno
+ 1), message
365 if exit_code
is not None:
369 f
= file(filename
, 'r')
372 print 'could not open file %s' % filename
376 for i
,line
in enumerate(f
):
377 line
= line
.rstrip('\n')
379 # no carriage returns
380 if line
.find('\r') != -1:
383 msg(i
, line
, 'carriage return found')
386 # lines max out at 79 chars
393 msg(i
, line
, 'line too long (%d chars)' % llen
)
396 # no tabs used to indent
397 match
= lead
.search(line
)
398 if match
and match
.group(1).find('\t') != -1:
401 msg(i
, line
, 'using tabs to indent')
404 # no trailing whitespace
405 if trail
.search(line
):
408 msg(i
, line
, 'trailing whitespace')
411 # for c++, exactly one space betwen if/while/for and (
413 match
= any_control
.search(line
)
414 if match
and not good_control
.search(line
):
415 stats
.badcontrol
+= 1
417 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
421 def _modified_regions(repo
, patterns
, **kwargs
):
422 opt_all
= kwargs
.get('all', False)
423 opt_no_ignore
= kwargs
.get('no_ignore', False)
425 # Import the match (repository file name matching helper)
426 # function. Different versions of Mercurial keep it in different
427 # modules and implement them differently.
429 from mercurial
import scmutil
430 m
= scmutil
.match(repo
[None], patterns
, kwargs
)
432 from mercurial
import cmdutil
433 m
= cmdutil
.match(repo
, patterns
, kwargs
)
435 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= \
436 repo
.status(match
=m
, clean
=opt_all
)
440 wctx
= repo
.workingctx()
442 from mercurial
import context
443 wctx
= context
.workingctx(repo
)
445 files
= [ (fn
, all_regions
) for fn
in added
] + \
446 [ (fn
, modregions(wctx
, fn
)) for fn
in modified
]
448 files
= [ (fn
, all_regions
) for fn
in added
+ modified
+ clean
]
450 for fname
, mod_regions
in files
:
451 if opt_no_ignore
or not check_ignores(fname
):
452 yield fname
, mod_regions
455 def do_check_style(hgui
, repo
, *pats
, **opts
):
456 """check files for proper m5 style guidelines
458 Without an argument, checks all modified and added files for gem5
459 coding style violations. A list of files can be specified to limit
460 the checker to a subset of the repository. The style rules are
461 normally applied on a diff of the repository state (i.e., added
462 files are checked in their entirety while only modifications of
463 modified files are checked).
465 The --all option can be specified to include clean files and check
466 modified files in their entirety.
468 opt_fix_all
= opts
.get('fix_all', False)
470 opt_fix_white
= opts
.get('fix_white', False)
471 opt_fix_include
= opts
.get('fix_include', False)
474 opt_fix_include
= True
476 ui
= MercurialUI(hgui
, verbose
=hgui
.verbose
)
478 def prompt(name
, func
, regions
=all_regions
):
479 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
487 def no_prompt(name
, func
, regions
=all_regions
):
491 prompt_white
= prompt
if not opt_fix_white
else no_prompt
492 prompt_include
= prompt
if not opt_fix_include
else no_prompt
494 whitespace
= Whitespace(ui
, repo
)
495 sorted_includes
= SortedIncludes(ui
, repo
)
496 for fname
, mod_regions
in _modified_regions(repo
, pats
, **opts
):
497 if whitespace
.apply(fname
, prompt_white
, mod_regions
):
500 if sorted_includes
.apply(fname
, prompt_include
, mod_regions
):
505 def do_check_format(hgui
, repo
, *pats
, **opts
):
506 """check files for gem5 code formatting violations
508 Without an argument, checks all modified and added files for gem5
509 code formatting violations. A list of files can be specified to
510 limit the checker to a subset of the repository. The style rules
511 are normally applied on a diff of the repository state (i.e.,
512 added files are checked in their entirety while only modifications
513 of modified files are checked).
515 The --all option can be specified to include clean files and check
516 modified files in their entirety.
518 ui
= MercurialUI(hgui
, hgui
.verbose
)
521 for fname
, mod_regions
in _modified_regions(repo
, pats
, **opts
):
522 stats
= ValidationStats()
523 validate(joinpath(repo
.root
, fname
), stats
, verbose
, None)
527 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
534 def check_hook(hooktype
):
535 if hooktype
not in ('pretxncommit', 'pre-qrefresh'):
536 raise AttributeError, \
537 "This hook is not meant for %s" % hooktype
539 def check_style(ui
, repo
, hooktype
, **kwargs
):
544 return do_check_style(ui
, repo
, **args
)
547 traceback
.print_exc()
550 def check_format(ui
, repo
, hooktype
, **kwargs
):
555 return do_check_format(ui
, repo
, **args
)
558 traceback
.print_exc()
562 from mercurial
.i18n
import _
567 _common_region_options
= [
569 _("include clean files and unmodified parts of modified files")),
570 ('', 'no-ignore', False, _("ignore the style ignore list")),
576 ('f', 'fix-all', False, _("automatically fix style issues")),
577 ('', 'fix-white', False, _("automatically fix white space issues")),
578 ('', 'fix-include', False, _("automatically fix include ordering")),
579 ] + _common_region_options
+ commands
.walkopts
,
580 _('hg m5style [-a] [FILE]...')),
583 ] + _common_region_options
+ commands
.walkopts
,
584 _('hg m5format [FILE]...')),
587 if __name__
== '__main__':
590 progname
= sys
.argv
[0]
591 if len(sys
.argv
) < 2:
592 sys
.exit('usage: %s <command> [<command args>]' % progname
)
594 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
595 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
596 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
598 command
= sys
.argv
[1]
599 if command
== 'fixwhite':
601 usage
= fixwhite_usage
602 elif command
== 'chkwhite':
604 usage
= chkwhite_usage
605 elif command
== 'chkformat':
607 usage
= chkformat_usage
609 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
611 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
623 if command
== 'fixwhite':
624 for filename
in args
:
625 fixwhite(filename
, tabsize
)
626 elif command
== 'chkwhite':
627 for filename
in args
:
628 for line
,num
in checkwhite(filename
):
629 print 'invalid whitespace: %s:%d' % (filename
, num
)
631 print '>>%s<<' % line
[:-1]
632 elif command
== 'chkformat':
633 stats
= ValidationStats()
634 for filename
in args
:
635 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
640 sys
.exit("command '%s' not found" % command
)