forked from mirrors/gecko-dev
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
135 lines
3.7 KiB
Python
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
|