Blame src/util/cstyle.py

Packit fd8b60
# Copyright (C) 2012 by the Massachusetts Institute of Technology.
Packit fd8b60
# All rights reserved.
Packit fd8b60
#
Packit fd8b60
# Export of this software from the United States of America may
Packit fd8b60
#   require a specific license from the United States Government.
Packit fd8b60
#   It is the responsibility of any person or organization contemplating
Packit fd8b60
#   export to obtain such a license before exporting.
Packit fd8b60
#
Packit fd8b60
# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
Packit fd8b60
# distribute this software and its documentation for any purpose and
Packit fd8b60
# without fee is hereby granted, provided that the above copyright
Packit fd8b60
# notice appear in all copies and that both that copyright notice and
Packit fd8b60
# this permission notice appear in supporting documentation, and that
Packit fd8b60
# the name of M.I.T. not be used in advertising or publicity pertaining
Packit fd8b60
# to distribution of the software without specific, written prior
Packit fd8b60
# permission.  Furthermore if you modify this software you must label
Packit fd8b60
# your software as modified software and not distribute it in such a
Packit fd8b60
# fashion that it might be confused with the original M.I.T. software.
Packit fd8b60
# M.I.T. makes no representations about the suitability of
Packit fd8b60
# this software for any purpose.  It is provided "as is" without express
Packit fd8b60
# or implied warranty.
Packit fd8b60
Packit fd8b60
# This program attempts to detect MIT krb5 coding style violations
Packit fd8b60
# attributable to the changes a series of git commits.  It can be run
Packit fd8b60
# from anywhere within a git working tree.
Packit fd8b60
Packit fd8b60
import getopt
Packit fd8b60
import os
Packit fd8b60
import re
Packit fd8b60
import sys
Packit fd8b60
from subprocess import Popen, PIPE, call
Packit fd8b60
Packit fd8b60
def usage():
Packit fd8b60
    u = ['Usage: cstyle [-w] [rev|rev1..rev2]',
Packit fd8b60
         '',
Packit fd8b60
         'By default, checks working tree against HEAD, or checks changes in',
Packit fd8b60
         'HEAD if the working tree is clean.  With a revision option, checks',
Packit fd8b60
         'changes in rev or the series rev1..rev2.  With the -w option,',
Packit fd8b60
         'checks working tree against rev (defaults to HEAD).']
Packit fd8b60
    sys.stderr.write('\n'.join(u) + '\n')
Packit fd8b60
    sys.exit(1)
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Run a command and return a list of its output lines.
Packit fd8b60
def run(args):
Packit fd8b60
    # subprocess.check_output would be ideal here, but requires Python 2.7.
Packit fd8b60
    p = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True)
Packit fd8b60
    out, err = p.communicate()
Packit fd8b60
    if p.returncode != 0:
Packit fd8b60
        sys.stderr.write('Failed command: ' + ' '.join(args) + '\n')
Packit fd8b60
        if err != '':
Packit fd8b60
            sys.stderr.write('stderr:\n' + err)
Packit fd8b60
        sys.stderr.write('Unexpected command failure, exiting\n')
Packit fd8b60
        sys.exit(1)
Packit fd8b60
    return out.splitlines()
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Find the top level of the git working tree, or None if we're not in
Packit fd8b60
# one.
Packit fd8b60
def find_toplevel():
Packit fd8b60
    # git doesn't seem to have a way to do this, so we search by hand.
Packit fd8b60
    dir = os.getcwd()
Packit fd8b60
    while True:
Packit fd8b60
        if os.path.exists(os.path.join(dir, '.git')):
Packit fd8b60
            break
Packit fd8b60
        parent = os.path.dirname(dir)
Packit fd8b60
        if (parent == dir):
Packit fd8b60
            return None
Packit fd8b60
        dir = parent
Packit fd8b60
    return dir
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Check for style issues in a file within rev (or within the current
Packit fd8b60
# checkout if rev is None).  Report only problems on line numbers in
Packit fd8b60
# new_lines.
Packit fd8b60
line_re = re.compile(r'^\s*(\d+)  (.*)$')
Packit fd8b60
def check_file(filename, rev, new_lines):
Packit fd8b60
    # Process only C source files under src.
Packit fd8b60
    root, ext = os.path.splitext(filename)
Packit fd8b60
    if not filename.startswith('src/') or ext not in ('.c', '.h', '.hin'):
Packit fd8b60
        return
Packit fd8b60
    dispname = filename[4:]
Packit fd8b60
Packit fd8b60
    if rev is None:
Packit fd8b60
        p1 = Popen(['cat', filename], stdout=PIPE)
Packit fd8b60
    else:
Packit fd8b60
        p1 = Popen(['git', 'show', rev + ':' + filename], stdout=PIPE)
Packit fd8b60
    p2 = Popen([sys.executable, 'src/util/cstyle-file.py'], stdin=p1.stdout,
Packit fd8b60
               stdout=PIPE, universal_newlines=True)
Packit fd8b60
    p1.stdout.close()
Packit fd8b60
    out, err = p2.communicate()
Packit fd8b60
    if p2.returncode != 0:
Packit fd8b60
        sys.exit(1)
Packit fd8b60
Packit fd8b60
    first = True
Packit fd8b60
    for line in out.splitlines():
Packit fd8b60
        m = line_re.match(line)
Packit fd8b60
        if int(m.group(1)) in new_lines:
Packit fd8b60
            if first:
Packit fd8b60
                print('  ' + dispname + ':')
Packit fd8b60
                first = False
Packit fd8b60
            print('    ' + line)
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Determine the lines of each file modified by diff (a sequence of
Packit fd8b60
# strings) and check for style violations in those lines.  rev
Packit fd8b60
# indicates the version in which the new contents of each file can be
Packit fd8b60
# found, or is None if the current contents are in the working copy.
Packit fd8b60
chunk_header_re = re.compile(r'^@@ -\d+(,(\d+))? \+(\d+)(,(\d+))? @@')
Packit fd8b60
def check_diff(diff, rev):
Packit fd8b60
    old_count, new_count, lineno = 0, 0, 0
Packit fd8b60
    filename = None
Packit fd8b60
    for line in diff:
Packit fd8b60
        if not line or line.startswith('\\ No newline'):
Packit fd8b60
            continue
Packit fd8b60
        if old_count > 0 or new_count > 0:
Packit fd8b60
            # We're in a chunk.
Packit fd8b60
            if line[0] == '+':
Packit fd8b60
                new_lines.append(lineno)
Packit fd8b60
            if line[0] in ('+', ' '):
Packit fd8b60
                new_count = new_count - 1
Packit fd8b60
                lineno = lineno + 1
Packit fd8b60
            if line[0] in ('-', ' '):
Packit fd8b60
                old_count = old_count - 1
Packit fd8b60
        elif line.startswith('+++ b/'):
Packit fd8b60
            # We're starting a new file.  Check the last one.
Packit fd8b60
            if filename:
Packit fd8b60
                check_file(filename, rev, new_lines)
Packit fd8b60
            filename = line[6:]
Packit fd8b60
            new_lines = []
Packit fd8b60
        else:
Packit fd8b60
            m = chunk_header_re.match(line)
Packit fd8b60
            if m:
Packit fd8b60
                old_count = int(m.group(2) or '1')
Packit fd8b60
                lineno = int(m.group(3))
Packit fd8b60
                new_count = int(m.group(5) or '1')
Packit fd8b60
Packit fd8b60
    # Check the last file in the diff.
Packit fd8b60
    if filename:
Packit fd8b60
        check_file(filename, rev, new_lines)
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Check a sequence of revisions for style issues.
Packit fd8b60
def check_series(revlist):
Packit fd8b60
    for rev in revlist:
Packit fd8b60
        sys.stdout.flush()
Packit fd8b60
        call(['git', 'show', '-s', '--oneline', rev])
Packit fd8b60
        diff = run(['git', 'diff-tree', '--no-commit-id', '--root', '-M',
Packit fd8b60
                    '--cc', rev])
Packit fd8b60
        check_diff(diff, rev)
Packit fd8b60
Packit fd8b60
Packit fd8b60
# Parse arguments.
Packit fd8b60
try:
Packit fd8b60
    opts, args = getopt.getopt(sys.argv[1:], 'w')
Packit fd8b60
except getopt.GetoptError as err:
Packit fd8b60
    print(str(err))
Packit fd8b60
    usage()
Packit fd8b60
if len(args) > 1:
Packit fd8b60
    usage()
Packit fd8b60
Packit fd8b60
# Change to the top level of the working tree so we easily run the file
Packit fd8b60
# checker and refer to working tree files.
Packit fd8b60
toplevel = find_toplevel()
Packit fd8b60
if toplevel is None:
Packit fd8b60
    sys.stderr.write('%s must be run within a git working tree')
Packit fd8b60
os.chdir(toplevel)
Packit fd8b60
Packit fd8b60
if ('-w', '') in opts:
Packit fd8b60
    # Check the working tree against a base revision.
Packit fd8b60
    arg = 'HEAD'
Packit fd8b60
    if args:
Packit fd8b60
        arg = args[0]
Packit fd8b60
    check_diff(run(['git', 'diff', arg]), None)
Packit fd8b60
elif args:
Packit fd8b60
    # Check the differences in a rev or a series of revs.
Packit fd8b60
    if '..' in args[0]:
Packit fd8b60
        check_series(run(['git', 'rev-list', '--reverse', args[0]]))
Packit fd8b60
    else:
Packit fd8b60
        check_series([args[0]])
Packit fd8b60
else:
Packit fd8b60
    # No options or arguments.  Check the differences against HEAD, or
Packit fd8b60
    # the differences in HEAD if the working tree is clean.
Packit fd8b60
    diff = run(['git', 'diff', 'HEAD'])
Packit fd8b60
    if diff:
Packit fd8b60
        check_diff(diff, None)
Packit fd8b60
    else:
Packit fd8b60
        check_series(['HEAD'])