Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / tools / lint / rust / __init__.py
blob7ca07922102a14e2adddef47522e81d38543b59f
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 import os
6 import re
7 import signal
8 import subprocess
9 from collections import namedtuple
11 from mozboot.util import get_tools_dir
12 from mozfile import which
13 from mozlint import result
14 from mozlint.pathutils import expand_exclusions
15 from packaging.version import Version
17 RUSTFMT_NOT_FOUND = """
18 Could not find rustfmt! Install rustfmt and try again.
20 $ rustup component add rustfmt
22 And make sure that it is in the PATH
23 """.strip()
26 RUSTFMT_INSTALL_ERROR = """
27 Unable to install correct version of rustfmt
28 Try to install it manually with:
29 $ rustup component add rustfmt
30 """.strip()
33 RUSTFMT_WRONG_VERSION = """
34 You are probably using an old version of rustfmt.
35 Expected version is {version}.
36 Try to update it:
37 $ rustup update stable
38 """.strip()
41 def parse_issues(config, output, paths):
42 RustfmtDiff = namedtuple("RustfmtDiff", ["file", "line", "diff"])
43 issues = []
44 diff_line = re.compile("^Diff in (.*)(?: at line |:)([0-9]*):")
45 file = ""
46 line_no = 0
47 diff = ""
48 for line in output.split(b"\n"):
49 processed_line = (
50 line.decode("utf-8", "replace") if isinstance(line, bytes) else line
51 ).rstrip("\r\n")
52 match = diff_line.match(processed_line)
53 if match:
54 if diff:
55 issues.append(RustfmtDiff(file, line_no, diff.rstrip("\n")))
56 diff = ""
57 file, line_no = match.groups()
58 else:
59 diff += processed_line + "\n"
60 # the algorithm above will always skip adding the last issue
61 issues.append(RustfmtDiff(file, line_no, diff))
62 file = os.path.normcase(os.path.normpath(file))
63 results = []
64 for issue in issues:
65 # rustfmt can not be supplied the paths to the files we want to analyze
66 # therefore, for each issue detected, we check if any of the the paths
67 # supplied are part of the file name.
68 # This just filters out the issues that are not part of paths.
69 if any([os.path.normcase(os.path.normpath(path)) in file for path in paths]):
70 res = {
71 "path": issue.file,
72 "diff": issue.diff,
73 "level": "warning",
74 "lineno": issue.line,
76 results.append(result.from_config(config, **res))
77 return {"results": results, "fixed": 0}
80 def run_process(config, cmd):
81 orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
82 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
83 signal.signal(signal.SIGINT, orig)
85 try:
86 output, _ = proc.communicate()
87 proc.wait()
88 except KeyboardInterrupt:
89 proc.kill()
91 return output
94 def get_rustfmt_binary():
95 """
96 Returns the path of the first rustfmt binary available
97 if not found returns None
98 """
99 binary = os.environ.get("RUSTFMT")
100 if binary:
101 return binary
103 rust_path = os.path.join(get_tools_dir(), "rustc", "bin")
104 return which("rustfmt", path=os.pathsep.join([rust_path, os.environ["PATH"]]))
107 def get_rustfmt_version(binary):
109 Returns found binary's version
111 try:
112 output = subprocess.check_output(
113 [binary, "--version"],
114 stderr=subprocess.STDOUT,
115 universal_newlines=True,
117 except subprocess.CalledProcessError as e:
118 output = e.output
120 version = re.findall(r"\d.\d+.\d+", output)[0]
121 return Version(version)
124 def lint(paths, config, fix=None, **lintargs):
125 log = lintargs["log"]
126 paths = list(expand_exclusions(paths, config, lintargs["root"]))
128 # An empty path array can occur when the user passes in `-n`. If we don't
129 # return early in this case, rustfmt will attempt to read stdin and hang.
130 if not paths:
131 return []
133 binary = get_rustfmt_binary()
135 if not binary:
136 print(RUSTFMT_NOT_FOUND)
137 if "MOZ_AUTOMATION" in os.environ:
138 return 1
139 return []
141 min_version_str = config.get("min_rustfmt_version")
142 min_version = Version(min_version_str)
143 actual_version = get_rustfmt_version(binary)
144 log.debug(
145 "Found version: {}. Minimal expected version: {}".format(
146 actual_version, min_version
150 if actual_version < min_version:
151 print(RUSTFMT_WRONG_VERSION.format(version=min_version_str))
152 return 1
154 cmd_args = [binary]
155 cmd_args.append("--check")
156 base_command = cmd_args + paths
157 log.debug("Command: {}".format(" ".join(cmd_args)))
158 output = run_process(config, base_command)
160 issues = parse_issues(config, output, paths)
162 if fix:
163 issues["fixed"] = len(issues["results"])
164 issues["results"] = []
165 cmd_args.remove("--check")
167 base_command = cmd_args + paths
168 log.debug("Command: {}".format(" ".join(cmd_args)))
169 output = run_process(config, base_command)
171 return issues