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

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
                "linux": ["cpu", "cu116", "cu117", "rocm5.1.1", "rocm5.2"],
Andrey Talman's avatar
Andrey Talman committed
36
                "win": ["cpu", "cu116", "cu117"],
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
58
59
                        if not fb and (
                            os_type == "linux" and cu_version == "cpu" and btype == "wheel" and python_version == "3.7"
                        ):
60
                            # the fields must match the build_docs "requires" dependency
61
                            fb = "/.*/"
62
63
64
65
66
67
68
69

                        # Disable all Linux Wheels Workflows from CircleCI
                        # since those will now be done through Nova. We'll keep
                        # around the py3.7 Linux Wheels build since the docs
                        # job depends on it.
                        if os_type == "linux" and btype == "wheel" and python_version != "3.7":
                            continue

70
                        w += workflow_pair(
71
72
                            btype, os_type, python_version, cu_version, unicode, prefix, upload, filter_branch=fb
                        )
73

74
75
    if not filter_branch:
        # Build on every pull request, but upload only on nightly and tags
76
77
        w += build_doc_job("/.*/")
        w += upload_doc_job("nightly")
78
79
80
    return indent(indentation, w)


81
def workflow_pair(btype, os_type, python_version, cu_version, unicode, prefix="", upload=False, *, filter_branch=None):
82
83
84
85
86

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

87
88
89
90
91
    w.append(
        generate_base_workflow(
            base_workflow_name, python_version, cu_version, unicode, os_type, btype, filter_branch=filter_branch
        )
    )
92

93
94
95
96
97
    # For the remaining py3.7 Linux Wheels job left around for the docs build,
    # we'll disable uploads.
    if os_type == "linux" and btype == "wheel":
        upload = False

98
    if upload:
99
        w.append(generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, filter_branch=filter_branch))
100
101
102
103
        # 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))
104
105
106
107

    return w


108
109
110
111
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
        "python_version": "3.7",
112
113
114
        "requires": [
            "binary_linux_wheel_py3.7_cpu",
        ],
115
116
117
    }

    if filter_branch:
118
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
119
120
121
122
123
124
125
126
    return [{"build_docs": job}]


def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
        "context": "org-member",
        "python_version": "3.7",
127
128
129
        "requires": [
            "build_docs",
        ],
130
131
132
    }

    if filter_branch:
133
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
134
135
136
    return [{"upload_docs": job}]


137
manylinux_images = {
138
    "cu116": "pytorch/manylinux-cuda116",
ptrblck's avatar
ptrblck committed
139
    "cu117": "pytorch/manylinux-cuda117",
140
141
142
143
}


def get_manylinux_image(cu_version):
Jeff Daily's avatar
Jeff Daily committed
144
    if cu_version == "cpu":
145
        return "pytorch/manylinux-cpu"
146
147
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
148
        return f"pytorch/manylinux-cuda{cu_suffix}"
149
150
    elif cu_version.startswith("rocm"):
        rocm_suffix = cu_version[len("rocm") :]
Jeff Daily's avatar
Jeff Daily committed
151
        return f"pytorch/manylinux-rocm:{rocm_suffix}"
152
153


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


162
163
164
def generate_base_workflow(
    base_workflow_name, python_version, cu_version, unicode, os_type, btype, *, filter_branch=None
):
165
166
167
168

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
169
        "cu_version": cu_version,
170
171
    }

172
    if os_type != "win" and unicode:
173
        d["unicode_abi"] = "1"
174

175
176
    if os_type != "win":
        d["wheel_docker_image"] = get_manylinux_image(cu_version)
Jeff Daily's avatar
Jeff Daily committed
177
178
179
        # ROCm conda packages not yet supported
        if "rocm" not in cu_version:
            d["conda_docker_image"] = get_conda_image(cu_version)
180

181
    if filter_branch is not None:
182
        d["filters"] = {
183
            "branches": {"only": filter_branch},
184
185
186
187
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
188
            },
189
        }
190

191
    w = f"binary_{os_type}_{btype}"
192
    return {w: d}
193
194


195
196
197
198
199
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
200
201


202
def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None):
203
204
205
206
207
208
    d = {
        "name": f"{base_workflow_name}_upload",
        "context": "org-member",
        "requires": [base_workflow_name],
    }

209
210
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
211

212
    if filter_branch is not None:
213
        d["filters"] = {
214
            "branches": {"only": filter_branch},
215
216
217
218
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
219
            },
220
        }
221

222
223
224
    return {f"binary_{btype}_upload": d}


guyang3532's avatar
guyang3532 committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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
240
    return {f"smoke_test_{os_type}_{pydistro}": d}
guyang3532's avatar
guyang3532 committed
241
242


243
def indent(indentation, data_list):
244
    return ("\n" + " " * indentation).join(yaml.dump(data_list, default_flow_style=False).splitlines())
245
246


247
248
def unittest_workflows(indentation=6):
    jobs = []
Francisco Massa's avatar
Francisco Massa committed
249
    for os_type in ["linux", "windows", "macos"]:
250
        for device_type in ["cpu", "gpu"]:
Francisco Massa's avatar
Francisco Massa committed
251
252
            if os_type == "macos" and device_type == "gpu":
                continue
253
254
            if os_type == "linux" and device_type == "cpu":
                continue
255
256
257
258
259
260
            for i, python_version in enumerate(PYTHON_VERSIONS):
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
                    "python_version": python_version,
                }

261
                if device_type == "gpu":
262
                    if python_version != "3.8":
263
                        job["filters"] = gen_filter_branch_tree("main", "nightly")
Andrey Talman's avatar
Andrey Talman committed
264
                    job["cu_version"] = "cu116"
265
                else:
266
                    job["cu_version"] = "cpu"
267
268
269
270
271
272

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

    return indent(indentation, jobs)


273
274
def cmake_workflows(indentation=6):
    jobs = []
275
276
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
277
        # Skip OSX CUDA
278
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
279
        for device in device_types:
280
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
281

Andrey Talman's avatar
Andrey Talman committed
282
            job["cu_version"] = "cu116" if device == "gpu" else "cpu"
283
            if device == "gpu" and os_type == "linux":
Andrey Talman's avatar
Andrey Talman committed
284
                job["wheel_docker_image"] = "pytorch/manylinux-cuda116"
285
            jobs.append({f"cmake_{os_type}_{device}": job})
286
287
288
    return indent(indentation, jobs)


289
290
291
292
293
def ios_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""
294
295
    for arch, platform in [("x86_64", "SIMULATOR"), ("arm64", "OS")]:
        name = f"{name_prefix}binary_libtorchvision_ops_ios_12.0.0_{arch}"
296
297
        build_job_names.append(name)
        build_job = {
298
299
300
301
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-{arch}",
            "ios_arch": arch,
            "ios_platform": platform,
            "name": name,
302
303
        }
        if nightly:
304
305
            build_job["filters"] = gen_filter_branch_tree("nightly")
        jobs.append({"binary_ios_build": build_job})
306
307
308

    if nightly:
        upload_job = {
309
310
311
312
            "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,
313
        }
314
        jobs.append({"binary_ios_upload": upload_job})
315
316
317
    return indent(indentation, jobs)


318
319
320
321
322
323
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

324
    name = f"{name_prefix}binary_libtorchvision_ops_android"
325
326
    build_job_names.append(name)
    build_job = {
327
328
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
329
330
331
332
    }

    if nightly:
        upload_job = {
333
334
335
336
            "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",
337
        }
338
        jobs.append({"binary_android_upload": upload_job})
339
    else:
340
        jobs.append({"binary_android_build": build_job})
341
342
343
    return indent(indentation, jobs)


344
345
346
347
348
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
349
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
350
        keep_trailing_newline=True,
351
352
    )

353
354
355
356
357
358
359
360
361
362
    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,
            )
        )