1 #!/usr/bin/env python2.7
3 # Copyright (c) 2014, 2016 ARM Limited
6 # The license below extends only to copyright in the software and shall
7 # not be construed as granting a license to any other intellectual
8 # property including but not limited to intellectual property relating
9 # to a hardware implementation of the functionality of the software
10 # licensed hereunder. You may use the software subject to the license
11 # terms below provided that you ensure that this notice is replicated
12 # unmodified and in its entirety in all distributions of the software,
13 # modified or unmodified, in source code or in binary form.
15 # Copyright (c) 2006 The Regents of The University of Michigan
16 # Copyright (c) 2007,2011 The Hewlett-Packard Development Company
17 # Copyright (c) 2016 Advanced Micro Devices, Inc.
18 # All rights reserved.
20 # Redistribution and use in source and binary forms, with or without
21 # modification, are permitted provided that the following conditions are
22 # met: redistributions of source code must retain the above copyright
23 # notice, this list of conditions and the following disclaimer;
24 # redistributions in binary form must reproduce the above copyright
25 # notice, this list of conditions and the following disclaimer in the
26 # documentation and/or other materials provided with the distribution;
27 # neither the name of the copyright holders nor the names of its
28 # contributors may be used to endorse or promote products derived from
29 # this software without specific prior written permission.
31 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 # Authors: Nathan Binkert
47 from abc
import ABCMeta
, abstractmethod
48 from difflib
import SequenceMatcher
57 from file_types
import lang_type
60 def safefix(fix_func
):
61 """ Decorator for the fix functions of the Verifier class.
62 This function wraps the fix function and creates a backup file
63 just in case there is an error.
65 def safefix_wrapper(*args
, **kwargs
):
66 # Check to be sure that this is decorating a function we expect:
67 # a class method with filename as the first argument (after self)
68 assert(os
.path
.exists(args
[1]))
70 assert(is_verifier(self
.__class
__))
73 # Now, Let's make a backup file.
74 from shutil
import copyfile
75 backup_name
= filename
+'.bak'
76 copyfile(filename
, backup_name
)
78 # Try to apply the fix. If it fails, then we revert the file
79 # Either way, we need to clean up our backup file
81 fix_func(*args
, **kwargs
)
82 except Exception as e
:
83 # Restore the original file to the backup file
84 self
.ui
.write("Error! Restoring the original file.\n")
85 copyfile(backup_name
, filename
)
88 # Clean up the backup file
89 os
.remove(backup_name
)
91 return safefix_wrapper
93 def _modified_regions(old
, new
):
95 m
= SequenceMatcher(a
=old
, b
=new
, autojunk
=False)
97 # autojunk was introduced in Python 2.7. We need a fallback
98 # mechanism to support old Python versions.
99 m
= SequenceMatcher(a
=old
, b
=new
)
101 for tag
, i1
, i2
, j1
, j2
in m
.get_opcodes():
103 regions
.extend(Region(i1
, i2
))
107 class Verifier(object):
108 """Base class for style verifiers
110 Verifiers check for style violations and optionally fix such
111 violations. Implementations should either inherit from this class
112 (Verifier) if they need to work on entire files or LineVerifier if
113 they operate on a line-by-line basis.
115 Subclasses must define these class attributes:
116 languages = set of strings identifying applicable languages
117 test_name = long descriptive name of test, will be used in
118 messages such as "error in <foo>" or "invalid <foo>"
119 opt_name = short name used to generate command-line options to
120 control the test (--fix-<foo>, --ignore-<foo>, etc.)
124 __metaclass__
= ABCMeta
126 def __init__(self
, ui
, opts
, base
=None):
130 # opt_name must be defined as a class attribute of derived classes.
131 # Check test-specific opts first as these have precedence.
132 self
.opt_fix
= opts
.get('fix_' + self
.opt_name
, False)
133 self
.opt_ignore
= opts
.get('ignore_' + self
.opt_name
, False)
134 self
.opt_skip
= opts
.get('skip_' + self
.opt_name
, False)
135 # If no test-specific opts were set, then set based on "-all" opts.
136 if not (self
.opt_fix
or self
.opt_ignore
or self
.opt_skip
):
137 self
.opt_fix
= opts
.get('fix_all', False)
138 self
.opt_ignore
= opts
.get('ignore_all', False)
139 self
.opt_skip
= opts
.get('skip_all', False)
141 def normalize_filename(self
, name
):
142 abs_name
= os
.path
.abspath(name
)
143 if self
.base
is None:
146 abs_base
= os
.path
.abspath(self
.base
)
147 return os
.path
.relpath(abs_name
, start
=abs_base
)
149 def open(self
, filename
, mode
):
151 f
= file(filename
, mode
)
153 print 'could not open file %s: %s' % (filename
, msg
)
158 def skip(self
, filename
):
159 # We never want to handle symlinks, so always skip them: If the
160 # location pointed to is a directory, skip it. If the location is a
161 # file inside the gem5 directory, it will be checked as a file, so
162 # symlink can be skipped. If the location is a file outside gem5, we
163 # don't want to check it anyway.
164 if os
.path
.islink(filename
):
166 return lang_type(filename
) not in self
.languages
168 def apply(self
, filename
, regions
=all_regions
):
169 """Possibly apply to specified regions of file 'filename'.
171 Verifier is skipped if --skip-<test> option was provided or if
172 file is not of an applicable type. Otherwise file is checked
173 and error messages printed. Errors are fixed or ignored if
174 the corresponding --fix-<test> or --ignore-<test> options were
175 provided. If neither, the user is prompted for an action.
177 Returns True to abort, False otherwise.
179 if not (self
.opt_skip
or self
.skip(filename
)):
180 errors
= self
.check(filename
, regions
)
181 if errors
and not self
.opt_ignore
:
183 self
.fix(filename
, regions
)
185 result
= self
.ui
.prompt("(a)bort, (i)gnore, or (f)ix?",
188 self
.fix(filename
, regions
)
195 def check(self
, filename
, regions
=all_regions
, fobj
=None, silent
=False):
196 """Check specified regions of file 'filename'.
198 Given that it is possible that the current contents of the file
199 differ from the file as 'staged to commit', for those cases, and
200 maybe others, the argument fobj should be a file object open and reset
201 with the contents matching what the file would look like after the
202 commit. This is needed keep the messages using 'filename' meaningful.
204 The argument silent is useful to prevent output when we run check in
205 the staged file vs the actual file to detect if the user forgot
206 staging fixes to the commit. This way, we prevent reporting errors
209 Line-by-line checks can simply provide a check_line() method
210 that returns True if the line is OK and False if it has an
211 error. Verifiers that need a multi-line view (like
212 SortedIncludes) must override this entire function.
214 Returns a count of errors (0 if none), though actual non-zero
215 count value is not currently used anywhere.
220 def fix(self
, filename
, regions
=all_regions
):
221 """Fix specified regions of file 'filename'.
223 Line-by-line fixes can simply provide a fix_line() method that
224 returns the fixed line. Verifiers that need a multi-line view
225 (like SortedIncludes) must override this entire function.
229 class LineVerifier(Verifier
):
230 def check(self
, filename
, regions
=all_regions
, fobj
=None, silent
=False):
233 fobj
= self
.open(filename
, 'r')
236 lang
= lang_type(filename
)
237 assert lang
in self
.languages
240 for num
,line
in enumerate(fobj
):
241 if num
not in regions
:
243 line
= line
.rstrip('\n')
244 if not self
.check_line(line
, language
=lang
):
246 self
.ui
.write("invalid %s in %s:%d\n" % \
247 (self
.test_name
, filename
, num
+ 1))
249 self
.ui
.write(">>%s<<\n" % line
[:-1])
256 def fix(self
, filename
, regions
=all_regions
):
257 f
= self
.open(filename
, 'r+')
259 lang
= lang_type(filename
)
260 assert lang
in self
.languages
267 for i
,line
in enumerate(lines
):
268 line
= line
.rstrip('\n')
270 line
= self
.fix_line(line
, language
=lang
)
275 self
.current_language
= None
278 def check_line(self
, line
, **kwargs
):
282 def fix_line(self
, line
, **kwargs
):
285 class Whitespace(LineVerifier
):
289 - No tabs used for indent
290 - No trailing whitespace
293 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons',
295 trail_only
= set(('make', 'dts'))
297 test_name
= 'whitespace'
300 _lead
= re
.compile(r
'^([ \t]+)')
301 _trail
= re
.compile(r
'([ \t]+)$')
304 def skip_lead(self
, language
):
305 return language
in Whitespace
.trail_only
307 def check_line(self
, line
, language
):
308 if not self
.skip_lead(language
):
309 match
= Whitespace
._lead
.search(line
)
310 if match
and match
.group(1).find('\t') != -1:
313 match
= Whitespace
._trail
.search(line
)
319 def fix_line(self
, line
, language
):
320 if not self
.skip_lead(language
) and Whitespace
._lead
.search(line
):
322 for i
,c
in enumerate(line
):
326 newline
+= ' ' * (style
.tabsize
- \
327 len(newline
) % style
.tabsize
)
337 class SortedIncludes(Verifier
):
338 """Check for proper sorting of include statements"""
340 languages
= sort_includes
.default_languages
341 test_name
= 'include file order'
344 def __init__(self
, *args
, **kwargs
):
345 super(SortedIncludes
, self
).__init
__(*args
, **kwargs
)
346 self
.sort_includes
= sort_includes
.SortIncludes()
348 def check(self
, filename
, regions
=all_regions
, fobj
=None, silent
=False):
351 fobj
= self
.open(filename
, 'r')
353 norm_fname
= self
.normalize_filename(filename
)
355 old
= [ l
.rstrip('\n') for l
in fobj
.xreadlines() ]
362 language
= lang_type(filename
, old
[0])
363 new
= list(self
.sort_includes(old
, norm_fname
, language
))
365 modified
= _modified_regions(old
, new
) & regions
369 self
.ui
.write("invalid sorting of includes in %s\n"
372 for start
, end
in modified
.regions
:
373 self
.ui
.write("bad region [%d, %d)\n" % (start
, end
))
379 def fix(self
, filename
, regions
=all_regions
):
380 f
= self
.open(filename
, 'r+')
381 norm_fname
= self
.normalize_filename(filename
)
384 lines
= [ l
.rstrip('\n') for l
in old
]
385 language
= lang_type(filename
, lines
[0])
386 sort_lines
= list(self
.sort_includes(lines
, norm_fname
, language
))
387 new
= ''.join(line
+ '\n' for line
in sort_lines
)
392 for i
,line
in enumerate(sort_lines
):
398 class ControlSpace(LineVerifier
):
399 """Check for exactly one space after if/while/for"""
401 languages
= set(('C', 'C++'))
402 test_name
= 'spacing after if/while/for'
405 _any_control
= re
.compile(r
'\b(if|while|for)([ \t]*)\(')
407 def check_line(self
, line
, **kwargs
):
408 match
= ControlSpace
._any
_control
.search(line
)
409 return not (match
and match
.group(2) != " ")
411 def fix_line(self
, line
, **kwargs
):
412 new_line
= ControlSpace
._any
_control
.sub(r
'\1 (', line
)
416 class LineLength(LineVerifier
):
417 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
418 test_name
= 'line length'
421 def check_line(self
, line
, **kwargs
):
422 return style
.normalized_len(line
) <= 79
424 def fix(self
, filename
, regions
=all_regions
, **kwargs
):
425 self
.ui
.write("Warning: cannot automatically fix overly long lines.\n")
427 def fix_line(self
, line
):
430 class ControlCharacters(LineVerifier
):
431 languages
= set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
432 test_name
= 'control character'
436 invalid
= "".join([chr(i
) for i
in range(0, 0x20) if chr(i
) not in valid
])
438 def check_line(self
, line
, **kwargs
):
439 return self
.fix_line(line
) == line
441 def fix_line(self
, line
, **kwargs
):
442 return line
.translate(None, ControlCharacters
.invalid
)
444 class BoolCompare(LineVerifier
):
445 languages
= set(('C', 'C++', 'python'))
446 test_name
= 'boolean comparison'
447 opt_name
= 'boolcomp'
449 regex
= re
.compile(r
'\s*==\s*([Tt]rue|[Ff]alse)\b')
451 def check_line(self
, line
, **kwargs
):
452 return self
.regex
.search(line
) == None
454 def fix_line(self
, line
, **kwargs
):
455 match
= self
.regex
.search(line
)
457 if match
.group(1) in ('true', 'True'):
458 line
= self
.regex
.sub('', line
)
460 self
.ui
.write("Warning: cannot automatically fix "
461 "comparisons with false/False.\n")
464 def is_verifier(cls
):
465 """Determine if a class is a Verifier that can be instantiated"""
467 return inspect
.isclass(cls
) and issubclass(cls
, Verifier
) and \
468 not inspect
.isabstract(cls
)
470 # list of all verifier classes
471 all_verifiers
= [ v
for n
, v
in \
472 inspect
.getmembers(sys
.modules
[__name__
], is_verifier
) ]