xref: /aosp_15_r20/external/pytorch/tools/extract_scripts.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1*da0073e9SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*da0073e9SAndroid Build Coastguard Worker
3*da0073e9SAndroid Build Coastguard Workerfrom __future__ import annotations
4*da0073e9SAndroid Build Coastguard Worker
5*da0073e9SAndroid Build Coastguard Workerimport argparse
6*da0073e9SAndroid Build Coastguard Workerimport re
7*da0073e9SAndroid Build Coastguard Workerimport sys
8*da0073e9SAndroid Build Coastguard Workerfrom pathlib import Path
9*da0073e9SAndroid Build Coastguard Workerfrom typing import Any, Dict
10*da0073e9SAndroid Build Coastguard Workerfrom typing_extensions import TypedDict  # Python 3.11+
11*da0073e9SAndroid Build Coastguard Worker
12*da0073e9SAndroid Build Coastguard Workerimport yaml
13*da0073e9SAndroid Build Coastguard Worker
14*da0073e9SAndroid Build Coastguard Worker
15*da0073e9SAndroid Build Coastguard WorkerStep = Dict[str, Any]
16*da0073e9SAndroid Build Coastguard Worker
17*da0073e9SAndroid Build Coastguard Worker
18*da0073e9SAndroid Build Coastguard Workerclass Script(TypedDict):
19*da0073e9SAndroid Build Coastguard Worker    extension: str
20*da0073e9SAndroid Build Coastguard Worker    script: str
21*da0073e9SAndroid Build Coastguard Worker
22*da0073e9SAndroid Build Coastguard Worker
23*da0073e9SAndroid Build Coastguard Workerdef extract(step: Step) -> Script | None:
24*da0073e9SAndroid Build Coastguard Worker    run = step.get("run")
25*da0073e9SAndroid Build Coastguard Worker
26*da0073e9SAndroid Build Coastguard Worker    # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
27*da0073e9SAndroid Build Coastguard Worker    shell = step.get("shell", "bash")
28*da0073e9SAndroid Build Coastguard Worker    extension = {
29*da0073e9SAndroid Build Coastguard Worker        "bash": ".sh",
30*da0073e9SAndroid Build Coastguard Worker        "pwsh": ".ps1",
31*da0073e9SAndroid Build Coastguard Worker        "python": ".py",
32*da0073e9SAndroid Build Coastguard Worker        "sh": ".sh",
33*da0073e9SAndroid Build Coastguard Worker        "cmd": ".cmd",
34*da0073e9SAndroid Build Coastguard Worker        "powershell": ".ps1",
35*da0073e9SAndroid Build Coastguard Worker    }.get(shell)
36*da0073e9SAndroid Build Coastguard Worker
37*da0073e9SAndroid Build Coastguard Worker    is_gh_script = step.get("uses", "").startswith("actions/github-script@")
38*da0073e9SAndroid Build Coastguard Worker    gh_script = step.get("with", {}).get("script")
39*da0073e9SAndroid Build Coastguard Worker
40*da0073e9SAndroid Build Coastguard Worker    if run is not None and extension is not None:
41*da0073e9SAndroid Build Coastguard Worker        script = {
42*da0073e9SAndroid Build Coastguard Worker            "bash": f"#!/usr/bin/env bash\nset -eo pipefail\n{run}",
43*da0073e9SAndroid Build Coastguard Worker            "sh": f"#!/usr/bin/env sh\nset -e\n{run}",
44*da0073e9SAndroid Build Coastguard Worker        }.get(shell, run)
45*da0073e9SAndroid Build Coastguard Worker        return {"extension": extension, "script": script}
46*da0073e9SAndroid Build Coastguard Worker    elif is_gh_script and gh_script is not None:
47*da0073e9SAndroid Build Coastguard Worker        return {"extension": ".js", "script": gh_script}
48*da0073e9SAndroid Build Coastguard Worker    else:
49*da0073e9SAndroid Build Coastguard Worker        return None
50*da0073e9SAndroid Build Coastguard Worker
51*da0073e9SAndroid Build Coastguard Worker
52*da0073e9SAndroid Build Coastguard Workerdef main() -> None:
53*da0073e9SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
54*da0073e9SAndroid Build Coastguard Worker    parser.add_argument("--out", required=True)
55*da0073e9SAndroid Build Coastguard Worker    args = parser.parse_args()
56*da0073e9SAndroid Build Coastguard Worker
57*da0073e9SAndroid Build Coastguard Worker    out = Path(args.out)
58*da0073e9SAndroid Build Coastguard Worker    if out.exists():
59*da0073e9SAndroid Build Coastguard Worker        sys.exit(f"{out} already exists; aborting to avoid overwriting")
60*da0073e9SAndroid Build Coastguard Worker
61*da0073e9SAndroid Build Coastguard Worker    gha_expressions_found = False
62*da0073e9SAndroid Build Coastguard Worker
63*da0073e9SAndroid Build Coastguard Worker    for p in Path(".github/workflows").iterdir():
64*da0073e9SAndroid Build Coastguard Worker        with open(p, "rb") as f:
65*da0073e9SAndroid Build Coastguard Worker            workflow = yaml.safe_load(f)
66*da0073e9SAndroid Build Coastguard Worker
67*da0073e9SAndroid Build Coastguard Worker        for job_name, job in workflow["jobs"].items():
68*da0073e9SAndroid Build Coastguard Worker            job_dir = out / p / job_name
69*da0073e9SAndroid Build Coastguard Worker            if "steps" not in job:
70*da0073e9SAndroid Build Coastguard Worker                continue
71*da0073e9SAndroid Build Coastguard Worker            steps = job["steps"]
72*da0073e9SAndroid Build Coastguard Worker            index_chars = len(str(len(steps) - 1))
73*da0073e9SAndroid Build Coastguard Worker            for i, step in enumerate(steps, start=1):
74*da0073e9SAndroid Build Coastguard Worker                extracted = extract(step)
75*da0073e9SAndroid Build Coastguard Worker                if extracted:
76*da0073e9SAndroid Build Coastguard Worker                    script = extracted["script"]
77*da0073e9SAndroid Build Coastguard Worker                    step_name = step.get("name", "")
78*da0073e9SAndroid Build Coastguard Worker                    if "${{" in script:
79*da0073e9SAndroid Build Coastguard Worker                        gha_expressions_found = True
80*da0073e9SAndroid Build Coastguard Worker                        print(
81*da0073e9SAndroid Build Coastguard Worker                            f"{p} job `{job_name}` step {i}: {step_name}",
82*da0073e9SAndroid Build Coastguard Worker                            file=sys.stderr,
83*da0073e9SAndroid Build Coastguard Worker                        )
84*da0073e9SAndroid Build Coastguard Worker
85*da0073e9SAndroid Build Coastguard Worker                    job_dir.mkdir(parents=True, exist_ok=True)
86*da0073e9SAndroid Build Coastguard Worker
87*da0073e9SAndroid Build Coastguard Worker                    sanitized = re.sub(
88*da0073e9SAndroid Build Coastguard Worker                        "[^a-zA-Z_]+",
89*da0073e9SAndroid Build Coastguard Worker                        "_",
90*da0073e9SAndroid Build Coastguard Worker                        f"_{step_name}",
91*da0073e9SAndroid Build Coastguard Worker                    ).rstrip("_")
92*da0073e9SAndroid Build Coastguard Worker                    extension = extracted["extension"]
93*da0073e9SAndroid Build Coastguard Worker                    filename = f"{i:0{index_chars}}{sanitized}{extension}"
94*da0073e9SAndroid Build Coastguard Worker                    (job_dir / filename).write_text(script)
95*da0073e9SAndroid Build Coastguard Worker
96*da0073e9SAndroid Build Coastguard Worker    if gha_expressions_found:
97*da0073e9SAndroid Build Coastguard Worker        sys.exit(
98*da0073e9SAndroid Build Coastguard Worker            "Each of the above scripts contains a GitHub Actions "
99*da0073e9SAndroid Build Coastguard Worker            "${{ <expression> }} which must be replaced with an `env` variable"
100*da0073e9SAndroid Build Coastguard Worker            " for security reasons."
101*da0073e9SAndroid Build Coastguard Worker        )
102*da0073e9SAndroid Build Coastguard Worker
103*da0073e9SAndroid Build Coastguard Worker
104*da0073e9SAndroid Build Coastguard Workerif __name__ == "__main__":
105*da0073e9SAndroid Build Coastguard Worker    main()
106