mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
[lint] add lintrunner support for circleci_linter (#67872)
Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/67872 As title. This demonstrates some of the nice features of lintrunner: - Uniform error reporting means you get a nice diff of the changes for free - Can run with -a to just accept the changes (don't need to tell people to run a special regenerate command since the linter adaper already knows how.) Differential Revision: D32187386 D32187386 Test Plan: Imported from OSS Reviewed By: driazati Pulled By: suo fbshipit-source-id: 71de6b042730be80ff6794652039e9bc655a72b1
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b3770766c4
commit
5bb5bfccf7
@ -117,3 +117,15 @@ args = [
|
||||
'--',
|
||||
'@{{PATHSFILE}}'
|
||||
]
|
||||
|
||||
[[linter]]
|
||||
name = 'CIRCLECI'
|
||||
include_patterns=['.circleci/**']
|
||||
args = [
|
||||
'python3',
|
||||
'tools/linter/adapters/circleci_linter.py',
|
||||
'--regen-script-working-dir=.circleci',
|
||||
'--config-yml=.circleci/config.yml',
|
||||
'--regen-script=generate_config_yml.py',
|
||||
]
|
||||
bypass_matched_file_filter = true
|
||||
|
153
tools/linter/adapters/circleci_linter.py
Normal file
153
tools/linter/adapters/circleci_linter.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""
|
||||
Checks that the configuration in .circleci/config.yml has been properly regenerated.
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import List, NamedTuple, Optional
|
||||
import json
|
||||
|
||||
|
||||
CHECKED_IN_FILE = "config.yml"
|
||||
REGENERATION_SCRIPT = "regenerate.sh"
|
||||
|
||||
PARENT_DIR = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
|
||||
README_PATH = os.path.join(PARENT_DIR, "README.md")
|
||||
|
||||
|
||||
class LintSeverity(str, Enum):
|
||||
ERROR = "error"
|
||||
WARNING = "warning"
|
||||
ADVICE = "advice"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
severity: LintSeverity
|
||||
name: str
|
||||
original: Optional[str]
|
||||
replacement: Optional[str]
|
||||
description: Optional[str]
|
||||
bypassChangedLineFiltering: Optional[bool]
|
||||
|
||||
|
||||
IS_WINDOWS: bool = os.name == "nt"
|
||||
|
||||
|
||||
def as_posix(name: str) -> str:
|
||||
return name.replace("\\", "/") if IS_WINDOWS else name
|
||||
|
||||
|
||||
def run_command(args: List[str], cwd: str) -> "subprocess.CompletedProcess[bytes]":
|
||||
logging.debug("$ %s", " ".join(args))
|
||||
start_time = time.monotonic()
|
||||
try:
|
||||
return subprocess.run(
|
||||
args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True,
|
||||
)
|
||||
finally:
|
||||
end_time = time.monotonic()
|
||||
logging.debug("took %dms", (end_time - start_time) * 1000)
|
||||
|
||||
|
||||
def run_check(
|
||||
regen_script_working_dir: str, regen_script: str, config_file: str
|
||||
) -> List[LintMessage]:
|
||||
try:
|
||||
proc = run_command(["python3", regen_script], regen_script_working_dir)
|
||||
except Exception as err:
|
||||
return [
|
||||
LintMessage(
|
||||
path=None,
|
||||
line=None,
|
||||
char=None,
|
||||
code="CIRCLECI",
|
||||
severity=LintSeverity.ERROR,
|
||||
name="command-failed",
|
||||
original=None,
|
||||
replacement=None,
|
||||
description=(
|
||||
f"Failed due to {err.__class__.__name__}:\n{err}"
|
||||
if not isinstance(err, subprocess.CalledProcessError)
|
||||
else (
|
||||
"COMMAND (exit code {returncode})\n"
|
||||
"{command}\n\n"
|
||||
"STDERR\n{stderr}\n\n"
|
||||
"STDOUT\n{stdout}"
|
||||
).format(
|
||||
returncode=err.returncode,
|
||||
command=" ".join(as_posix(x) for x in err.cmd),
|
||||
stderr=err.stderr.decode("utf-8").strip() or "(empty)",
|
||||
stdout=err.stdout.decode("utf-8").strip() or "(empty)",
|
||||
)
|
||||
),
|
||||
bypassChangedLineFiltering=None,
|
||||
)
|
||||
]
|
||||
|
||||
with open(config_file, mode="rb") as f:
|
||||
config = f.read()
|
||||
if proc.stdout == config:
|
||||
return []
|
||||
|
||||
return [
|
||||
LintMessage(
|
||||
path=config_file,
|
||||
line=1,
|
||||
char=1,
|
||||
code="CIRCLECI",
|
||||
severity=LintSeverity.ERROR,
|
||||
name="config inconsistency",
|
||||
original=config.decode("utf-8"),
|
||||
replacement=proc.stdout.decode("utf-8"),
|
||||
description=(
|
||||
"The checked-in CircleCI config.yml file does not match what was generated by the scripts. "
|
||||
"Re-run with '-a' to accept changes."
|
||||
),
|
||||
bypassChangedLineFiltering=True,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="circleci consistency linter", fromfile_prefix_chars="@",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config-yml", required=True, help="location of config.yml",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--regen-script-working-dir",
|
||||
required=True,
|
||||
help="this script will chdir to this argument before running --regen-script",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--regen-script",
|
||||
required=True,
|
||||
help="location of the config generation script, relative to --regen-script-working-dir",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", action="store_true", help="verbose logging",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(
|
||||
format="<%(threadName)s:%(levelname)s> %(message)s",
|
||||
level=logging.NOTSET if args.verbose else logging.DEBUG,
|
||||
stream=sys.stderr,
|
||||
)
|
||||
|
||||
for lint_message in run_check(
|
||||
args.regen_script_working_dir, args.regen_script, args.config_yml
|
||||
):
|
||||
print(json.dumps(lint_message._asdict()), flush=True)
|
@ -25,7 +25,7 @@ class LintSeverity(str, Enum):
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: str
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
|
@ -28,7 +28,7 @@ class LintSeverity(str, Enum):
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: str
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
|
@ -26,7 +26,7 @@ class LintSeverity(str, Enum):
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: str
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
|
@ -24,7 +24,7 @@ class LintSeverity(str, Enum):
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: str
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
@ -107,7 +107,7 @@ def main() -> None:
|
||||
proc = run_command(["grep", "-nPH", args.pattern, *args.filenames])
|
||||
except OSError as err:
|
||||
err_msg = LintMessage(
|
||||
path="<none>",
|
||||
path=None,
|
||||
line=None,
|
||||
char=None,
|
||||
code=args.linter_name,
|
||||
|
@ -26,7 +26,7 @@ class LintSeverity(str, Enum):
|
||||
|
||||
|
||||
class LintMessage(NamedTuple):
|
||||
path: str
|
||||
path: Optional[str]
|
||||
line: Optional[int]
|
||||
char: Optional[int]
|
||||
code: str
|
||||
@ -97,7 +97,7 @@ def check_file(
|
||||
except OSError as err:
|
||||
return [
|
||||
LintMessage(
|
||||
path=filename,
|
||||
path=None,
|
||||
line=None,
|
||||
char=None,
|
||||
code="MYPY",
|
||||
|
Reference in New Issue
Block a user