regenerate.py 7.12 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

"""
This script should use a very simple, functional programming style.
Avoid Jinja macros in favor of native Python functions.

Don't go overboard on code generation; use Python only to generate
content that can't be easily declared statically using CircleCI's YAML API.

Data declarations (e.g. the nested loops for defining the configuration matrix)
should be at the top of the file for easy updating.

See this comment for design rationale:
https://github.com/pytorch/vision/pull/1321#issuecomment-531033978
"""
16

17
18
import os.path

19
import jinja2
20
import yaml
21
from jinja2 import select_autoescape
22

23

24
PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11"]
25
CU_VERSIONS_DICT = {
pbialecki's avatar
pbialecki committed
26
    "windows": ["cpu", "cu117", "cu118", "cu121"],
27
}
Nikita Shulga's avatar
Nikita Shulga committed
28

moto's avatar
moto committed
29

30
def build_workflows(prefix="", upload=False, filter_branch=None, indentation=6):
31
    w = []
32
    w += build_download_job(filter_branch)
moto's avatar
moto committed
33
    for os_type in ["windows"]:
34
        w += build_ffmpeg_job(os_type, filter_branch)
moto's avatar
moto committed
35
        for btype in ["wheel", "conda"]:
moto's avatar
moto committed
36
            for python_version in PYTHON_VERSIONS:
Nikita Shulga's avatar
Nikita Shulga committed
37
                for cu_version in CU_VERSIONS_DICT[os_type]:
Matti Picus's avatar
Matti Picus committed
38
39
                    fb = filter_branch
                    w += build_workflow_pair(btype, os_type, python_version, cu_version, fb, prefix, upload)
40
41
42
43

    return indent(indentation, w)


44
45
def build_download_job(filter_branch):
    job = {
46
        "name": "download_third_parties",
47
48
49
50
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
51
52
53
54
55
56
57
58
59
60
61
62
63
    return [{"download_third_parties": job}]


def build_ffmpeg_job(os_type, filter_branch):
    job = {
        "name": f"build_ffmpeg_{os_type}",
        "requires": ["download_third_parties"],
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    job["python_version"] = "foo"
    return [{f"build_ffmpeg_{os_type}": job}]
64
65


66
def build_workflow_pair(btype, os_type, python_version, cu_version, filter_branch, prefix="", upload=False):
67
68

    w = []
Nikita Shulga's avatar
Nikita Shulga committed
69
70
    base_workflow_name = f"{prefix}binary_{os_type}_{btype}_py{python_version}_{cu_version}"
    w.append(generate_base_workflow(base_workflow_name, python_version, cu_version, filter_branch, os_type, btype))
71
72

    if upload:
73
        w.append(generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version))
74

moto's avatar
moto committed
75
76
77
78
79
    if os_type != "macos":
        pydistro = "pip" if btype == "wheel" else "conda"
        w.append(
            generate_smoketest_workflow(
                pydistro, base_workflow_name, filter_branch, python_version, cu_version, os_type
80
            )
moto's avatar
moto committed
81
        )
82
83
84

    return w

moto's avatar
moto committed
85

86
87
88
89
def docstring_parameters_sync_job(filter_branch):
    job = {
        "name": "docstring_parameters_sync",
        "python_version": "3.8",
90
91
92
        "requires": [
            "binary_linux_wheel_py3.8_cpu",
        ],
93
94
95
96
97
98
99
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    return [{"docstring_parameters_sync": job}]


Nikita Shulga's avatar
Nikita Shulga committed
100
def generate_base_workflow(base_workflow_name, python_version, cu_version, filter_branch, os_type, btype):
101
102
103
104

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
105
        "cuda_version": cu_version,
106
        "requires": [f"build_ffmpeg_{os_type}"],
107
108
    }

109
110
111
112
113
    if btype == "conda":
        d["conda_docker_image"] = f'pytorch/conda-builder:{cu_version.replace("cu1","cuda1")}'
    elif cu_version.startswith("cu"):
        d["wheel_docker_image"] = f'pytorch/manylinux-{cu_version.replace("cu1","cuda1")}'
    elif cu_version.startswith("rocm"):
114
        d["wheel_docker_image"] = f"pytorch/manylinux-rocm:{cu_version[len('rocm'):]}"
115

116
117
118
    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

119
    return {f"binary_{os_type}_{btype}": d}
120
121


moto's avatar
moto committed
122
def gen_filter_branch_tree(*branches):
123
124
125
126
127
128
129
130
    return {
        "branches": {
            "only": list(branches),
        },
        "tags": {
            # Using a raw string here to avoid having to escape
            # anything
            "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
131
        },
132
    }
133
134


135
def generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version):
136
137
138
139
140
141
    d = {
        "name": "{base_workflow_name}_upload".format(base_workflow_name=base_workflow_name),
        "context": "org-member",
        "requires": [base_workflow_name],
    }

142
143
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
144

145
146
147
148
149
150
    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

    return {"binary_{btype}_upload".format(btype=btype): d}


Nikita Shulga's avatar
Nikita Shulga committed
151
def generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, cu_version, os_type):
152

Nikita Shulga's avatar
Nikita Shulga committed
153
    smoke_suffix = f"smoke_test_{pydistro}".format(pydistro=pydistro)
154
    d = {
Nikita Shulga's avatar
Nikita Shulga committed
155
        "name": f"{base_workflow_name}_{smoke_suffix}",
moto's avatar
moto committed
156
        "requires": [base_workflow_name],
157
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
158
        "cuda_version": cu_version,
159
160
161
162
163
    }

    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

Caroline Chen's avatar
Caroline Chen committed
164
    smoke_name = f"smoke_test_{os_type}_{pydistro}"
165
    if pydistro == "conda" and (os_type == "linux" or os_type == "windows") and cu_version != "cpu":
Caroline Chen's avatar
Caroline Chen committed
166
167
        smoke_name += "_gpu"
    return {smoke_name: d}
168
169
170
171
172
173


def indent(indentation, data_list):
    return ("\n" + " " * indentation).join(yaml.dump(data_list).splitlines())


174
175
176
177
178
179
180
181
def unittest_python_versions(os):
    return {
        "windows": PYTHON_VERSIONS[:1],
        "macos": PYTHON_VERSIONS[:1],
        "linux": PYTHON_VERSIONS,
    }.get(os)


moto's avatar
moto committed
182
def unittest_workflows(indentation=6):
moto's avatar
moto committed
183
    jobs = []
184
    jobs += build_download_job(None)
moto's avatar
moto committed
185
    for os_type in ["linux", "windows", "macos"]:
moto's avatar
moto committed
186
        for device_type in ["cpu", "gpu"]:
187
            if os_type != "windows" and device_type == "gpu":
moto's avatar
moto committed
188
189
                continue

190
            for i, python_version in enumerate(unittest_python_versions(os_type)):
moto's avatar
moto committed
191
192
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
moto's avatar
moto committed
193
                    "python_version": python_version,
194
                    "cuda_version": "cpu" if device_type == "cpu" else "cu117",
195
                    "requires": ["download_third_parties"],
moto's avatar
moto committed
196
                }
moto's avatar
moto committed
197
198

                jobs.append({f"unittest_{os_type}_{device_type}": job})
199
200

                if i == 0 and os_type == "linux" and device_type == "cpu":
201
202
203
204
205
206
207
                    jobs.append(
                        {
                            "stylecheck": {
                                "name": f"stylecheck_py{python_version}",
                                "python_version": python_version,
                                "cuda_version": "cpu",
                            }
208
                        }
209
                    )
moto's avatar
moto committed
210
    return indent(indentation, jobs)
moto's avatar
moto committed
211
212


213
214
215
216
217
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
218
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
219
220
    )

221
222
223
224
225
226
227
    with open(os.path.join(d, "config.yml"), "w") as f:
        f.write(
            env.get_template("config.yml.in").render(
                build_workflows=build_workflows,
                unittest_workflows=unittest_workflows,
            )
        )
moto's avatar
moto committed
228
        f.write("\n")