Skip to content

Commit 5c20d61

Browse files
PaliCfacebook-github-bot
authored andcommitted
Copy over lintrunner from pytorch (#288)
Summary: Pull Request resolved: #288 Here we copy over the lintrunner code from pytorch/pytorch and into multipy. This PR is purely here to make reviewing #289 easier. This PR should be a no-op in all respects. Test Plan: Imported from OSS Reviewed By: kurman Differential Revision: D42256287 Pulled By: PaliC fbshipit-source-id: 4f161472d0fc0804f92ba73493aff6b7741b11d5
1 parent 433822d commit 5c20d61

25 files changed

+4331
-0
lines changed

.lintrunner.toml

Lines changed: 872 additions & 0 deletions
Large diffs are not rendered by default.

scripts/linter/__init__.py

Whitespace-only changes.

scripts/linter/adapters/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# lintrunner adapters
2+
3+
These files adapt our various linters to work with `lintrunner`.
4+
5+
## Adding a new linter
6+
1. init and linter
7+
2. {{DRYRUN}} and {{PATHSFILE}}
8+
3. never exit uncleanly
9+
4. Communication protocol
10+
5. Self-contained
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import argparse
2+
import concurrent.futures
3+
import json
4+
import logging
5+
import os
6+
import re
7+
import subprocess
8+
import time
9+
from enum import Enum
10+
from typing import List, NamedTuple, Optional, Pattern
11+
12+
13+
LINTER_CODE = "ACTIONLINT"
14+
15+
16+
class LintSeverity(str, Enum):
17+
ERROR = "error"
18+
WARNING = "warning"
19+
ADVICE = "advice"
20+
DISABLED = "disabled"
21+
22+
23+
class LintMessage(NamedTuple):
24+
path: Optional[str]
25+
line: Optional[int]
26+
char: Optional[int]
27+
code: str
28+
severity: LintSeverity
29+
name: str
30+
original: Optional[str]
31+
replacement: Optional[str]
32+
description: Optional[str]
33+
34+
35+
RESULTS_RE: Pattern[str] = re.compile(
36+
r"""(?mx)
37+
^
38+
(?P<file>.*?):
39+
(?P<line>\d+):
40+
(?P<char>\d+):
41+
\s(?P<message>.*)
42+
\s(?P<code>\[.*\])
43+
$
44+
"""
45+
)
46+
47+
48+
def run_command(
49+
args: List[str],
50+
) -> "subprocess.CompletedProcess[bytes]":
51+
logging.debug("$ %s", " ".join(args))
52+
start_time = time.monotonic()
53+
try:
54+
return subprocess.run(
55+
args,
56+
stdout=subprocess.PIPE,
57+
stderr=subprocess.PIPE,
58+
)
59+
finally:
60+
end_time = time.monotonic()
61+
logging.debug("took %dms", (end_time - start_time) * 1000)
62+
63+
64+
def check_file(
65+
binary: str,
66+
file: str,
67+
) -> List[LintMessage]:
68+
try:
69+
proc = run_command([binary, file])
70+
except OSError as err:
71+
return [
72+
LintMessage(
73+
path=None,
74+
line=None,
75+
char=None,
76+
code=LINTER_CODE,
77+
severity=LintSeverity.ERROR,
78+
name="command-failed",
79+
original=None,
80+
replacement=None,
81+
description=(f"Failed due to {err.__class__.__name__}:\n{err}"),
82+
)
83+
]
84+
stdout = str(proc.stdout, "utf-8").strip()
85+
return [
86+
LintMessage(
87+
path=match["file"],
88+
name=match["code"],
89+
description=match["message"],
90+
line=int(match["line"]),
91+
char=int(match["char"]),
92+
code=LINTER_CODE,
93+
severity=LintSeverity.ERROR,
94+
original=None,
95+
replacement=None,
96+
)
97+
for match in RESULTS_RE.finditer(stdout)
98+
]
99+
100+
101+
if __name__ == "__main__":
102+
parser = argparse.ArgumentParser(
103+
description="actionlint runner",
104+
fromfile_prefix_chars="@",
105+
)
106+
parser.add_argument(
107+
"--binary",
108+
required=True,
109+
help="actionlint binary path",
110+
)
111+
parser.add_argument(
112+
"filenames",
113+
nargs="+",
114+
help="paths to lint",
115+
)
116+
117+
args = parser.parse_args()
118+
119+
if not os.path.exists(args.binary):
120+
err_msg = LintMessage(
121+
path="<none>",
122+
line=None,
123+
char=None,
124+
code=LINTER_CODE,
125+
severity=LintSeverity.ERROR,
126+
name="command-failed",
127+
original=None,
128+
replacement=None,
129+
description=(
130+
f"Could not find actionlint binary at {args.binary},"
131+
" you may need to run `lintrunner init`."
132+
),
133+
)
134+
print(json.dumps(err_msg._asdict()), flush=True)
135+
exit(0)
136+
137+
with concurrent.futures.ThreadPoolExecutor(
138+
max_workers=os.cpu_count(),
139+
thread_name_prefix="Thread",
140+
) as executor:
141+
futures = {
142+
executor.submit(
143+
check_file,
144+
args.binary,
145+
filename,
146+
): filename
147+
for filename in args.filenames
148+
}
149+
for future in concurrent.futures.as_completed(futures):
150+
try:
151+
for lint_message in future.result():
152+
print(json.dumps(lint_message._asdict()), flush=True)
153+
except Exception:
154+
logging.critical('Failed at "%s".', futures[future])
155+
raise
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import argparse
2+
import concurrent.futures
3+
import json
4+
import logging
5+
import os
6+
import subprocess
7+
import sys
8+
import time
9+
from enum import Enum
10+
from typing import Any, BinaryIO, List, NamedTuple, Optional
11+
12+
13+
IS_WINDOWS: bool = os.name == "nt"
14+
15+
16+
def eprint(*args: Any, **kwargs: Any) -> None:
17+
print(*args, file=sys.stderr, flush=True, **kwargs)
18+
19+
20+
class LintSeverity(str, Enum):
21+
ERROR = "error"
22+
WARNING = "warning"
23+
ADVICE = "advice"
24+
DISABLED = "disabled"
25+
26+
27+
class LintMessage(NamedTuple):
28+
path: Optional[str]
29+
line: Optional[int]
30+
char: Optional[int]
31+
code: str
32+
severity: LintSeverity
33+
name: str
34+
original: Optional[str]
35+
replacement: Optional[str]
36+
description: Optional[str]
37+
38+
39+
def as_posix(name: str) -> str:
40+
return name.replace("\\", "/") if IS_WINDOWS else name
41+
42+
43+
def _run_command(
44+
args: List[str],
45+
*,
46+
stdin: BinaryIO,
47+
timeout: int,
48+
) -> "subprocess.CompletedProcess[bytes]":
49+
logging.debug("$ %s", " ".join(args))
50+
start_time = time.monotonic()
51+
try:
52+
return subprocess.run(
53+
args,
54+
stdin=stdin,
55+
stdout=subprocess.PIPE,
56+
stderr=subprocess.PIPE,
57+
shell=IS_WINDOWS, # So batch scripts are found.
58+
timeout=timeout,
59+
check=True,
60+
)
61+
finally:
62+
end_time = time.monotonic()
63+
logging.debug("took %dms", (end_time - start_time) * 1000)
64+
65+
66+
def run_command(
67+
args: List[str],
68+
*,
69+
stdin: BinaryIO,
70+
retries: int,
71+
timeout: int,
72+
) -> "subprocess.CompletedProcess[bytes]":
73+
remaining_retries = retries
74+
while True:
75+
try:
76+
return _run_command(args, stdin=stdin, timeout=timeout)
77+
except subprocess.TimeoutExpired as err:
78+
if remaining_retries == 0:
79+
raise err
80+
remaining_retries -= 1
81+
logging.warning(
82+
"(%s/%s) Retrying because command failed with: %r",
83+
retries - remaining_retries,
84+
retries,
85+
err,
86+
)
87+
time.sleep(1)
88+
89+
90+
def check_file(
91+
filename: str,
92+
retries: int,
93+
timeout: int,
94+
) -> List[LintMessage]:
95+
try:
96+
with open(filename, "rb") as f:
97+
original = f.read()
98+
with open(filename, "rb") as f:
99+
proc = run_command(
100+
[sys.executable, "-mblack", "--stdin-filename", filename, "-"],
101+
stdin=f,
102+
retries=retries,
103+
timeout=timeout,
104+
)
105+
except subprocess.TimeoutExpired:
106+
return [
107+
LintMessage(
108+
path=filename,
109+
line=None,
110+
char=None,
111+
code="BLACK",
112+
severity=LintSeverity.ERROR,
113+
name="timeout",
114+
original=None,
115+
replacement=None,
116+
description=(
117+
"black timed out while trying to process a file. "
118+
"Please report an issue in pytorch/pytorch with the "
119+
"label 'module: lint'"
120+
),
121+
)
122+
]
123+
except (OSError, subprocess.CalledProcessError) as err:
124+
return [
125+
LintMessage(
126+
path=filename,
127+
line=None,
128+
char=None,
129+
code="BLACK",
130+
severity=LintSeverity.ADVICE,
131+
name="command-failed",
132+
original=None,
133+
replacement=None,
134+
description=(
135+
f"Failed due to {err.__class__.__name__}:\n{err}"
136+
if not isinstance(err, subprocess.CalledProcessError)
137+
else (
138+
"COMMAND (exit code {returncode})\n"
139+
"{command}\n\n"
140+
"STDERR\n{stderr}\n\n"
141+
"STDOUT\n{stdout}"
142+
).format(
143+
returncode=err.returncode,
144+
command=" ".join(as_posix(x) for x in err.cmd),
145+
stderr=err.stderr.decode("utf-8").strip() or "(empty)",
146+
stdout=err.stdout.decode("utf-8").strip() or "(empty)",
147+
)
148+
),
149+
)
150+
]
151+
152+
replacement = proc.stdout
153+
if original == replacement:
154+
return []
155+
156+
return [
157+
LintMessage(
158+
path=filename,
159+
line=None,
160+
char=None,
161+
code="BLACK",
162+
severity=LintSeverity.WARNING,
163+
name="format",
164+
original=original.decode("utf-8"),
165+
replacement=replacement.decode("utf-8"),
166+
description="Run `lintrunner -a` to apply this patch.",
167+
)
168+
]
169+
170+
171+
def main() -> None:
172+
parser = argparse.ArgumentParser(
173+
description="Format files with black.",
174+
fromfile_prefix_chars="@",
175+
)
176+
parser.add_argument(
177+
"--retries",
178+
default=3,
179+
type=int,
180+
help="times to retry timed out black",
181+
)
182+
parser.add_argument(
183+
"--timeout",
184+
default=90,
185+
type=int,
186+
help="seconds to wait for black",
187+
)
188+
parser.add_argument(
189+
"--verbose",
190+
action="store_true",
191+
help="verbose logging",
192+
)
193+
parser.add_argument(
194+
"filenames",
195+
nargs="+",
196+
help="paths to lint",
197+
)
198+
args = parser.parse_args()
199+
200+
logging.basicConfig(
201+
format="<%(threadName)s:%(levelname)s> %(message)s",
202+
level=logging.NOTSET
203+
if args.verbose
204+
else logging.DEBUG
205+
if len(args.filenames) < 1000
206+
else logging.INFO,
207+
stream=sys.stderr,
208+
)
209+
210+
with concurrent.futures.ThreadPoolExecutor(
211+
max_workers=os.cpu_count(),
212+
thread_name_prefix="Thread",
213+
) as executor:
214+
futures = {
215+
executor.submit(check_file, x, args.retries, args.timeout): x
216+
for x in args.filenames
217+
}
218+
for future in concurrent.futures.as_completed(futures):
219+
try:
220+
for lint_message in future.result():
221+
print(json.dumps(lint_message._asdict()), flush=True)
222+
except Exception:
223+
logging.critical('Failed at "%s".', futures[future])
224+
raise
225+
226+
227+
if __name__ == "__main__":
228+
main()

0 commit comments

Comments
 (0)