Backed out changeset e8fcfc7f8108 (bug 1811850) Backed out changeset f8950d716c9e (bug 1811850) Backed out changeset f650123cc188 (bug 1811850) Backed out changeset d96f90c2c58b (bug 1811850) Backed out changeset c3b0f9666183 (bug 1811850)
180 lines
5.6 KiB
Python
180 lines
5.6 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 os
|
|
import platform
|
|
import re
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
|
|
import mozpack.path as mozpath
|
|
from mozfile import which
|
|
from mozlint import result
|
|
from mozlint.pathutils import expand_exclusions
|
|
from mozprocess import ProcessHandler
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
BLACK_REQUIREMENTS_PATH = os.path.join(here, "black_requirements.txt")
|
|
|
|
BLACK_INSTALL_ERROR = """
|
|
Unable to install correct version of black
|
|
Try to install it manually with:
|
|
$ pip install -U --require-hashes -r {}
|
|
""".strip().format(
|
|
BLACK_REQUIREMENTS_PATH
|
|
)
|
|
|
|
|
|
def default_bindir():
|
|
# We use sys.prefix to find executables as that gets modified with
|
|
# virtualenv's activate_this.py, whereas sys.executable doesn't.
|
|
if platform.system() == "Windows":
|
|
return os.path.join(sys.prefix, "Scripts")
|
|
else:
|
|
return os.path.join(sys.prefix, "bin")
|
|
|
|
|
|
def get_black_version(binary):
|
|
"""
|
|
Returns found binary's version
|
|
"""
|
|
try:
|
|
output = subprocess.check_output(
|
|
[binary, "--version"],
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True,
|
|
)
|
|
except subprocess.CalledProcessError as e:
|
|
output = e.output
|
|
try:
|
|
# Accept `black.EXE, version ...` on Windows.
|
|
# for old version of black, the output is
|
|
# black, version 21.4b2
|
|
# From black 21.11b1, the output is like
|
|
# black, 21.11b1 (compiled: no)
|
|
return re.match(r"black.*,( version)? (\S+)", output)[2]
|
|
except TypeError as e:
|
|
print("Could not parse the version '{}'".format(output))
|
|
print("Error: {}".format(e))
|
|
|
|
|
|
def parse_issues(config, output, paths, *, log):
|
|
would_reformat = re.compile("^would reformat (.*)$", re.I)
|
|
reformatted = re.compile("^reformatted (.*)$", re.I)
|
|
cannot_reformat = re.compile("^error: cannot format (.*?): (.*)$", re.I)
|
|
results = []
|
|
for line in output:
|
|
line = line.decode("utf-8")
|
|
if line.startswith("All done!") or line.startswith("Oh no!"):
|
|
break
|
|
|
|
match = would_reformat.match(line)
|
|
if match:
|
|
res = {"path": match.group(1), "level": "error"}
|
|
results.append(result.from_config(config, **res))
|
|
continue
|
|
|
|
match = reformatted.match(line)
|
|
if match:
|
|
res = {"path": match.group(1), "level": "warning", "message": "reformatted"}
|
|
results.append(result.from_config(config, **res))
|
|
continue
|
|
|
|
match = cannot_reformat.match(line)
|
|
if match:
|
|
res = {"path": match.group(1), "level": "error", "message": match.group(2)}
|
|
results.append(result.from_config(config, **res))
|
|
continue
|
|
|
|
log.debug("Unhandled line", line)
|
|
return results
|
|
|
|
|
|
class BlackProcess(ProcessHandler):
|
|
def __init__(self, config, *args, **kwargs):
|
|
self.config = config
|
|
kwargs["stream"] = False
|
|
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 run_process(config, cmd):
|
|
proc = BlackProcess(config, cmd)
|
|
proc.run()
|
|
try:
|
|
proc.wait()
|
|
except KeyboardInterrupt:
|
|
proc.kill()
|
|
|
|
return proc.output
|
|
|
|
|
|
def setup(root, **lintargs):
|
|
log = lintargs["log"]
|
|
virtualenv_bin_path = lintargs.get("virtualenv_bin_path")
|
|
# Using `which` searches multiple directories and handles `.exe` on Windows.
|
|
binary = which("black", path=(virtualenv_bin_path, default_bindir()))
|
|
|
|
if binary and os.path.exists(binary):
|
|
binary = mozpath.normsep(binary)
|
|
log.debug("Looking for black at {}".format(binary))
|
|
version = get_black_version(binary)
|
|
versions = [
|
|
line.split()[0].strip()
|
|
for line in open(BLACK_REQUIREMENTS_PATH).readlines()
|
|
if line.startswith("black==")
|
|
]
|
|
if ["black=={}".format(version)] == versions:
|
|
log.debug("Black is present with expected version {}".format(version))
|
|
return 0
|
|
else:
|
|
log.debug("Black is present but unexpected version {}".format(version))
|
|
|
|
log.debug("Black needs to be installed or updated")
|
|
virtualenv_manager = lintargs["virtualenv_manager"]
|
|
try:
|
|
virtualenv_manager.install_pip_requirements(BLACK_REQUIREMENTS_PATH, quiet=True)
|
|
except subprocess.CalledProcessError:
|
|
print(BLACK_INSTALL_ERROR)
|
|
return 1
|
|
|
|
|
|
def run_black(config, paths, fix=None, *, log, virtualenv_bin_path):
|
|
fixed = 0
|
|
binary = os.path.join(virtualenv_bin_path or default_bindir(), "black")
|
|
|
|
log.debug("Black version {}".format(get_black_version(binary)))
|
|
|
|
cmd_args = [binary]
|
|
if not fix:
|
|
cmd_args.append("--check")
|
|
|
|
base_command = cmd_args + paths
|
|
log.debug("Command: {}".format(" ".join(base_command)))
|
|
output = parse_issues(config, run_process(config, base_command), paths, log=log)
|
|
|
|
# black returns an issue for fixed files as well
|
|
for eachIssue in output:
|
|
if eachIssue.message == "reformatted":
|
|
fixed += 1
|
|
|
|
return {"results": output, "fixed": fixed}
|
|
|
|
|
|
def lint(paths, config, fix=None, **lintargs):
|
|
files = list(expand_exclusions(paths, config, lintargs["root"]))
|
|
|
|
return run_black(
|
|
config,
|
|
files,
|
|
fix=fix,
|
|
log=lintargs["log"],
|
|
virtualenv_bin_path=lintargs.get("virtualenv_bin_path"),
|
|
)
|