regenerate.py 12.7 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"]
25

26
27
RC_PATTERN = r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"

28

29
def build_workflows(prefix="", filter_branch=None, upload=False, indentation=6, windows_latest_only=False):
30
31
    w = []
    for btype in ["wheel", "conda"]:
32
        for os_type in ["linux", "macos", "win"]:
33
            python_versions = PYTHON_VERSIONS
34
            cu_versions_dict = {
Andrey Talman's avatar
Andrey Talman committed
35
36
                "linux": ["cpu", "cu116", "cu117", "cu118", "rocm5.2", "rocm5.3"],
                "win": ["cpu", "cu116", "cu117", "cu118"],
37
38
                "macos": ["cpu"],
            }
39
            cu_versions = cu_versions_dict[os_type]
40
41
            for python_version in python_versions:
                for cu_version in cu_versions:
Jeff Daily's avatar
Jeff Daily committed
42
                    # ROCm conda packages not yet supported
43
                    if cu_version.startswith("rocm") and btype == "conda":
Jeff Daily's avatar
Jeff Daily committed
44
                        continue
Nikita Shulga's avatar
Nikita Shulga committed
45
                    for unicode in [False]:
46
                        fb = filter_branch
47
48
49
50
51
52
53
54
55
                        if (
                            windows_latest_only
                            and os_type == "win"
                            and filter_branch is None
                            and (
                                python_version != python_versions[-1]
                                or (cu_version not in [cu_versions[0], cu_versions[-1]])
                            )
                        ):
56
                            fb = "main"
57
                        if not fb and (
58
                            os_type == "linux" and cu_version == "cpu" and btype == "wheel" and python_version == "3.8"
59
                        ):
60
                            # the fields must match the build_docs "requires" dependency
61
                            fb = "/.*/"
62
63
64

                        # Disable all Linux Wheels Workflows from CircleCI
                        # since those will now be done through Nova. We'll keep
65
                        # around the py3.8 cpu Linux Wheels build since the docs
66
                        # job depends on it.
67
68
69
                        if os_type == "linux" and btype == "wheel":
                            if not (python_version == "3.8" and cu_version == "cpu"):
                                continue
70

71
72
73
74
                        # Disable all Macos Wheels Workflows from CircleCI.
                        if os_type == "macos" and btype == "wheel":
                            continue

75
76
77
78
                        # Disable all non-Windows Conda workflows
                        if os_type != "win" and btype == "conda":
                            continue

79
                        w += workflow_pair(
80
81
                            btype, os_type, python_version, cu_version, unicode, prefix, upload, filter_branch=fb
                        )
82

83
84
    if not filter_branch:
        # Build on every pull request, but upload only on nightly and tags
85
86
        w += build_doc_job("/.*/")
        w += upload_doc_job("nightly")
87
88
89
    return indent(indentation, w)


90
def workflow_pair(btype, os_type, python_version, cu_version, unicode, prefix="", upload=False, *, filter_branch=None):
91
92
93
94
95

    w = []
    unicode_suffix = "u" if unicode else ""
    base_workflow_name = f"{prefix}binary_{os_type}_{btype}_py{python_version}{unicode_suffix}_{cu_version}"

96
97
98
99
100
    w.append(
        generate_base_workflow(
            base_workflow_name, python_version, cu_version, unicode, os_type, btype, filter_branch=filter_branch
        )
    )
101

102
    # For the remaining py3.8 Linux Wheels job left around for the docs build,
103
104
105
106
    # we'll disable uploads.
    if os_type == "linux" and btype == "wheel":
        upload = False

107
    if upload:
108
        w.append(generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, filter_branch=filter_branch))
109
110
111
112
        # disable smoke tests, they are broken and needs to be fixed
        # if filter_branch == "nightly" and os_type in ["linux", "win"]:
        #     pydistro = "pip" if btype == "wheel" else "conda"
        #     w.append(generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, os_type))
113
114
115
116

    return w


117
118
119
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
120
        "python_version": "3.8",
121
        "requires": [
122
            "binary_linux_wheel_py3.8_cpu",
123
        ],
124
125
126
    }

    if filter_branch:
127
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
128
129
130
131
132
133
134
    return [{"build_docs": job}]


def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
        "context": "org-member",
135
        "python_version": "3.8",
136
137
138
        "requires": [
            "build_docs",
        ],
139
140
141
    }

    if filter_branch:
142
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
143
144
145
    return [{"upload_docs": job}]


146
manylinux_images = {
147
    "cu116": "pytorch/manylinux-cuda116",
ptrblck's avatar
ptrblck committed
148
    "cu117": "pytorch/manylinux-cuda117",
Andrey Talman's avatar
Andrey Talman committed
149
    "cu118": "pytorch/manylinux-cuda118",
150
151
152
153
}


def get_manylinux_image(cu_version):
Jeff Daily's avatar
Jeff Daily committed
154
    if cu_version == "cpu":
155
        return "pytorch/manylinux-cpu"
156
157
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
158
        return f"pytorch/manylinux-cuda{cu_suffix}"
159
160
    elif cu_version.startswith("rocm"):
        rocm_suffix = cu_version[len("rocm") :]
Jeff Daily's avatar
Jeff Daily committed
161
        return f"pytorch/manylinux-rocm:{rocm_suffix}"
162
163


164
165
166
def get_conda_image(cu_version):
    if cu_version == "cpu":
        return "pytorch/conda-builder:cpu"
167
168
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
169
        return f"pytorch/conda-builder:cuda{cu_suffix}"
170
171


172
173
174
def generate_base_workflow(
    base_workflow_name, python_version, cu_version, unicode, os_type, btype, *, filter_branch=None
):
175
176
177
178

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
179
        "cu_version": cu_version,
180
181
    }

182
    if os_type != "win" and unicode:
183
        d["unicode_abi"] = "1"
184

185
186
    if os_type != "win":
        d["wheel_docker_image"] = get_manylinux_image(cu_version)
Jeff Daily's avatar
Jeff Daily committed
187
188
189
        # ROCm conda packages not yet supported
        if "rocm" not in cu_version:
            d["conda_docker_image"] = get_conda_image(cu_version)
190

191
    if filter_branch is not None:
192
        d["filters"] = {
193
            "branches": {"only": filter_branch},
194
195
196
197
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
198
            },
199
        }
200

201
    w = f"binary_{os_type}_{btype}"
202
    return {w: d}
203
204


205
206
207
208
209
def gen_filter_branch_tree(*branches, tags_list=None):
    filter_dict = {"branches": {"only": [b for b in branches]}}
    if tags_list is not None:
        filter_dict["tags"] = {"only": tags_list}
    return filter_dict
210
211


212
def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None):
213
214
215
216
217
218
    d = {
        "name": f"{base_workflow_name}_upload",
        "context": "org-member",
        "requires": [base_workflow_name],
    }

219
220
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
221

222
    if filter_branch is not None:
223
        d["filters"] = {
224
            "branches": {"only": filter_branch},
225
226
227
228
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
229
            },
230
        }
231

232
233
234
    return {f"binary_{btype}_upload": d}


guyang3532's avatar
guyang3532 committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, os_type):

    required_build_suffix = "_upload"
    required_build_name = base_workflow_name + required_build_suffix

    smoke_suffix = f"smoke_test_{pydistro}"
    d = {
        "name": f"{base_workflow_name}_{smoke_suffix}",
        "requires": [required_build_name],
        "python_version": python_version,
    }

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

Nikita Shulga's avatar
Nikita Shulga committed
250
    return {f"smoke_test_{os_type}_{pydistro}": d}
guyang3532's avatar
guyang3532 committed
251
252


253
def indent(indentation, data_list):
254
    return ("\n" + " " * indentation).join(yaml.dump(data_list, default_flow_style=False).splitlines())
255
256


257
258
def unittest_workflows(indentation=6):
    jobs = []
Francisco Massa's avatar
Francisco Massa committed
259
    for os_type in ["linux", "windows", "macos"]:
260
        for device_type in ["cpu", "gpu"]:
Francisco Massa's avatar
Francisco Massa committed
261
262
            if os_type == "macos" and device_type == "gpu":
                continue
263
264
            if os_type == "linux" and device_type == "cpu":
                continue
265
266
267
268
269
270
            for i, python_version in enumerate(PYTHON_VERSIONS):
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
                    "python_version": python_version,
                }

271
                if device_type == "gpu":
272
                    if python_version != "3.8":
273
                        job["filters"] = gen_filter_branch_tree("main", "nightly")
Andrey Talman's avatar
Andrey Talman committed
274
                    job["cu_version"] = "cu116"
275
                else:
276
                    job["cu_version"] = "cpu"
277
278
279
280
281
282

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

    return indent(indentation, jobs)


283
284
def cmake_workflows(indentation=6):
    jobs = []
285
286
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
287
        # Skip OSX CUDA
288
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
289
        for device in device_types:
290
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
291

Andrey Talman's avatar
Andrey Talman committed
292
            job["cu_version"] = "cu116" if device == "gpu" else "cpu"
293
            if device == "gpu" and os_type == "linux":
Andrey Talman's avatar
Andrey Talman committed
294
                job["wheel_docker_image"] = "pytorch/manylinux-cuda116"
295
            jobs.append({f"cmake_{os_type}_{device}": job})
296
297
298
    return indent(indentation, jobs)


299
300
301
302
303
def ios_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""
304
305
    for arch, platform in [("x86_64", "SIMULATOR"), ("arm64", "OS")]:
        name = f"{name_prefix}binary_libtorchvision_ops_ios_12.0.0_{arch}"
306
307
        build_job_names.append(name)
        build_job = {
308
309
310
311
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-{arch}",
            "ios_arch": arch,
            "ios_platform": platform,
            "name": name,
312
313
        }
        if nightly:
314
315
            build_job["filters"] = gen_filter_branch_tree("nightly")
        jobs.append({"binary_ios_build": build_job})
316
317
318

    if nightly:
        upload_job = {
319
320
321
322
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-upload",
            "context": "org-member",
            "filters": gen_filter_branch_tree("nightly"),
            "requires": build_job_names,
323
        }
324
        jobs.append({"binary_ios_upload": upload_job})
325
326
327
    return indent(indentation, jobs)


328
329
330
331
332
333
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

334
    name = f"{name_prefix}binary_libtorchvision_ops_android"
335
336
    build_job_names.append(name)
    build_job = {
337
338
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
339
340
341
342
    }

    if nightly:
        upload_job = {
343
344
345
346
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-android-upload",
            "context": "org-member",
            "filters": gen_filter_branch_tree("nightly"),
            "name": f"{name_prefix}binary_libtorchvision_ops_android_upload",
347
        }
348
        jobs.append({"binary_android_upload": upload_job})
349
    else:
350
        jobs.append({"binary_android_build": build_job})
351
352
353
    return indent(indentation, jobs)


354
355
356
357
358
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
359
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
360
        keep_trailing_newline=True,
361
362
    )

363
364
365
366
367
368
369
370
371
372
    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,
                cmake_workflows=cmake_workflows,
                ios_workflows=ios_workflows,
                android_workflows=android_workflows,
            )
        )