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, auto
=False):
131 self
.verbose
= verbose
133 def prompt(self
, prompt
, results
, default
):
138 result
= self
.do_prompt(prompt
, results
, default
)
139 if result
in results
:
142 class MercurialUI(UserInterface
):
143 def __init__(self
, ui
, *args
, **kwargs
):
144 super(MercurialUI
, self
).__init
__(*args
, **kwargs
)
147 def do_prompt(self
, prompt
, results
, default
):
148 return self
.ui
.prompt(prompt
, default
=default
)
150 def write(self
, string
):
151 self
.ui
.write(string
)
153 class StdioUI(UserInterface
):
154 def do_prompt(self
, prompt
, results
, default
):
155 return raw_input(prompt
) or default
157 def write(self
, string
):
158 sys
.stdout
.write(string
)
160 class Verifier(object):
161 def __init__(self
, ui
, repo
=None):
167 def __getattr__(self
, attr
):
168 if attr
in ('prompt', 'write'):
169 return getattr(self
.ui
, attr
)
173 wctx
= repo
.workingctx()
175 from mercurial
import context
176 wctx
= context
.workingctx(repo
)
182 def open(self
, filename
, mode
):
184 filename
= self
.repo
.wjoin(filename
)
187 f
= file(filename
, mode
)
189 print 'could not open file %s: %s' % (filename
, msg
)
194 def skip(self
, filename
):
195 # We never want to handle symlinks, so always skip them: If the location
196 # pointed to is a directory, skip it. If the location is a file inside
197 # the gem5 directory, it will be checked as a file, so symlink can be
198 # skipped. If the location is a file outside gem5, we don't want to
200 if os
.path
.islink(filename
):
202 return lang_type(filename
) not in self
.languages
204 def check(self
, filename
, regions
=all_regions
):
205 f
= self
.open(filename
, 'r')
208 for num
,line
in enumerate(f
):
209 if num
not in regions
:
211 if not self
.check_line(line
):
212 self
.write("invalid %s in %s:%d\n" % \
213 (self
.test_name
, filename
, num
+ 1))
215 self
.write(">>%s<<\n" % line
[-1])
219 def fix(self
, filename
, regions
=all_regions
):
220 f
= self
.open(filename
, 'r+')
227 for i
,line
in enumerate(lines
):
229 line
= self
.fix_line(line
)
234 def apply(self
, filename
, prompt
, regions
=all_regions
):
235 if not self
.skip(filename
):
236 errors
= self
.check(filename
, regions
)
238 if prompt(filename
, self
.fix
, regions
):
243 class Whitespace(Verifier
):
244 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
245 test_name
= 'whitespace'
246 def check_line(self
, line
):
247 match
= lead
.search(line
)
248 if match
and match
.group(1).find('\t') != -1:
251 match
= trail
.search(line
)
257 def fix_line(self
, line
):
258 if lead
.search(line
):
260 for i
,c
in enumerate(line
):
264 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
271 return line
.rstrip() + '\n'
273 class SortedIncludes(Verifier
):
274 languages
= sort_includes
.default_languages
275 def __init__(self
, *args
, **kwargs
):
276 super(SortedIncludes
, self
).__init
__(*args
, **kwargs
)
277 self
.sort_includes
= sort_includes
.SortIncludes()
279 def check(self
, filename
, regions
=all_regions
):
280 f
= self
.open(filename
, 'r')
282 lines
= [ l
.rstrip('\n') for l
in f
.xreadlines() ]
283 old
= ''.join(line
+ '\n' for line
in lines
)
289 language
= lang_type(filename
, lines
[0])
290 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
291 new
= ''.join(line
+ '\n' for line
in sort_lines
)
293 mod
= modified_regions(old
, new
)
294 modified
= mod
& regions
297 self
.write("invalid sorting of includes in %s\n" % (filename
))
299 for start
, end
in modified
.regions
:
300 self
.write("bad region [%d, %d)\n" % (start
, end
))
305 def fix(self
, filename
, regions
=all_regions
):
306 f
= self
.open(filename
, 'r+')
309 lines
= [ l
.rstrip('\n') for l
in old
]
310 language
= lang_type(filename
, lines
[0])
311 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
312 new
= ''.join(line
+ '\n' for line
in sort_lines
)
317 for i
,line
in enumerate(sort_lines
):
323 tabs
= line
.count('\t')
330 count
+= tabsize
- count
% tabsize
336 class ValidationStats(object):
347 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
348 %d cases of whitespace at the end of a line.
349 %d cases of tabs to indent.
350 %d bad parens after if/while/for.
351 %d carriage returns found.
352 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
353 self
.badcontrol
, self
.cret
)
355 def __nonzero__(self
):
356 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
357 self
.trailwhite
or self
.badcontrol
or self
.cret
359 def validate(filename
, stats
, verbose
, exit_code
):
360 if lang_type(filename
) not in format_types
:
363 def msg(lineno
, line
, message
):
364 print '%s:%d>' % (filename
, lineno
+ 1), message
369 if exit_code
is not None:
373 f
= file(filename
, 'r')
376 print 'could not open file %s' % filename
380 for i
,line
in enumerate(f
):
381 line
= line
.rstrip('\n')
383 # no carriage returns
384 if line
.find('\r') != -1:
387 msg(i
, line
, 'carriage return found')
390 # lines max out at 79 chars
397 msg(i
, line
, 'line too long (%d chars)' % llen
)
400 # no tabs used to indent
401 match
= lead
.search(line
)
402 if match
and match
.group(1).find('\t') != -1:
405 msg(i
, line
, 'using tabs to indent')
408 # no trailing whitespace
409 if trail
.search(line
):
412 msg(i
, line
, 'trailing whitespace')
415 # for c++, exactly one space betwen if/while/for and (
417 match
= any_control
.search(line
)
418 if match
and not good_control
.search(line
):
419 stats
.badcontrol
+= 1
421 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
425 def do_check_style(hgui
, repo
, *pats
, **opts
):
426 """check files for proper m5 style guidelines
428 Without an argument, checks all modified and added files for gem5
429 coding style violations. A list of files can be specified to limit
430 the checker to a subset of the repository. The style rules are
431 normally applied on a diff of the repository state (i.e., added
432 files are checked in their entirety while only modifications of
433 modified files are checked).
435 The --all option can be specified to include clean files and check
436 modified files in their entirety.
438 from mercurial
import mdiff
, util
440 opt_fix_white
= opts
.get('fix_white', False)
441 opt_all
= opts
.get('all', False)
442 opt_no_ignore
= opts
.get('no_ignore', False)
443 ui
= MercurialUI(hgui
, hgui
.verbose
, opt_fix_white
)
445 def prompt(name
, func
, regions
=all_regions
):
446 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
450 func(repo
.wjoin(name
), regions
)
455 # Import the match (repository file name matching helper)
456 # function. Different versions of Mercurial keep it in different
457 # modules and implement them differently.
459 from mercurial
import scmutil
460 m
= scmutil
.match(repo
[None], pats
, opts
)
462 from mercurial
import cmdutil
463 m
= cmdutil
.match(repo
, pats
, opts
)
465 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= \
466 repo
.status(match
=m
, clean
=opt_all
)
469 wctx
= repo
.workingctx()
471 from mercurial
import context
472 wctx
= context
.workingctx(repo
)
474 files
= [ (fn
, all_regions
) for fn
in added
] + \
475 [ (fn
, modregions(wctx
, fn
)) for fn
in modified
]
477 files
= [ (fn
, all_regions
) for fn
in added
+ modified
+ clean
]
479 whitespace
= Whitespace(ui
)
480 sorted_includes
= SortedIncludes(ui
)
481 for fname
, mod_regions
in files
:
482 if not opt_no_ignore
and check_ignores(fname
):
485 fpath
= joinpath(repo
.root
, fname
)
487 if whitespace
.apply(fpath
, prompt
, mod_regions
):
490 if sorted_includes
.apply(fpath
, prompt
, mod_regions
):
495 def do_check_format(hgui
, repo
, **args
):
496 ui
= MercurialUI(hgui
, hgui
.verbose
, auto
)
498 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
501 stats
= ValidationStats()
502 for f
in modified
+ added
:
503 validate(joinpath(repo
.root
, f
), stats
, verbose
, None)
507 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
514 def check_hook(hooktype
):
515 if hooktype
not in ('pretxncommit', 'pre-qrefresh'):
516 raise AttributeError, \
517 "This hook is not meant for %s" % hooktype
519 def check_style(ui
, repo
, hooktype
, **kwargs
):
524 return do_check_style(ui
, repo
, **args
)
527 traceback
.print_exc()
530 def check_format(ui
, repo
, hooktype
, **kwargs
):
535 return do_check_format(ui
, repo
, **args
)
538 traceback
.print_exc()
542 from mercurial
.i18n
import _
550 ('w', 'fix-white', False, _("automatically fix whitespace")),
552 _("include clean files and unmodified parts of modified files")),
553 ('', 'no-ignore', False, _("ignore the style ignore list")),
554 ] + commands
.walkopts
,
555 _('hg m5style [-a] [FILE]...')),
559 _('hg m5format [FILE]...')),
562 if __name__
== '__main__':
565 progname
= sys
.argv
[0]
566 if len(sys
.argv
) < 2:
567 sys
.exit('usage: %s <command> [<command args>]' % progname
)
569 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
570 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
571 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
573 command
= sys
.argv
[1]
574 if command
== 'fixwhite':
576 usage
= fixwhite_usage
577 elif command
== 'chkwhite':
579 usage
= chkwhite_usage
580 elif command
== 'chkformat':
582 usage
= chkformat_usage
584 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
586 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
598 if command
== 'fixwhite':
599 for filename
in args
:
600 fixwhite(filename
, tabsize
)
601 elif command
== 'chkwhite':
602 for filename
in args
:
603 for line
,num
in checkwhite(filename
):
604 print 'invalid whitespace: %s:%d' % (filename
, num
)
606 print '>>%s<<' % line
[:-1]
607 elif command
== 'chkformat':
608 stats
= ValidationStats()
609 for filename
in args
:
610 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
615 sys
.exit("command '%s' not found" % command
)