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/.
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
26 RUSTFMT_INSTALL_ERROR
= """
27 Unable to install correct version of rustfmt
28 Try to install it manually with:
29 $ rustup component add rustfmt
33 RUSTFMT_WRONG_VERSION
= """
34 You are probably using an old version of rustfmt.
35 Expected version is {version}.
37 $ rustup update stable
41 def parse_issues(config
, output
, paths
):
42 RustfmtDiff
= namedtuple("RustfmtDiff", ["file", "line", "diff"])
44 diff_line
= re
.compile("^Diff in (.*)(?: at line |:)([0-9]*):")
48 for line
in output
.split(b
"\n"):
50 line
.decode("utf-8", "replace") if isinstance(line
, bytes
) else line
52 match
= diff_line
.match(processed_line
)
55 issues
.append(RustfmtDiff(file, line_no
, diff
.rstrip("\n")))
57 file, line_no
= match
.groups()
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))
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
]):
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
)
86 output
, _
= proc
.communicate()
88 except KeyboardInterrupt:
94 def get_rustfmt_binary():
96 Returns the path of the first rustfmt binary available
97 if not found returns None
99 binary
= os
.environ
.get("RUSTFMT")
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
112 output
= subprocess
.check_output(
113 [binary
, "--version"],
114 stderr
=subprocess
.STDOUT
,
115 universal_newlines
=True,
117 except subprocess
.CalledProcessError
as e
:
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.
133 binary
= get_rustfmt_binary()
136 print(RUSTFMT_NOT_FOUND
)
137 if "MOZ_AUTOMATION" in os
.environ
:
141 min_version_str
= config
.get("min_rustfmt_version")
142 min_version
= Version(min_version_str
)
143 actual_version
= get_rustfmt_version(binary
)
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
))
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
)
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
)