Skip to content

Commit 4152c66

Browse files
committed
[paved path] add lintrunner following pytorch
1 parent bc3c7e9 commit 4152c66

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Black + usort
2+
[[linter]]
3+
code = 'UFMT'
4+
include_patterns = [
5+
'**/*.py',
6+
]
7+
command = [
8+
'python3',
9+
'tools/linter/ufmt_linter.py',
10+
'--',
11+
'@{{PATHSFILE}}'
12+
]
13+
init_command = [
14+
'python3',
15+
'tools/linter/pip_init.py',
16+
'--dry-run={{DRYRUN}}',
17+
'black==22.3.0',
18+
'ufmt==1.3.3',
19+
'usort==1.0.2',
20+
]
21+
is_formatter = true
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
# All rights reserved.
4+
#
5+
# This source code is licensed under the BSD-style license found in the
6+
# LICENSE file in the root directory of this source tree.
7+
8+
import argparse
9+
import logging
10+
import os
11+
import subprocess
12+
import sys
13+
import time
14+
15+
from typing import List
16+
17+
18+
def run_command(args: List[str]) -> "subprocess.CompletedProcess[bytes]":
19+
logging.debug("$ %s", " ".join(args))
20+
start_time = time.monotonic()
21+
try:
22+
return subprocess.run(args, check=True)
23+
finally:
24+
end_time = time.monotonic()
25+
logging.debug("took %dms", (end_time - start_time) * 1000)
26+
27+
28+
if __name__ == "__main__":
29+
parser = argparse.ArgumentParser(description="pip initializer")
30+
parser.add_argument(
31+
"packages",
32+
nargs="+",
33+
help="pip packages to install",
34+
)
35+
parser.add_argument(
36+
"--verbose",
37+
action="store_true",
38+
help="verbose logging",
39+
)
40+
parser.add_argument(
41+
"--dry-run", help="do not install anything, just print what would be done."
42+
)
43+
44+
args = parser.parse_args()
45+
46+
logging.basicConfig(
47+
format="<%(threadName)s:%(levelname)s> %(message)s",
48+
level=logging.NOTSET if args.verbose else logging.DEBUG,
49+
stream=sys.stderr,
50+
)
51+
52+
for package in args.packages:
53+
package_name, _, version = package.partition("=")
54+
if version == "":
55+
raise RuntimeError(
56+
"Package {package_name} did not have a version specified. "
57+
"Please specify a version to product a consistent linting experience."
58+
)
59+
pip_args = ["pip3", "install"]
60+
61+
# If we are in a global install, use `--user` to install so that you do not
62+
# need root access in order to initialize linters.
63+
#
64+
# However, `pip install --user` interacts poorly with virtualenvs (see:
65+
# https://bit.ly/3vD4kvl) and conda (see: https://bit.ly/3KG7ZfU). So in
66+
# these cases perform a regular installation.
67+
in_conda = os.environ.get("CONDA_PREFIX") is not None
68+
in_virtualenv = os.environ.get("VIRTUAL_ENV") is not None
69+
if not in_conda and not in_virtualenv:
70+
pip_args.append("--user")
71+
72+
pip_args.extend(args.packages)
73+
74+
dry_run = args.dry_run == "1"
75+
if dry_run:
76+
print(f"Would have run: {pip_args}")
77+
sys.exit(0)
78+
79+
run_command(pip_args)
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import argparse
2+
import concurrent.futures
3+
import json
4+
import logging
5+
import os
6+
import sys
7+
from enum import Enum
8+
from pathlib import Path
9+
from typing import Any, List, NamedTuple, Optional
10+
11+
from ufmt.core import make_black_config, ufmt_string
12+
from usort import Config as UsortConfig
13+
14+
IS_WINDOWS: bool = os.name == "nt"
15+
16+
17+
def eprint(*args: Any, **kwargs: Any) -> None:
18+
print(*args, file=sys.stderr, flush=True, **kwargs)
19+
20+
21+
class LintSeverity(str, Enum):
22+
ERROR = "error"
23+
WARNING = "warning"
24+
ADVICE = "advice"
25+
DISABLED = "disabled"
26+
27+
28+
class LintMessage(NamedTuple):
29+
path: Optional[str]
30+
line: Optional[int]
31+
char: Optional[int]
32+
code: str
33+
severity: LintSeverity
34+
name: str
35+
original: Optional[str]
36+
replacement: Optional[str]
37+
description: Optional[str]
38+
39+
40+
def as_posix(name: str) -> str:
41+
return name.replace("\\", "/") if IS_WINDOWS else name
42+
43+
44+
def format_error_message(filename: str, err: Exception) -> LintMessage:
45+
return LintMessage(
46+
path=filename,
47+
line=None,
48+
char=None,
49+
code="UFMT",
50+
severity=LintSeverity.ADVICE,
51+
name="command-failed",
52+
original=None,
53+
replacement=None,
54+
description=(f"Failed due to {err.__class__.__name__}:\n{err}"),
55+
)
56+
57+
58+
def check_file(
59+
filename: str,
60+
) -> List[LintMessage]:
61+
with open(filename, "rb") as f:
62+
original = f.read().decode("utf-8")
63+
64+
try:
65+
path = Path(filename)
66+
67+
usort_config = UsortConfig.find(path)
68+
black_config = make_black_config(path)
69+
70+
# Use UFMT API to call both usort and black
71+
replacement = ufmt_string(
72+
path=path,
73+
content=original,
74+
usort_config=usort_config,
75+
black_config=black_config,
76+
)
77+
78+
if original == replacement:
79+
return []
80+
81+
return [
82+
LintMessage(
83+
path=filename,
84+
line=None,
85+
char=None,
86+
code="UFMT",
87+
severity=LintSeverity.WARNING,
88+
name="format",
89+
original=original,
90+
replacement=replacement,
91+
description="Run `lintrunner -a` to apply this patch.",
92+
)
93+
]
94+
except Exception as err:
95+
return [format_error_message(filename, err)]
96+
97+
98+
def main() -> None:
99+
parser = argparse.ArgumentParser(
100+
description="Format files with ufmt (black + usort).",
101+
fromfile_prefix_chars="@",
102+
)
103+
parser.add_argument(
104+
"--verbose",
105+
action="store_true",
106+
help="verbose logging",
107+
)
108+
parser.add_argument(
109+
"filenames",
110+
nargs="+",
111+
help="paths to lint",
112+
)
113+
args = parser.parse_args()
114+
115+
logging.basicConfig(
116+
format="<%(threadName)s:%(levelname)s> %(message)s",
117+
level=logging.NOTSET
118+
if args.verbose
119+
else logging.DEBUG
120+
if len(args.filenames) < 1000
121+
else logging.INFO,
122+
stream=sys.stderr,
123+
)
124+
125+
with concurrent.futures.ThreadPoolExecutor(
126+
max_workers=os.cpu_count(),
127+
thread_name_prefix="Thread",
128+
) as executor:
129+
futures = {executor.submit(check_file, x): x for x in args.filenames}
130+
for future in concurrent.futures.as_completed(futures):
131+
try:
132+
for lint_message in future.result():
133+
print(json.dumps(lint_message._asdict()), flush=True)
134+
except Exception:
135+
logging.critical('Failed at "%s".', futures[future])
136+
raise
137+
138+
139+
if __name__ == "__main__":
140+
main()

0 commit comments

Comments
 (0)