forked from pytorch/pytorch
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[lint] create a workflow consistency linter (pytorch#80200)
In order to maintain consistency between jobs, introduce a linter that checks whether jobs sharing the same `sync-tag` are indeed the same. `sync-tag` is just a dummy input on the reusable workflow. I chose to use a dummy input over the following alternatives: - The job's id isn't great, because we are likely to change a job's id (say, when upgrading CUDA or linux versions) - The job's name doesn't work as we have build/test jobs that share the same name Pull Request resolved: pytorch#80200 Approved by: https://github.com/janeyx99
- Loading branch information
1 parent
769b446
commit 769df74
Showing
17 changed files
with
230 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
"""Checks for consistency of jobs between different GitHub workflows. | ||
Any job with a specific `sync-tag` must match all other jobs with the same `sync-tag`. | ||
""" | ||
import argparse | ||
import itertools | ||
import json | ||
from pathlib import Path | ||
from typing import Iterable, Any, Optional, NamedTuple, Dict | ||
from enum import Enum | ||
from collections import defaultdict | ||
|
||
from yaml import load, CSafeLoader, dump | ||
|
||
|
||
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] | ||
|
||
|
||
def glob_yamls(path: Path) -> Iterable[Path]: | ||
return itertools.chain(path.glob("**/*.yml"), path.glob("**/*.yaml")) | ||
|
||
|
||
def load_yaml(path: Path) -> Any: | ||
with open(path) as f: | ||
return load(f, CSafeLoader) | ||
|
||
|
||
def is_workflow(yaml: Any) -> bool: | ||
return yaml.get("jobs") is not None | ||
|
||
|
||
def print_lint_message(path: Path, job: Dict[str, Any], sync_tag: str) -> None: | ||
job_id = list(job.keys())[0] | ||
with open(path) as f: | ||
lines = f.readlines() | ||
for i, line in enumerate(lines): | ||
if f"{job_id}:" in line: | ||
line_number = i + 1 | ||
|
||
lint_message = LintMessage( | ||
path=str(path), | ||
line=line_number, | ||
char=None, | ||
code="WORKFLOWSYNC", | ||
severity=LintSeverity.ERROR, | ||
name="workflow-inconsistency", | ||
original=None, | ||
replacement=None, | ||
description=f"Job doesn't match other jobs with sync-tag: '{sync_tag}'", | ||
) | ||
print(json.dumps(lint_message._asdict()), flush=True) | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser( | ||
description="workflow consistency linter.", | ||
fromfile_prefix_chars="@", | ||
) | ||
parser.add_argument( | ||
"filenames", | ||
nargs="+", | ||
help="paths to lint", | ||
) | ||
args = parser.parse_args() | ||
|
||
# Go through the provided files, aggregating jobs with the same sync tag | ||
tag_to_jobs = defaultdict(list) | ||
for path in args.filenames: | ||
workflow = load_yaml(Path(path)) | ||
jobs = workflow["jobs"] | ||
for job_id, job in jobs.items(): | ||
try: | ||
sync_tag = job["with"]["sync-tag"] | ||
except KeyError: | ||
continue | ||
|
||
# remove the "if" field, which we allow to be different between jobs | ||
# (since you might have different triggering conditions on pull vs. | ||
# trunk, say.) | ||
if "if" in job: | ||
del job["if"] | ||
|
||
tag_to_jobs[sync_tag].append((path, {job_id: job})) | ||
|
||
# For each sync tag, check that all the jobs have the same code. | ||
for sync_tag, path_and_jobs in tag_to_jobs.items(): | ||
baseline_path, baseline_dict = path_and_jobs.pop() | ||
baseline_str = dump(baseline_dict) | ||
|
||
printed_baseline = False | ||
|
||
for path, job_dict in path_and_jobs: | ||
job_str = dump(job_dict) | ||
if baseline_str != job_str: | ||
print_lint_message(path, job_dict, sync_tag) | ||
|
||
if not printed_baseline: | ||
print_lint_message(baseline_path, baseline_dict, sync_tag) | ||
printed_baseline = True |