regenerate.py 8.72 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.7", "3.8", "3.9", "3.10"]
25
CU_VERSIONS_DICT = {
pbialecki's avatar
pbialecki committed
26
27
    "linux": ["cpu", "cu116", "cu117", "cu118", "rocm5.2", "rocm5.3"],
    "windows": ["cpu", "cu116", "cu117", "cu118"],
28
29
    "macos": ["cpu"],
}
Nikita Shulga's avatar
Nikita Shulga committed
30

moto's avatar
moto committed
31

32
DOC_VERSION = ("linux", "3.8")
33

moto's avatar
moto committed
34

35
def build_workflows(prefix="", upload=False, filter_branch=None, indentation=6):
36
    w = []
37
    w += build_download_job(filter_branch)
38
39
    for os_type in ["linux", "macos", "windows"]:
        w += build_ffmpeg_job(os_type, filter_branch)
40
    for btype in ["wheel", "conda"]:
peterjc123's avatar
peterjc123 committed
41
        for os_type in ["linux", "macos", "windows"]:
moto's avatar
moto committed
42
            for python_version in PYTHON_VERSIONS:
Nikita Shulga's avatar
Nikita Shulga committed
43
                for cu_version in CU_VERSIONS_DICT[os_type]:
Matti Picus's avatar
Matti Picus committed
44
                    fb = filter_branch
45
46
47
48
                    if (
                        (cu_version.startswith("rocm") and btype == "conda")
                        or (os_type == "linux" and btype == "wheel")
                        or (os_type == "macos" and btype == "wheel")
49
                    ):
Matti Picus's avatar
Matti Picus committed
50
                        continue
51

52
53
54
                    if not fb and (
                        os_type == "linux" and btype == "wheel" and python_version == "3.8" and cu_version == "cpu"
                    ):
Matti Picus's avatar
Matti Picus committed
55
                        # the fields must match the build_docs "requires" dependency
56
                        fb = "/.*/"
57

Matti Picus's avatar
Matti Picus committed
58
                    w += build_workflow_pair(btype, os_type, python_version, cu_version, fb, prefix, upload)
59

60
61
    if not filter_branch:
        # Build on every pull request, but upload only on nightly and tags
62
63
        w += build_doc_job("/.*/")
        w += upload_doc_job("nightly")
64

65
66
67
    return indent(indentation, w)


68
69
def build_download_job(filter_branch):
    job = {
70
        "name": "download_third_parties",
71
72
73
74
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
75
76
77
78
79
80
81
82
83
84
85
86
87
    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}]
88
89


90
def build_workflow_pair(btype, os_type, python_version, cu_version, filter_branch, prefix="", upload=False):
91
92

    w = []
Nikita Shulga's avatar
Nikita Shulga committed
93
94
    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))
95
96

    if upload:
97
        w.append(generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version))
98

moto's avatar
moto committed
99
100
101
102
103
    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
104
            )
moto's avatar
moto committed
105
        )
106
107
108

    return w

moto's avatar
moto committed
109

110
111
112
113
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
        "python_version": "3.8",
114
        "cuda_version": "cu116",
115
        "requires": [
116
            "binary_linux_conda_py3.8_cu116",
117
        ],
118
119
120
121
122
123
    }

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

moto's avatar
moto committed
124

125
126
127
def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
128
        "context": "org-member",
129
        "python_version": "3.8",
130
131
132
        "requires": [
            "build_docs",
        ],
133
134
135
136
137
138
    }

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

139

140
141
142
143
def docstring_parameters_sync_job(filter_branch):
    job = {
        "name": "docstring_parameters_sync",
        "python_version": "3.8",
144
145
146
        "requires": [
            "binary_linux_wheel_py3.8_cpu",
        ],
147
148
149
150
151
152
153
    }

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


Nikita Shulga's avatar
Nikita Shulga committed
154
def generate_base_workflow(base_workflow_name, python_version, cu_version, filter_branch, os_type, btype):
155
156
157
158

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
159
        "cuda_version": cu_version,
160
        "requires": [f"build_ffmpeg_{os_type}"],
161
162
    }

163
164
165
166
167
    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"):
168
        d["wheel_docker_image"] = f"pytorch/manylinux-rocm:{cu_version[len('rocm'):]}"
169

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

173
    return {f"binary_{os_type}_{btype}": d}
174
175


moto's avatar
moto committed
176
def gen_filter_branch_tree(*branches):
177
178
179
180
181
182
183
184
    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]+/"
185
        },
186
    }
187
188


189
def generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version):
190
191
192
193
194
195
    d = {
        "name": "{base_workflow_name}_upload".format(base_workflow_name=base_workflow_name),
        "context": "org-member",
        "requires": [base_workflow_name],
    }

196
197
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
198

199
200
201
202
203
204
    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
205
def generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, cu_version, os_type):
206

Nikita Shulga's avatar
Nikita Shulga committed
207
    smoke_suffix = f"smoke_test_{pydistro}".format(pydistro=pydistro)
208
    d = {
Nikita Shulga's avatar
Nikita Shulga committed
209
        "name": f"{base_workflow_name}_{smoke_suffix}",
moto's avatar
moto committed
210
        "requires": [base_workflow_name],
211
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
212
        "cuda_version": cu_version,
213
214
215
216
217
    }

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

Caroline Chen's avatar
Caroline Chen committed
218
    smoke_name = f"smoke_test_{os_type}_{pydistro}"
219
    if pydistro == "conda" and (os_type == "linux" or os_type == "windows") and cu_version != "cpu":
Caroline Chen's avatar
Caroline Chen committed
220
221
        smoke_name += "_gpu"
    return {smoke_name: d}
222
223
224
225
226
227


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


228
229
230
231
232
233
234
235
def unittest_python_versions(os):
    return {
        "windows": PYTHON_VERSIONS[:1],
        "macos": PYTHON_VERSIONS[:1],
        "linux": PYTHON_VERSIONS,
    }.get(os)


moto's avatar
moto committed
236
def unittest_workflows(indentation=6):
moto's avatar
moto committed
237
    jobs = []
238
    jobs += build_download_job(None)
moto's avatar
moto committed
239
    for os_type in ["linux", "windows", "macos"]:
moto's avatar
moto committed
240
        for device_type in ["cpu", "gpu"]:
moto's avatar
moto committed
241
242
243
            if os_type == "macos" and device_type == "gpu":
                continue

244
            for i, python_version in enumerate(unittest_python_versions(os_type)):
moto's avatar
moto committed
245
246
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
moto's avatar
moto committed
247
                    "python_version": python_version,
248
                    "cuda_version": "cpu" if device_type == "cpu" else "cu116",
249
                    "requires": ["download_third_parties"],
moto's avatar
moto committed
250
                }
moto's avatar
moto committed
251
252

                jobs.append({f"unittest_{os_type}_{device_type}": job})
253
254

                if i == 0 and os_type == "linux" and device_type == "cpu":
255
256
257
258
259
260
261
                    jobs.append(
                        {
                            "stylecheck": {
                                "name": f"stylecheck_py{python_version}",
                                "python_version": python_version,
                                "cuda_version": "cpu",
                            }
262
                        }
263
                    )
moto's avatar
moto committed
264
    return indent(indentation, jobs)
moto's avatar
moto committed
265
266


267
268
269
270
271
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
272
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
273
274
    )

275
276
277
278
279
280
281
    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
282
        f.write("\n")