123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- import json
- import os
- import signal
- import subprocess
- import which
- from mozprocess import ProcessHandler
- from mozlint import result
- here = os.path.abspath(os.path.dirname(__file__))
- FLAKE8_REQUIREMENTS_PATH = os.path.join(here, 'flake8', 'flake8_requirements.txt')
- FLAKE8_NOT_FOUND = """
- Could not find flake8! Install flake8 and try again.
- $ pip install -U --require-hashes -r {}
- """.strip().format(FLAKE8_REQUIREMENTS_PATH)
- FLAKE8_INSTALL_ERROR = """
- Unable to install correct version of flake8
- Try to install it manually with:
- $ pip install -U --require-hashes -r {}
- """.strip().format(FLAKE8_REQUIREMENTS_PATH)
- LINE_OFFSETS = {
- # continuation line under-indented for hanging indent
- 'E121': (-1, 2),
- # continuation line missing indentation or outdented
- 'E122': (-1, 2),
- # continuation line over-indented for hanging indent
- 'E126': (-1, 2),
- # continuation line over-indented for visual indent
- 'E127': (-1, 2),
- # continuation line under-indented for visual indent
- 'E128': (-1, 2),
- # continuation line unaligned for hanging indend
- 'E131': (-1, 2),
- # expected 1 blank line, found 0
- 'E301': (-1, 2),
- # expected 2 blank lines, found 1
- 'E302': (-2, 3),
- }
- """Maps a flake8 error to a lineoffset tuple.
- The offset is of the form (lineno_offset, num_lines) and is passed
- to the lineoffset property of `ResultContainer`.
- """
- EXTENSIONS = ['.py', '.lint']
- results = []
- def process_line(line):
- # Escape slashes otherwise JSON conversion will not work
- line = line.replace('\\', '\\\\')
- try:
- res = json.loads(line)
- except ValueError:
- print('Non JSON output from linter, will not be processed: {}'.format(line))
- return
- if 'code' in res:
- if res['code'].startswith('W'):
- res['level'] = 'warning'
- if res['code'] in LINE_OFFSETS:
- res['lineoffset'] = LINE_OFFSETS[res['code']]
- results.append(result.from_linter(LINTER, **res))
- def run_process(cmdargs):
- # flake8 seems to handle SIGINT poorly. Handle it here instead
- # so we can kill the process without a cryptic traceback.
- orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
- proc = ProcessHandler(cmdargs, env=os.environ,
- processOutputLine=process_line)
- proc.run()
- signal.signal(signal.SIGINT, orig)
- try:
- proc.wait()
- except KeyboardInterrupt:
- proc.kill()
- def get_flake8_binary():
- """
- Returns the path of the first flake8 binary available
- if not found returns None
- """
- binary = os.environ.get('FLAKE8')
- if binary:
- return binary
- try:
- return which.which('flake8')
- except which.WhichError:
- return None
- def _run_pip(*args):
- """
- Helper function that runs pip with subprocess
- """
- try:
- subprocess.check_output(['pip'] + list(args),
- stderr=subprocess.STDOUT)
- return True
- except subprocess.CalledProcessError as e:
- print(e.output)
- return False
- def reinstall_flake8():
- """
- Try to install flake8 at the target version, returns True on success
- otherwise prints the otuput of the pip command and returns False
- """
- if _run_pip('install', '-U',
- '--require-hashes', '-r',
- FLAKE8_REQUIREMENTS_PATH):
- return True
- return False
- def lint(files, **lintargs):
- if not reinstall_flake8():
- print(FLAKE8_INSTALL_ERROR)
- return 1
- binary = get_flake8_binary()
- cmdargs = [
- binary,
- '--format', '{"path":"%(path)s","lineno":%(row)s,'
- '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
- ]
- # Run any paths with a .flake8 file in the directory separately so
- # it gets picked up. This means only .flake8 files that live in
- # directories that are explicitly included will be considered.
- # See bug 1277851
- no_config = []
- for f in files:
- if not os.path.isfile(os.path.join(f, '.flake8')):
- no_config.append(f)
- continue
- run_process(cmdargs+[f])
- # XXX For some reason passing in --exclude results in flake8 not using
- # the local .flake8 file. So for now only pass in --exclude if there
- # is no local config.
- exclude = lintargs.get('exclude')
- if exclude:
- cmdargs += ['--exclude', ','.join(lintargs['exclude'])]
- if no_config:
- run_process(cmdargs+no_config)
- return results
- LINTER = {
- 'name': "flake8",
- 'description': "Python linter",
- 'include': [
- 'python/mozlint',
- 'taskcluster',
- 'testing/firefox-ui',
- 'testing/marionette/client',
- 'testing/marionette/harness',
- 'testing/marionette/puppeteer',
- 'testing/mozbase',
- 'testing/mochitest',
- 'testing/talos/',
- 'tools/lint',
- ],
- 'exclude': ["testing/mozbase/mozdevice/mozdevice/Zeroconf.py",
- 'testing/mochitest/pywebsocket'],
- 'extensions': EXTENSIONS,
- 'type': 'external',
- 'payload': lint,
- }
|