regenerate.py 11.8 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 = {
35
                "linux": ["cpu", "cu102", "cu111", "cu113", "cu115", "rocm4.3.1", "rocm4.5.2"],
36
                "win": ["cpu", "cu113", "cu115"],
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
                        w += workflow_pair(
63
64
                            btype, os_type, python_version, cu_version, unicode, prefix, upload, filter_branch=fb
                        )
65

66
67
    if not filter_branch:
        # Build on every pull request, but upload only on nightly and tags
68
69
        w += build_doc_job("/.*/")
        w += upload_doc_job("nightly")
70
71
72
    return indent(indentation, w)


73
def workflow_pair(btype, os_type, python_version, cu_version, unicode, prefix="", upload=False, *, filter_branch=None):
74
75
76
77
78

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

79
80
81
82
83
    w.append(
        generate_base_workflow(
            base_workflow_name, python_version, cu_version, unicode, os_type, btype, filter_branch=filter_branch
        )
    )
84
85

    if upload:
86
        w.append(generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, filter_branch=filter_branch))
87
88
        if filter_branch == "nightly" and os_type in ["linux", "win"]:
            pydistro = "pip" if btype == "wheel" else "conda"
guyang3532's avatar
guyang3532 committed
89
            w.append(generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, os_type))
90
91
92
93

    return w


94
95
96
97
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
        "python_version": "3.7",
98
99
100
        "requires": [
            "binary_linux_wheel_py3.7_cpu",
        ],
101
102
103
    }

    if filter_branch:
104
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
105
106
107
108
109
110
111
112
    return [{"build_docs": job}]


def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
        "context": "org-member",
        "python_version": "3.7",
113
114
115
        "requires": [
            "build_docs",
        ],
116
117
118
    }

    if filter_branch:
119
        job["filters"] = gen_filter_branch_tree(filter_branch, tags_list=RC_PATTERN)
120
121
122
    return [{"upload_docs": job}]


123
124
125
126
manylinux_images = {
    "cu92": "pytorch/manylinux-cuda92",
    "cu101": "pytorch/manylinux-cuda101",
    "cu102": "pytorch/manylinux-cuda102",
peterjc123's avatar
peterjc123 committed
127
    "cu110": "pytorch/manylinux-cuda110",
128
    "cu111": "pytorch/manylinux-cuda111",
129
    "cu112": "pytorch/manylinux-cuda112",
130
    "cu113": "pytorch/manylinux-cuda113",
131
    "cu115": "pytorch/manylinux-cuda115",
132
133
134
135
}


def get_manylinux_image(cu_version):
Jeff Daily's avatar
Jeff Daily committed
136
137
    if cu_version == "cpu":
        return "pytorch/manylinux-cuda102"
138
139
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
140
        return f"pytorch/manylinux-cuda{cu_suffix}"
141
142
    elif cu_version.startswith("rocm"):
        rocm_suffix = cu_version[len("rocm") :]
Jeff Daily's avatar
Jeff Daily committed
143
        return f"pytorch/manylinux-rocm:{rocm_suffix}"
144
145


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


154
155
156
def generate_base_workflow(
    base_workflow_name, python_version, cu_version, unicode, os_type, btype, *, filter_branch=None
):
157
158
159
160

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
161
        "cu_version": cu_version,
162
163
    }

164
    if os_type != "win" and unicode:
165
        d["unicode_abi"] = "1"
166

167
168
    if os_type != "win":
        d["wheel_docker_image"] = get_manylinux_image(cu_version)
Jeff Daily's avatar
Jeff Daily committed
169
170
171
        # ROCm conda packages not yet supported
        if "rocm" not in cu_version:
            d["conda_docker_image"] = get_conda_image(cu_version)
172

173
    if filter_branch is not None:
174
        d["filters"] = {
175
            "branches": {"only": filter_branch},
176
177
178
179
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
180
            },
181
        }
182

183
    w = f"binary_{os_type}_{btype}"
184
    return {w: d}
185
186


187
188
189
190
191
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
192
193


194
def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None):
195
196
197
198
199
200
    d = {
        "name": f"{base_workflow_name}_upload",
        "context": "org-member",
        "requires": [base_workflow_name],
    }

201
202
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
203

204
    if filter_branch is not None:
205
        d["filters"] = {
206
            "branches": {"only": filter_branch},
207
208
209
210
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
211
            },
212
        }
213

214
215
216
    return {f"binary_{btype}_upload": d}


guyang3532's avatar
guyang3532 committed
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
232
    return {f"smoke_test_{os_type}_{pydistro}": d}
guyang3532's avatar
guyang3532 committed
233
234


235
def indent(indentation, data_list):
236
    return ("\n" + " " * indentation).join(yaml.dump(data_list, default_flow_style=False).splitlines())
237
238


239
240
def unittest_workflows(indentation=6):
    jobs = []
Francisco Massa's avatar
Francisco Massa committed
241
    for os_type in ["linux", "windows", "macos"]:
242
        for device_type in ["cpu", "gpu"]:
Francisco Massa's avatar
Francisco Massa committed
243
244
            if os_type == "macos" and device_type == "gpu":
                continue
245
246
247
248
249
250
            for i, python_version in enumerate(PYTHON_VERSIONS):
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
                    "python_version": python_version,
                }

251
                if device_type == "gpu":
252
                    if python_version != "3.8":
253
254
                        job["filters"] = gen_filter_branch_tree("main", "nightly")
                    job["cu_version"] = "cu102"
255
                else:
256
                    job["cu_version"] = "cpu"
257
258
259
260
261
262

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

    return indent(indentation, jobs)


263
264
def cmake_workflows(indentation=6):
    jobs = []
265
266
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
267
        # Skip OSX CUDA
268
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
269
        for device in device_types:
270
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
271

272
            job["cu_version"] = "cu113" if device == "gpu" else "cpu"
273
            if device == "gpu" and os_type == "linux":
274
                job["wheel_docker_image"] = "pytorch/manylinux-cuda113"
275
            jobs.append({f"cmake_{os_type}_{device}": job})
276
277
278
    return indent(indentation, jobs)


279
280
281
282
283
def ios_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""
284
285
    for arch, platform in [("x86_64", "SIMULATOR"), ("arm64", "OS")]:
        name = f"{name_prefix}binary_libtorchvision_ops_ios_12.0.0_{arch}"
286
287
        build_job_names.append(name)
        build_job = {
288
289
290
291
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-{arch}",
            "ios_arch": arch,
            "ios_platform": platform,
            "name": name,
292
293
        }
        if nightly:
294
295
            build_job["filters"] = gen_filter_branch_tree("nightly")
        jobs.append({"binary_ios_build": build_job})
296
297
298

    if nightly:
        upload_job = {
299
300
301
302
            "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,
303
        }
304
        jobs.append({"binary_ios_upload": upload_job})
305
306
307
    return indent(indentation, jobs)


308
309
310
311
312
313
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

314
    name = f"{name_prefix}binary_libtorchvision_ops_android"
315
316
    build_job_names.append(name)
    build_job = {
317
318
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
319
320
321
322
    }

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


334
335
336
337
338
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
339
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
340
        keep_trailing_newline=True,
341
342
    )

343
344
345
346
347
348
349
350
351
352
    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,
            )
        )