fune/tools/lint/python/pylint.py
Mitchell Hentges e913081903 Bug 1751795: Log more information if pip_install_requirements fails r=ahal
In addition to showing the `pip check` failure, also show:
* The `pip install --requirements ...` output
* The output of `pip list -v`
* The path to the `requirements.txt` file and the affected site.

To get sufficient detail of `pip install --requirements` to display
if the subsequent `pip check` call failed, I had to remove the
`--quiet` parameter. It was replicated by hiding _all_ output
if `quiet=True` and `pip install` succeeds - this is different to
the previous behaviour, where warnings would still poke through,
even if `quiet=True` and the install succeeded.
This was the only way to make it work such that all output would
be available without having to run `pip install` twice (which would
likely get different output the second time). Besides, there weren't
many warnings triggering this edge case, so the change in behaviour is
probably acceptable.

Additionally, in both places doing `pip check`
(post-install-requirements and when doing system-package compatibility
checking), move the `pip check` output to happen last, so that users
don't need to scroll to the top of the `pip list` wall of text to find
the reason for their command failure.

Differential Revision: https://phabricator.services.mozilla.com/D137206
2022-02-03 21:51:49 +00:00

135 lines
3.7 KiB
Python

# 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 subprocess
import signal
from mozprocess import ProcessHandler
from mozlint import result
from mozlint.pathutils import expand_exclusions
from mach.site import InstallPipRequirementsException
here = os.path.abspath(os.path.dirname(__file__))
PYLINT_REQUIREMENTS_PATH = os.path.join(here, "pylint_requirements.txt")
PYLINT_NOT_FOUND = """
Could not find pylint! Install pylint and try again.
$ pip install -U --require-hashes -r {}
""".strip().format(
PYLINT_REQUIREMENTS_PATH
)
PYLINT_INSTALL_ERROR = """
Unable to install correct version of pylint
Try to install it manually with:
$ pip install -U --require-hashes -r {}
""".strip().format(
PYLINT_REQUIREMENTS_PATH
)
class PylintProcess(ProcessHandler):
def __init__(self, config, *args, **kwargs):
self.config = config
kwargs["stream"] = False
kwargs["universal_newlines"] = True
ProcessHandler.__init__(self, *args, **kwargs)
def run(self, *args, **kwargs):
orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
ProcessHandler.run(self, *args, **kwargs)
signal.signal(signal.SIGINT, orig)
def setup(root, **lintargs):
virtualenv_manager = lintargs["virtualenv_manager"]
try:
virtualenv_manager.install_pip_requirements(
PYLINT_REQUIREMENTS_PATH,
quiet=True,
)
except (subprocess.CalledProcessError, InstallPipRequirementsException):
print(PYLINT_INSTALL_ERROR)
return 1
def get_pylint_binary():
return "pylint"
def run_process(config, cmd):
proc = PylintProcess(config, cmd)
proc.run()
try:
proc.wait()
except KeyboardInterrupt:
proc.kill()
return proc.output
def parse_issues(log, config, issues_json, path):
results = []
try:
issues = json.loads(issues_json)
except json.decoder.JSONDecodeError:
log.debug("Could not parse the output:")
log.debug("pylint output: {}".format(issues_json))
return []
for issue in issues:
res = {
"path": issue["path"],
"level": issue["type"],
"lineno": issue["line"],
"column": issue["column"],
"message": issue["message"],
"rule": issue["message-id"],
}
results.append(result.from_config(config, **res))
return results
def get_pylint_version(binary):
return subprocess.check_output(
[binary, "--version"],
universal_newlines=True,
stderr=subprocess.STDOUT,
)
def lint(paths, config, **lintargs):
log = lintargs["log"]
binary = get_pylint_binary()
log = lintargs["log"]
paths = list(expand_exclusions(paths, config, lintargs["root"]))
cmd_args = [binary]
results = []
# list from https://code.visualstudio.com/docs/python/linting#_pylint
# And ignore a bit more elements
cmd_args += [
"-fjson",
"--disable=all",
"--enable=F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,no-else-return", # NOQA: E501
"--disable=import-error,no-member",
]
base_command = cmd_args + paths
log.debug("Command: {}".format(" ".join(cmd_args)))
log.debug("pylint version: {}".format(get_pylint_version(binary)))
output = " ".join(run_process(config, base_command))
results = parse_issues(log, config, str(output), [])
return results