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
):
165 def __getattr__(self
, attr
):
166 if attr
in ('prompt', 'write'):
167 return getattr(self
.ui
, attr
)
171 wctx
= repo
.workingctx()
173 from mercurial
import context
174 wctx
= context
.workingctx(repo
)
180 def open(self
, filename
, mode
):
181 filename
= self
.repo
.wjoin(filename
)
184 f
= file(filename
, mode
)
186 print 'could not open file %s: %s' % (filename
, msg
)
191 def skip(self
, filename
):
192 filename
= self
.repo
.wjoin(filename
)
194 # We never want to handle symlinks, so always skip them: If the location
195 # pointed to is a directory, skip it. If the location is a file inside
196 # the gem5 directory, it will be checked as a file, so symlink can be
197 # skipped. If the location is a file outside gem5, we don't want to
199 if os
.path
.islink(filename
):
201 return lang_type(filename
) not in self
.languages
203 def check(self
, filename
, regions
=all_regions
):
204 f
= self
.open(filename
, 'r')
207 for num
,line
in enumerate(f
):
208 if num
not in regions
:
210 if not self
.check_line(line
):
211 self
.write("invalid %s in %s:%d\n" % \
212 (self
.test_name
, filename
, num
+ 1))
214 self
.write(">>%s<<\n" % line
[-1])
218 def fix(self
, filename
, regions
=all_regions
):
219 f
= self
.open(filename
, 'r+')
226 for i
,line
in enumerate(lines
):
228 line
= self
.fix_line(line
)
233 def apply(self
, filename
, prompt
, regions
=all_regions
):
234 if not self
.skip(filename
):
235 errors
= self
.check(filename
, regions
)
237 if prompt(filename
, self
.fix
, regions
):
242 class Whitespace(Verifier
):
243 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
244 test_name
= 'whitespace'
245 def check_line(self
, line
):
246 match
= lead
.search(line
)
247 if match
and match
.group(1).find('\t') != -1:
250 match
= trail
.search(line
)
256 def fix_line(self
, line
):
257 if lead
.search(line
):
259 for i
,c
in enumerate(line
):
263 newline
+= ' ' * (tabsize
- len(newline
) % tabsize
)
270 return line
.rstrip() + '\n'
272 class SortedIncludes(Verifier
):
273 languages
= sort_includes
.default_languages
274 def __init__(self
, *args
, **kwargs
):
275 super(SortedIncludes
, self
).__init
__(*args
, **kwargs
)
276 self
.sort_includes
= sort_includes
.SortIncludes()
278 def check(self
, filename
, regions
=all_regions
):
279 f
= self
.open(filename
, 'r')
281 lines
= [ l
.rstrip('\n') for l
in f
.xreadlines() ]
282 old
= ''.join(line
+ '\n' for line
in lines
)
288 language
= lang_type(filename
, lines
[0])
289 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
290 new
= ''.join(line
+ '\n' for line
in sort_lines
)
292 mod
= modified_regions(old
, new
)
293 modified
= mod
& regions
296 self
.write("invalid sorting of includes in %s\n" % (filename
))
298 for start
, end
in modified
.regions
:
299 self
.write("bad region [%d, %d)\n" % (start
, end
))
304 def fix(self
, filename
, regions
=all_regions
):
305 f
= self
.open(filename
, 'r+')
308 lines
= [ l
.rstrip('\n') for l
in old
]
309 language
= lang_type(filename
, lines
[0])
310 sort_lines
= list(self
.sort_includes(lines
, filename
, language
))
311 new
= ''.join(line
+ '\n' for line
in sort_lines
)
316 for i
,line
in enumerate(sort_lines
):
322 tabs
= line
.count('\t')
329 count
+= tabsize
- count
% tabsize
335 class ValidationStats(object):
346 %d violations of lines over 79 chars. %d of which are 80 chars exactly.
347 %d cases of whitespace at the end of a line.
348 %d cases of tabs to indent.
349 %d bad parens after if/while/for.
350 %d carriage returns found.
351 ''' % (self
.toolong
, self
.toolong80
, self
.trailwhite
, self
.leadtabs
,
352 self
.badcontrol
, self
.cret
)
354 def __nonzero__(self
):
355 return self
.toolong
or self
.toolong80
or self
.leadtabs
or \
356 self
.trailwhite
or self
.badcontrol
or self
.cret
358 def validate(filename
, stats
, verbose
, exit_code
):
359 if lang_type(filename
) not in format_types
:
362 def msg(lineno
, line
, message
):
363 print '%s:%d>' % (filename
, lineno
+ 1), message
368 if exit_code
is not None:
372 f
= file(filename
, 'r')
375 print 'could not open file %s' % filename
379 for i
,line
in enumerate(f
):
380 line
= line
.rstrip('\n')
382 # no carriage returns
383 if line
.find('\r') != -1:
386 msg(i
, line
, 'carriage return found')
389 # lines max out at 79 chars
396 msg(i
, line
, 'line too long (%d chars)' % llen
)
399 # no tabs used to indent
400 match
= lead
.search(line
)
401 if match
and match
.group(1).find('\t') != -1:
404 msg(i
, line
, 'using tabs to indent')
407 # no trailing whitespace
408 if trail
.search(line
):
411 msg(i
, line
, 'trailing whitespace')
414 # for c++, exactly one space betwen if/while/for and (
416 match
= any_control
.search(line
)
417 if match
and not good_control
.search(line
):
418 stats
.badcontrol
+= 1
420 msg(i
, line
, 'improper spacing after %s' % match
.group(1))
424 def do_check_style(hgui
, repo
, *pats
, **opts
):
425 """check files for proper m5 style guidelines
427 Without an argument, checks all modified and added files for gem5
428 coding style violations. A list of files can be specified to limit
429 the checker to a subset of the repository. The style rules are
430 normally applied on a diff of the repository state (i.e., added
431 files are checked in their entirety while only modifications of
432 modified files are checked).
434 The --all option can be specified to include clean files and check
435 modified files in their entirety.
437 from mercurial
import mdiff
, util
439 opt_fix_white
= opts
.get('fix_white', False)
440 opt_all
= opts
.get('all', False)
441 opt_no_ignore
= opts
.get('no_ignore', False)
442 ui
= MercurialUI(hgui
, hgui
.verbose
, opt_fix_white
)
444 def prompt(name
, func
, regions
=all_regions
):
445 result
= ui
.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a')
454 # Import the match (repository file name matching helper)
455 # function. Different versions of Mercurial keep it in different
456 # modules and implement them differently.
458 from mercurial
import scmutil
459 m
= scmutil
.match(repo
[None], pats
, opts
)
461 from mercurial
import cmdutil
462 m
= cmdutil
.match(repo
, pats
, opts
)
464 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= \
465 repo
.status(match
=m
, clean
=opt_all
)
468 wctx
= repo
.workingctx()
470 from mercurial
import context
471 wctx
= context
.workingctx(repo
)
473 files
= [ (fn
, all_regions
) for fn
in added
] + \
474 [ (fn
, modregions(wctx
, fn
)) for fn
in modified
]
476 files
= [ (fn
, all_regions
) for fn
in added
+ modified
+ clean
]
478 whitespace
= Whitespace(ui
, repo
)
479 sorted_includes
= SortedIncludes(ui
, repo
)
480 for fname
, mod_regions
in files
:
481 if not opt_no_ignore
and check_ignores(fname
):
484 if whitespace
.apply(fname
, prompt
, mod_regions
):
487 if sorted_includes
.apply(fname
, prompt
, mod_regions
):
492 def do_check_format(hgui
, repo
, **args
):
493 ui
= MercurialUI(hgui
, hgui
.verbose
, auto
)
495 modified
, added
, removed
, deleted
, unknown
, ignore
, clean
= repo
.status()
498 stats
= ValidationStats()
499 for f
in modified
+ added
:
500 validate(joinpath(repo
.root
, f
), stats
, verbose
, None)
504 result
= ui
.prompt("invalid formatting\n(i)gnore or (a)bort?",
511 def check_hook(hooktype
):
512 if hooktype
not in ('pretxncommit', 'pre-qrefresh'):
513 raise AttributeError, \
514 "This hook is not meant for %s" % hooktype
516 def check_style(ui
, repo
, hooktype
, **kwargs
):
521 return do_check_style(ui
, repo
, **args
)
524 traceback
.print_exc()
527 def check_format(ui
, repo
, hooktype
, **kwargs
):
532 return do_check_format(ui
, repo
, **args
)
535 traceback
.print_exc()
539 from mercurial
.i18n
import _
547 ('w', 'fix-white', False, _("automatically fix whitespace")),
549 _("include clean files and unmodified parts of modified files")),
550 ('', 'no-ignore', False, _("ignore the style ignore list")),
551 ] + commands
.walkopts
,
552 _('hg m5style [-a] [FILE]...')),
556 _('hg m5format [FILE]...')),
559 if __name__
== '__main__':
562 progname
= sys
.argv
[0]
563 if len(sys
.argv
) < 2:
564 sys
.exit('usage: %s <command> [<command args>]' % progname
)
566 fixwhite_usage
= '%s fixwhite [-t <tabsize> ] <path> [...] \n' % progname
567 chkformat_usage
= '%s chkformat <path> [...] \n' % progname
568 chkwhite_usage
= '%s chkwhite <path> [...] \n' % progname
570 command
= sys
.argv
[1]
571 if command
== 'fixwhite':
573 usage
= fixwhite_usage
574 elif command
== 'chkwhite':
576 usage
= chkwhite_usage
577 elif command
== 'chkformat':
579 usage
= chkformat_usage
581 sys
.exit(fixwhite_usage
+ chkwhite_usage
+ chkformat_usage
)
583 opts
, args
= getopt
.getopt(sys
.argv
[2:], flags
)
595 if command
== 'fixwhite':
596 for filename
in args
:
597 fixwhite(filename
, tabsize
)
598 elif command
== 'chkwhite':
599 for filename
in args
:
600 for line
,num
in checkwhite(filename
):
601 print 'invalid whitespace: %s:%d' % (filename
, num
)
603 print '>>%s<<' % line
[:-1]
604 elif command
== 'chkformat':
605 stats
= ValidationStats()
606 for filename
in args
:
607 validate(filename
, stats
=stats
, verbose
=verbose
, exit_code
=code
)
612 sys
.exit("command '%s' not found" % command
)