diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /tools/lint/clippy | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/lint/clippy')
-rw-r--r-- | tools/lint/clippy/__init__.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/tools/lint/clippy/__init__.py b/tools/lint/clippy/__init__.py new file mode 100644 index 0000000000..75dbcd42fc --- /dev/null +++ b/tools/lint/clippy/__init__.py @@ -0,0 +1,252 @@ +# 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 re +import signal +import six +import subprocess + +from distutils.version import StrictVersion +from mozfile import which +from mozlint import result +from mozlint.pathutils import get_ancestors_by_name +from mozprocess import ProcessHandler + + +CLIPPY_WRONG_VERSION = """ +You are probably using an old version of clippy. +Expected version is {version}. + +To install it: + $ rustup component add clippy + +Or to update it: + $ rustup update + +And make sure that 'cargo' is in the PATH +""".strip() + + +CARGO_NOT_FOUND = """ +Could not find cargo! Install cargo. + +And make sure that it is in the PATH +""".strip() + + +def parse_issues(log, config, issues, path, onlyIn): + results = [] + for issue in issues: + + try: + detail = json.loads(six.ensure_text(issue)) + if "message" in detail: + p = detail["target"]["src_path"] + detail = detail["message"] + if "level" in detail: + if ( + detail["level"] == "error" or detail["level"] == "failure-note" + ) and not detail["code"]: + log.debug( + "Error outside of clippy." + "This means that the build failed. Therefore, skipping this" + ) + log.debug("File = {} / Detail = {}".format(p, detail)) + continue + # We are in a clippy warning + if len(detail["spans"]) == 0: + # For some reason, at the end of the summary, we can + # get the following line + # {'rendered': 'warning: 5 warnings emitted\n\n', 'children': + # [], 'code': None, 'level': 'warning', 'message': + # '5 warnings emitted', 'spans': []} + # if this is the case, skip it + log.debug( + "Skipping the summary line {} for file {}".format(detail, p) + ) + continue + + l = detail["spans"][0] + if onlyIn and onlyIn not in p: + # Case when we have a .rs in the include list in the yaml file + log.debug( + "{} is not part of the list of files '{}'".format(p, onlyIn) + ) + continue + res = { + "path": p, + "level": detail["level"], + "lineno": l["line_start"], + "column": l["column_start"], + "message": detail["message"], + "hint": detail["rendered"], + "rule": detail["code"]["code"], + "lineoffset": l["line_end"] - l["line_start"], + } + results.append(result.from_config(config, **res)) + + except json.decoder.JSONDecodeError: + log.debug("Could not parse the output:") + log.debug("clippy output: {}".format(issue)) + continue + + return results + + +def get_cargo_binary(log): + """ + Returns the path of the first rustfmt binary available + if not found returns None + """ + cargo_home = os.environ.get("CARGO_HOME") + if cargo_home: + log.debug("Found CARGO_HOME in {}".format(cargo_home)) + cargo_bin = os.path.join(cargo_home, "bin", "cargo") + if os.path.exists(cargo_bin): + return cargo_bin + log.debug("Did not find {} in CARGO_HOME".format(cargo_bin)) + return None + return which("cargo") + + +def get_clippy_version(log, binary): + """ + Check if we are running the deprecated rustfmt + """ + try: + output = subprocess.check_output( + [binary, "clippy", "--version"], + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + except subprocess.CalledProcessError: + # --version failed, clippy isn't installed. + return False + + log.debug("Found version: {}".format(output)) + + version = re.findall(r"(\d+-\d+-\d+)", output)[0].replace("-", ".") + version = StrictVersion(version) + return version + + +class clippyProcess(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(log, config, cmd): + log.debug("Command: {}".format(cmd)) + proc = clippyProcess(config, cmd) + proc.run() + try: + proc.wait() + except KeyboardInterrupt: + proc.kill() + + return proc.output + + +def lint(paths, config, fix=None, **lintargs): + log = lintargs["log"] + cargo = get_cargo_binary(log) + + if not cargo: + print(CARGO_NOT_FOUND) + if "MOZ_AUTOMATION" in os.environ: + return 1 + return [] + + min_version_str = config.get("min_clippy_version") + min_version = StrictVersion(min_version_str) + actual_version = get_clippy_version(log, cargo) + log.debug( + "Found version: {}. Minimal expected version: {}".format( + actual_version, min_version + ) + ) + + if actual_version < min_version: + print(CLIPPY_WRONG_VERSION.format(version=min_version_str)) + return 1 + + cmd_args_clean = [cargo] + cmd_args_clean.append("clean") + + cmd_args_common = ["--manifest-path"] + cmd_args_clippy = [cargo] + + if fix: + cmd_args_clippy += ["+nightly"] + + cmd_args_clippy += [ + "clippy", + "--message-format=json", + ] + + if fix: + cmd_args_clippy += ["--fix", "-Z", "unstable-options"] + + lock_files_to_delete = [] + for p in paths: + lock_file = os.path.join(p, "Cargo.lock") + if not os.path.exists(lock_file): + lock_files_to_delete.append(lock_file) + + results = [] + for p in paths: + # Quick sanity check of the paths + if p.endswith("Cargo.toml"): + print("Error: expects a directory or a rs file") + print("Found {}".format(p)) + return 1 + + for p in paths: + onlyIn = [] + path_conf = p + log.debug("Path = {}".format(p)) + if os.path.isfile(p): + # We are dealing with a file. We remove the filename from the path + # to find the closest Cargo file + # We also store the name of the file to be able to filter out other + # files built by the cargo + p = os.path.dirname(p) + onlyIn = path_conf + + if os.path.isdir(p): + # Sometimes, clippy reports issues from other crates + # Make sure that we don't display that either + onlyIn = p + + cargo_files = get_ancestors_by_name("Cargo.toml", p, lintargs["root"]) + p = cargo_files[0] + + log.debug("Path translated to = {}".format(p)) + # Needs clean because of https://github.com/rust-lang/rust-clippy/issues/2604 + clean_command = cmd_args_clean + cmd_args_common + [p] + run_process(log, config, clean_command) + + # Create the actual clippy command + base_command = cmd_args_clippy + cmd_args_common + [p] + output = run_process(log, config, base_command) + + # Remove build artifacts created by clippy + run_process(log, config, clean_command) + results += parse_issues(log, config, output, p, onlyIn) + + # Remove Cargo.lock files created by clippy + for lock_file in lock_files_to_delete: + if os.path.exists(lock_file): + os.remove(lock_file) + + return sorted(results, key=lambda issue: issue.path) |