#!/usr/bin/env python3 import collections import json import pathlib import subprocess import sys DEFAULT_REF = 'origin/master' THE_BIG_LIST_OF_NAUGHTY_FILES = list(map(pathlib.Path, [ # # Problems with files listed here get lighter treatment: they are blocking # only when they are a part of the current pull request. # # for pylint 'Documentation/conf.py', 'LibOS/shim/test/apps/ltp/contrib/conf_lint.py', 'LibOS/shim/test/apps/ltp/contrib/conf_merge.py', 'LibOS/shim/test/apps/ltp/contrib/conf_missing.py', 'LibOS/shim/test/apps/ltp/contrib/conf_remove_must_pass.py', 'LibOS/shim/test/apps/ltp/contrib/has_own_main.py', 'LibOS/shim/test/apps/ltp/contrib/report.py', 'LibOS/shim/test/apps/ltp/runltp_xml.py', 'LibOS/shim/test/apps/python-scipy-insecure/scripts/test-numpy.py', 'LibOS/shim/test/apps/python-scipy-insecure/scripts/test-scipy.py', 'LibOS/shim/test/apps/python-simple/scripts/benchrun.py', 'LibOS/shim/test/apps/python-simple/scripts/dummy-web-server.py', 'LibOS/shim/test/apps/python-simple/scripts/fibonacci.py', 'LibOS/shim/test/apps/python-simple/scripts/helloworld.py', 'LibOS/shim/test/apps/python-simple/scripts/test-http.py', 'LibOS/shim/test/fs/test_fs.py', 'LibOS/shim/test/regression/test_libos.py', 'Pal/regression/test_pal.py', 'Pal/src/host/Linux-SGX/sgx-driver/link-intel-driver.py', 'Pal/src/host/Linux/pal-gdb.py', 'Scripts/regression.py', 'Tools', # for shellcheck 'LibOS/shim/test/apps/bash/scripts/bash_test.sh', 'LibOS/shim/test/apps/common_tools/benchmark-http.sh', 'LibOS/shim/test/apps/python-simple/run-tests.sh', 'LibOS/shim/test/native', 'Pal/src/host/Linux-SGX/debugger/gdb', 'Pal/src/host/Linux-SGX/sgx-driver/load.sh', 'Runtime/pal_loader', 'Scripts/clean-check', 'Scripts/clean-check-prepare', 'Scripts/clean-check-test-copy', 'Scripts/list-all-graphene.sh', 'Scripts/memusg', '.ci/run-pylint', '.ci/run-shellcheck', ])) def get_diff_ranges(ref=DEFAULT_REF): '''Get chunks affected by a merge request Args: ref (str): a reference to diff the HEAD against (default: origin/master) Returns: dict: a dict with filenames in keys and list of `(start, end)` ranges in values (start is inclusive, end is not, wrt :py:func:`range`) ''' files = collections.defaultdict(list) data = subprocess.check_output(['git', 'diff', '-U0', ref, 'HEAD']).decode() path = None for line in data.split('\n'): if line.startswith('+++ '): path = (None if line == '+++ /dev/null' else line.split('/', maxsplit=1)[-1]) continue if line.startswith('@@ '): # @@ -8,0 +9 @@ [class name or previous line or whatever] if path is None: # /dev/null continue _, _, plus, *_ = line.split() start, length, *_ = *(int(i) for i in plus[1:].split(',')), 1 if not length: # remove-only chunk continue files[path].append((start, start + length)) return files class Diff: '''A quick and dirty diff evaluator >>> diff = Diff() >>> (message['file'], message['line']) in diff True # or False >>> diff.message_is_important(message) True # or False The default diff is to the ``origin/master`` ref. ''' # pylint: disable=too-few-public-methods def __init__(self, ref=DEFAULT_REF): self._files = get_diff_ranges(ref) def __contains__(self, pathline): path, line = pathline try: return any(start <= line < end for start, end in self._files[path]) except KeyError: return False def message_is_important(self, message): path = pathlib.Path(message['path']) for i in THE_BIG_LIST_OF_NAUGHTY_FILES: try: path.relative_to(i) break except ValueError: # path is not .relative_to() the path from WHITELIST pass else: # not on whitelist: always complain return True if (message['path'], message['line']) in self: # on whitelist, but in diff: do complain return True # on whitelist and outside diff: don't complain return False def main(): diff = Diff() with sys.stdin: pylint = json.load(sys.stdin) ret = 0 for message in pylint: # shellcheck if 'path' not in message: message['path'] = message['file'] if 'symbol' not in message: message['symbol'] = message['code'] if diff.message_is_important(message): if not ret: print('MESSAGES AFFECTING THIS PR:') print('{path} +{line}:{column}: {symbol}: {message}'.format( **message)) ret += 1 return min(ret, 255) if __name__ == '__main__': sys.exit(main())