regenerate.py 13 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

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 = {
35
36
                "linux": ["cpu", "cu117", "cu118", "rocm5.2", "rocm5.3"],
                "win": ["cpu", "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
80
81
82
83
84
85
                        # Not supporting Python 3.11 conda packages at the
                        # moment since the necessary dependencies are not
                        # available. Windows 3.11 Wheels will be built from
                        # CircleCI here, however.
                        if python_version == "3.11" and btype == "conda":
                            continue

86
                        w += workflow_pair(
87
88
                            btype, os_type, python_version, cu_version, unicode, prefix, upload, filter_branch=fb
                        )
89

90
91
    if not filter_branch:
        # Build on every pull request, but upload only on nightly and tags
92
93
        w += build_doc_job("/.*/")
        w += upload_doc_job("nightly")
94
95
96
    return indent(indentation, w)


97
def workflow_pair(btype, os_type, python_version, cu_version, unicode, prefix="", upload=False, *, filter_branch=None):
98
99
100
101
102

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

103
104
105
106
107
    w.append(
        generate_base_workflow(
            base_workflow_name, python_version, cu_version, unicode, os_type, btype, filter_branch=filter_branch
        )
    )
108

109
    # For the remaining py3.8 Linux Wheels job left around for the docs build,
110
111
112
113
    # we'll disable uploads.
    if os_type == "linux" and btype == "wheel":
        upload = False

114
    if upload:
115
        w.append(generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, filter_branch=filter_branch))
116
117
118
119
        # 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))
120
121
122
123

    return w


124
125
126
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
127
        "python_version": "3.8",
128
        "requires": [
129
            "binary_linux_wheel_py3.8_cpu",
130
        ],
131
132
133
    }

    if filter_branch:
134
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
135
136
137
138
139
140
141
    return [{"build_docs": job}]


def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
        "context": "org-member",
142
        "python_version": "3.8",
143
144
145
        "requires": [
            "build_docs",
        ],
146
147
148
    }

    if filter_branch:
149
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
150
151
152
    return [{"upload_docs": job}]


153
manylinux_images = {
ptrblck's avatar
ptrblck committed
154
    "cu117": "pytorch/manylinux-cuda117",
Andrey Talman's avatar
Andrey Talman committed
155
    "cu118": "pytorch/manylinux-cuda118",
156
157
158
159
}


def get_manylinux_image(cu_version):
Jeff Daily's avatar
Jeff Daily committed
160
    if cu_version == "cpu":
161
        return "pytorch/manylinux-cpu"
162
163
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
164
        return f"pytorch/manylinux-cuda{cu_suffix}"
165
166
    elif cu_version.startswith("rocm"):
        rocm_suffix = cu_version[len("rocm") :]
Jeff Daily's avatar
Jeff Daily committed
167
        return f"pytorch/manylinux-rocm:{rocm_suffix}"
168
169


170
171
172
def get_conda_image(cu_version):
    if cu_version == "cpu":
        return "pytorch/conda-builder:cpu"
173
174
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
175
        return f"pytorch/conda-builder:cuda{cu_suffix}"
176
177


178
179
180
def generate_base_workflow(
    base_workflow_name, python_version, cu_version, unicode, os_type, btype, *, filter_branch=None
):
181
182
183
184

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
185
        "cu_version": cu_version,
186
187
    }

188
    if os_type != "win" and unicode:
189
        d["unicode_abi"] = "1"
190

191
192
    if os_type != "win":
        d["wheel_docker_image"] = get_manylinux_image(cu_version)
Jeff Daily's avatar
Jeff Daily committed
193
194
195
        # ROCm conda packages not yet supported
        if "rocm" not in cu_version:
            d["conda_docker_image"] = get_conda_image(cu_version)
196

197
    if filter_branch is not None:
198
        d["filters"] = {
199
            "branches": {"only": filter_branch},
200
201
202
203
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
204
            },
205
        }
206

207
    w = f"binary_{os_type}_{btype}"
208
    return {w: d}
209
210


211
212
213
214
215
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
216
217


218
def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None):
219
220
221
222
223
224
    d = {
        "name": f"{base_workflow_name}_upload",
        "context": "org-member",
        "requires": [base_workflow_name],
    }

225
226
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
227

228
    if filter_branch is not None:
229
        d["filters"] = {
230
            "branches": {"only": filter_branch},
231
232
233
234
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
235
            },
236
        }
237

238
239
240
    return {f"binary_{btype}_upload": d}


guyang3532's avatar
guyang3532 committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
256
    return {f"smoke_test_{os_type}_{pydistro}": d}
guyang3532's avatar
guyang3532 committed
257
258


259
def indent(indentation, data_list):
260
    return ("\n" + " " * indentation).join(yaml.dump(data_list, default_flow_style=False).splitlines())
261
262


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

277
                if device_type == "gpu":
278
                    if python_version != "3.8":
279
                        job["filters"] = gen_filter_branch_tree("main", "nightly")
280
                    job["cu_version"] = "cu117"
281
                else:
282
                    job["cu_version"] = "cpu"
283
284
285
286
287
288

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

    return indent(indentation, jobs)


289
290
def cmake_workflows(indentation=6):
    jobs = []
291
292
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
293
        # Skip OSX CUDA
294
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
295
        for device in device_types:
296
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
297

298
            job["cu_version"] = "cu117" if device == "gpu" else "cpu"
299
            if device == "gpu" and os_type == "linux":
Andrey Talman's avatar
Andrey Talman committed
300
                job["wheel_docker_image"] = "pytorch/manylinux-cuda116"
301
            jobs.append({f"cmake_{os_type}_{device}": job})
302
303
304
    return indent(indentation, jobs)


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

    if nightly:
        upload_job = {
325
326
327
328
            "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,
329
        }
330
        jobs.append({"binary_ios_upload": upload_job})
331
332
333
    return indent(indentation, jobs)


334
335
336
337
338
339
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

340
    name = f"{name_prefix}binary_libtorchvision_ops_android"
341
342
    build_job_names.append(name)
    build_job = {
343
344
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
345
346
347
348
    }

    if nightly:
        upload_job = {
349
350
351
352
            "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",
353
        }
354
        jobs.append({"binary_android_upload": upload_job})
355
    else:
356
        jobs.append({"binary_android_build": build_job})
357
358
359
    return indent(indentation, jobs)


360
361
362
363
364
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
365
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
366
        keep_trailing_newline=True,
367
368
    )

369
370
371
372
373
374
375
376
377
378
    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,
            )
        )