X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;ds=sidebyside;f=util%2Fstyle.py;h=07505c0e05588127d999328f8e97e3358a3d3905;hb=a1a1adf3bd5d454b281a8747edde1648c23e869e;hp=0676ee1b515a368ce15c117a8efdd92fe3bdcabd;hpb=6402721d8e7a8ad954eab049bf4a49886315b18e;p=gem5.git diff --git a/util/style.py b/util/style.py old mode 100644 new mode 100755 index 0676ee1b5..07505c0e0 --- a/util/style.py +++ b/util/style.py @@ -1,7 +1,16 @@ -#! /usr/bin/env python -# Copyright (c) 2006 The Regents of The University of Michigan -# Copyright (c) 2007,2011 The Hewlett-Packard Development Company -# All rights reserved. +#! /usr/bin/env python3 +# +# Copyright (c) 2016 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -25,525 +34,110 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Authors: Nathan Binkert -import heapq import os -import re import sys -from os.path import dirname, join as joinpath -from itertools import count -from mercurial import bdiff, mdiff - -current_dir = dirname(__file__) -sys.path.insert(0, current_dir) -sys.path.insert(1, joinpath(dirname(current_dir), 'src', 'python')) - -from m5.util import neg_inf, pos_inf, Region, Regions -import sort_includes -from file_types import lang_type - -all_regions = Region(neg_inf, pos_inf) - -tabsize = 8 -lead = re.compile(r'^([ \t]+)') -trail = re.compile(r'([ \t]+)$') -any_control = re.compile(r'\b(if|while|for)[ \t]*[(]') -good_control = re.compile(r'\b(if|while|for) [(]') - -format_types = set(('C', 'C++')) - -def modified_regions(old_data, new_data): - regions = Regions() - beg = None - for pbeg, pend, fbeg, fend in bdiff.blocks(old_data, new_data): - if beg is not None and beg != fbeg: - regions.append(beg, fbeg) - beg = fend - return regions - -def modregions(wctx, fname): - fctx = wctx.filectx(fname) - pctx = fctx.parents() - - file_data = fctx.data() - lines = mdiff.splitnewlines(file_data) - if len(pctx) in (1, 2): - mod_regions = modified_regions(pctx[0].data(), file_data) - if len(pctx) == 2: - m2 = modified_regions(pctx[1].data(), file_data) - # only the lines that are new in both - mod_regions &= m2 - else: - mod_regions = Regions() - mod_regions.add(0, len(lines)) - - return mod_regions - -class UserInterface(object): - def __init__(self, verbose=False, auto=False): - self.auto = auto - self.verbose = verbose - - def prompt(self, prompt, results, default): - if self.auto: - return self.auto - - while True: - result = self.do_prompt(prompt, results, default) - if result in results: - return result - -class MercurialUI(UserInterface): - def __init__(self, ui, *args, **kwargs): - super(MercurialUI, self).__init__(*args, **kwargs) - self.ui = ui - - def do_prompt(self, prompt, results, default): - return self.ui.prompt(prompt, default=default) - - def write(self, string): - self.ui.write(string) - -class StdioUI(UserInterface): - def do_prompt(self, prompt, results, default): - return raw_input(prompt) or default - - def write(self, string): - sys.stdout.write(string) - -class Region(object): - def __init__(self, asdf): - self.regions = Foo - -class Verifier(object): - def __init__(self, ui, repo=None): - self.ui = ui - self.repo = repo - if repo is None: - self.wctx = None - - def __getattr__(self, attr): - if attr in ('prompt', 'write'): - return getattr(self.ui, attr) - - if attr == 'wctx': - try: - wctx = repo.workingctx() - except: - from mercurial import context - wctx = context.workingctx(repo) - self.wctx = wctx - return wctx - - raise AttributeError - - def open(self, filename, mode): - if self.repo: - filename = self.repo.wjoin(filename) - - try: - f = file(filename, mode) - except OSError, msg: - print 'could not open file %s: %s' % (filename, msg) - return None - - return f - - def skip(self, filename): - return lang_type(filename) not in self.languages - - def check(self, filename, regions=all_regions): - f = self.open(filename, 'r') - - errors = 0 - for num,line in enumerate(f): - if num not in regions: - continue - if not self.check_line(line): - self.write("invalid %s in %s:%d\n" % \ - (self.test_name, filename, num + 1)) - if self.ui.verbose: - self.write(">>%s<<\n" % line[-1]) - errors += 1 - return errors - - def fix(self, filename, regions=all_regions): - f = self.open(filename, 'r+') - - lines = list(f) - - f.seek(0) - f.truncate() - - for i,line in enumerate(lines): - if i in regions: - line = self.fix_line(line) - - f.write(line) - f.close() - - def apply(self, filename, prompt, regions=all_regions): - if not self.skip(filename): - errors = self.check(filename, regions) - if errors: - if prompt(filename, self.fix, regions): - return True - return False - - -class Whitespace(Verifier): - languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons')) - test_name = 'whitespace' - def check_line(self, line): - match = lead.search(line) - if match and match.group(1).find('\t') != -1: - return False - - match = trail.search(line) - if match: +from style.file_types import lang_type +import style.verifiers +from style.region import all_regions + +from style.style import StdioUI +from style import repo + +verifier_names = dict([ + (c.__name__, c) for c in style.verifiers.all_verifiers ]) + +def verify(filename, regions=all_regions, verbose=False, verifiers=None, + auto_fix=False): + ui = StdioUI() + opts = { + "fix_all" : auto_fix, + } + base = os.path.join(os.path.dirname(__file__), "..") + if verifiers is None: + verifiers = style.verifiers.all_verifiers + + if verbose: + print("Verifying %s[%s]..." % (filename, regions)) + for verifier in [ v(ui, opts, base=base) for v in verifiers ]: + if verbose: + print("Applying %s (%s)" % ( + verifier.test_name, verifier.__class__.__name__)) + if verifier.apply(filename, regions=regions): return False + return True + +def detect_repo(): + repo_classes = repo.detect_repo() + if not repo_classes: + print("Error: Failed to detect repository type, no " \ + "known repository type found.", file=sys.stderr) + sys.exit(1) + elif len(repo_classes) > 1: + print("Error: Detected multiple repository types.", file=sys.stderr) + sys.exit(1) + else: + return repo_classes[0]() - return True - - def fix_line(self, line): - if lead.search(line): - newline = '' - for i,c in enumerate(line): - if c == ' ': - newline += ' ' - elif c == '\t': - newline += ' ' * (tabsize - len(newline) % tabsize) - else: - newline += line[i:] - break - - line = newline - - return line.rstrip() + '\n' +repo_types = { + "auto" : detect_repo, + "none" : lambda : None, + "git" : repo.GitRepo, +} -class SortedIncludes(Verifier): - languages = sort_includes.default_languages - def __init__(self, *args, **kwargs): - super(SortedIncludes, self).__init__(*args, **kwargs) - self.sort_includes = sort_includes.SortIncludes() +if __name__ == '__main__': + import argparse - def check(self, filename, regions=all_regions): - f = self.open(filename, 'r') + parser = argparse.ArgumentParser( + description="Check a file for gem5 style violations", + epilog="""If no files are specified, the style checker tries to + determine the list of modified and added files from the version + control system and checks those.""" + ) - lines = [ l.rstrip('\n') for l in f.xreadlines() ] - old = ''.join(line + '\n' for line in lines) - f.close() + parser.add_argument("--verbose", "-v", action="count", + help="Produce verbose output") - language = lang_type(filename, lines[0]) - sort_lines = list(self.sort_includes(lines, filename, language)) - new = ''.join(line + '\n' for line in sort_lines) + parser.add_argument("--fix", "-f", action="store_true", + help="Automatically fix style violations.") - mod = modified_regions(old, new) - modified = mod & regions + parser.add_argument("--modifications", "-m", action="store_true", + help="""Apply the style checker to modified regions + instead of whole files""") - if modified: - self.write("invalid sorting of includes\n") - if self.ui.verbose: - for start, end in modified.regions: - self.write("bad region [%d, %d)\n" % (start, end)) - return 1 + parser.add_argument("--repo-type", choices=repo_types, default="auto", + help="Repository type to use to detect changes") - return 0 + parser.add_argument("--checker", "-c", choices=verifier_names, default=[], + action="append", + help="""Style checkers to run. Can be specified + multiple times.""") - def fix(self, filename, regions=all_regions): - f = self.open(filename, 'r+') + parser.add_argument("files", metavar="FILE", nargs="*", + type=str, + help="Source file(s) to inspect") - old = f.readlines() - lines = [ l.rstrip('\n') for l in old ] - language = lang_type(filename, lines[0]) - sort_lines = list(self.sort_includes(lines, filename, language)) - new = ''.join(line + '\n' for line in sort_lines) + args = parser.parse_args() - f.seek(0) - f.truncate() + repo = repo_types[args.repo_type]() - for i,line in enumerate(sort_lines): - f.write(line) - f.write('\n') - f.close() + verifiers = [ verifier_names[name] for name in args.checker ] \ + if args.checker else None -def linelen(line): - tabs = line.count('\t') - if not tabs: - return len(line) + files = args.files + if not files and repo: + added, modified = repo.staged_files() + files = [ repo.file_path(f) for f in added + modified ] - count = 0 - for c in line: - if c == '\t': - count += tabsize - count % tabsize + for filename in files: + if args.modifications and repo and repo.in_repo(filename): + regions = repo.modified_regions(filename) else: - count += 1 - - return count - -class ValidationStats(object): - def __init__(self): - self.toolong = 0 - self.toolong80 = 0 - self.leadtabs = 0 - self.trailwhite = 0 - self.badcontrol = 0 - self.cret = 0 - - def dump(self): - print '''\ -%d violations of lines over 79 chars. %d of which are 80 chars exactly. -%d cases of whitespace at the end of a line. -%d cases of tabs to indent. -%d bad parens after if/while/for. -%d carriage returns found. -''' % (self.toolong, self.toolong80, self.trailwhite, self.leadtabs, - self.badcontrol, self.cret) + regions = all_regions - def __nonzero__(self): - return self.toolong or self.toolong80 or self.leadtabs or \ - self.trailwhite or self.badcontrol or self.cret - -def validate(filename, stats, verbose, exit_code): - if lang_type(filename) not in format_types: - return - - def msg(lineno, line, message): - print '%s:%d>' % (filename, lineno + 1), message - if verbose > 2: - print line - - def bad(): - if exit_code is not None: - sys.exit(exit_code) - - try: - f = file(filename, 'r') - except OSError: - if verbose > 0: - print 'could not open file %s' % filename - bad() - return - - for i,line in enumerate(f): - line = line.rstrip('\n') - - # no carriage returns - if line.find('\r') != -1: - self.cret += 1 - if verbose > 1: - msg(i, line, 'carriage return found') - bad() - - # lines max out at 79 chars - llen = linelen(line) - if llen > 79: - stats.toolong += 1 - if llen == 80: - stats.toolong80 += 1 - if verbose > 1: - msg(i, line, 'line too long (%d chars)' % llen) - bad() - - # no tabs used to indent - match = lead.search(line) - if match and match.group(1).find('\t') != -1: - stats.leadtabs += 1 - if verbose > 1: - msg(i, line, 'using tabs to indent') - bad() - - # no trailing whitespace - if trail.search(line): - stats.trailwhite +=1 - if verbose > 1: - msg(i, line, 'trailing whitespace') - bad() - - # for c++, exactly one space betwen if/while/for and ( - if cpp: - match = any_control.search(line) - if match and not good_control.search(line): - stats.badcontrol += 1 - if verbose > 1: - msg(i, line, 'improper spacing after %s' % match.group(1)) - bad() - -def do_check_style(hgui, repo, *files, **args): - """check files for proper m5 style guidelines""" - from mercurial import mdiff, util - - auto = args.get('auto', False) - if auto: - auto = 'f' - ui = MercurialUI(hgui, hgui.verbose, auto) - - if files: - files = frozenset(files) - - def skip(name): - return files and name in files - - def prompt(name, func, regions=all_regions): - result = ui.prompt("(a)bort, (i)gnore, or (f)ix?", 'aif', 'a') - if result == 'a': - return True - elif result == 'f': - func(repo.wjoin(name), regions) - - return False - - modified, added, removed, deleted, unknown, ignore, clean = repo.status() - - whitespace = Whitespace(ui) - sorted_includes = SortedIncludes(ui) - for fname in added: - if skip(fname): - continue - - if whitespace.apply(fname, prompt): - return True - - if sorted_includes.apply(fname, prompt): - return True - - try: - wctx = repo.workingctx() - except: - from mercurial import context - wctx = context.workingctx(repo) - - for fname in modified: - if skip(fname): - continue - - regions = modregions(wctx, fname) - - if whitespace.apply(fname, prompt, regions): - return True - - if sorted_includes.apply(fname, prompt, regions): - return True - - return False - -def do_check_format(hgui, repo, **args): - ui = MercurialUI(hgui, hgui.verbose, auto) - - modified, added, removed, deleted, unknown, ignore, clean = repo.status() - - verbose = 0 - stats = ValidationStats() - for f in modified + added: - validate(f, stats, verbose, None) - - if stats: - stats.dump() - result = ui.prompt("invalid formatting\n(i)gnore or (a)bort?", - 'ai', 'a') - if result == 'a': - return True - - return False - -def check_hook(hooktype): - if hooktype not in ('pretxncommit', 'pre-qrefresh'): - raise AttributeError, \ - "This hook is not meant for %s" % hooktype - -def check_style(ui, repo, hooktype, **kwargs): - check_hook(hooktype) - args = {} - - try: - return do_check_style(ui, repo, **args) - except Exception, e: - import traceback - traceback.print_exc() - return True - -def check_format(ui, repo, hooktype, **kwargs): - check_hook(hooktype) - args = {} - - try: - return do_check_format(ui, repo, **args) - except Exception, e: - import traceback - traceback.print_exc() - return True - -try: - from mercurial.i18n import _ -except ImportError: - def _(arg): - return arg - -cmdtable = { - '^m5style' : - ( do_check_style, - [ ('a', 'auto', False, _("automatically fix whitespace")) ], - _('hg m5style [-a] [FILE]...')), - '^m5format' : - ( do_check_format, - [ ], - _('hg m5format [FILE]...')), -} - -if __name__ == '__main__': - import getopt - - progname = sys.argv[0] - if len(sys.argv) < 2: - sys.exit('usage: %s []' % progname) - - fixwhite_usage = '%s fixwhite [-t ] [...] \n' % progname - chkformat_usage = '%s chkformat [...] \n' % progname - chkwhite_usage = '%s chkwhite [...] \n' % progname - - command = sys.argv[1] - if command == 'fixwhite': - flags = 't:' - usage = fixwhite_usage - elif command == 'chkwhite': - flags = 'nv' - usage = chkwhite_usage - elif command == 'chkformat': - flags = 'nv' - usage = chkformat_usage - else: - sys.exit(fixwhite_usage + chkwhite_usage + chkformat_usage) - - opts, args = getopt.getopt(sys.argv[2:], flags) - - code = 1 - verbose = 1 - for opt,arg in opts: - if opt == '-n': - code = None - if opt == '-t': - tabsize = int(arg) - if opt == '-v': - verbose += 1 - - if command == 'fixwhite': - for filename in args: - fixwhite(filename, tabsize) - elif command == 'chkwhite': - for filename in args: - for line,num in checkwhite(filename): - print 'invalid whitespace: %s:%d' % (filename, num) - if verbose: - print '>>%s<<' % line[:-1] - elif command == 'chkformat': - stats = ValidationStats() - for filename in args: - validate(filename, stats=stats, verbose=verbose, exit_code=code) - - if verbose > 0: - stats.dump() - else: - sys.exit("command '%s' not found" % command) + if not verify(filename, regions=regions, + verbose=args.verbose, + verifiers=verifiers, + auto_fix=args.fix): + sys.exit(1)