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

                        # Disable all Linux Wheels Workflows from CircleCI
64
                        if os_type == "linux" and btype == "wheel":
65
                            continue
66

67
68
69
70
                        # Disable all Macos Wheels Workflows from CircleCI.
                        if os_type == "macos" and btype == "wheel":
                            continue

71
72
73
74
                        # Disable all non-Windows Conda workflows
                        if os_type != "win" and btype == "conda":
                            continue

75
                        w += workflow_pair(
76
77
                            btype, os_type, python_version, cu_version, unicode, prefix, upload, filter_branch=fb
                        )
78
79
80
81

    return indent(indentation, w)


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

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

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

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

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

    return w


109
manylinux_images = {
ptrblck's avatar
ptrblck committed
110
    "cu117": "pytorch/manylinux-cuda117",
Andrey Talman's avatar
Andrey Talman committed
111
    "cu118": "pytorch/manylinux-cuda118",
112
113
114
115
}


def get_manylinux_image(cu_version):
Jeff Daily's avatar
Jeff Daily committed
116
    if cu_version == "cpu":
117
        return "pytorch/manylinux-cpu"
118
119
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
120
        return f"pytorch/manylinux-cuda{cu_suffix}"
121
122
    elif cu_version.startswith("rocm"):
        rocm_suffix = cu_version[len("rocm") :]
Jeff Daily's avatar
Jeff Daily committed
123
        return f"pytorch/manylinux-rocm:{rocm_suffix}"
124
125


126
127
128
def get_conda_image(cu_version):
    if cu_version == "cpu":
        return "pytorch/conda-builder:cpu"
129
130
    elif cu_version.startswith("cu"):
        cu_suffix = cu_version[len("cu") :]
Jeff Daily's avatar
Jeff Daily committed
131
        return f"pytorch/conda-builder:cuda{cu_suffix}"
132
133


134
135
136
def generate_base_workflow(
    base_workflow_name, python_version, cu_version, unicode, os_type, btype, *, filter_branch=None
):
137
138
139
140

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
141
        "cu_version": cu_version,
142
143
    }

144
    if os_type != "win" and unicode:
145
        d["unicode_abi"] = "1"
146

147
148
    if os_type != "win":
        d["wheel_docker_image"] = get_manylinux_image(cu_version)
Jeff Daily's avatar
Jeff Daily committed
149
150
151
        # ROCm conda packages not yet supported
        if "rocm" not in cu_version:
            d["conda_docker_image"] = get_conda_image(cu_version)
152

153
    if filter_branch is not None:
154
        d["filters"] = {
155
            "branches": {"only": filter_branch},
156
157
158
159
            "tags": {
                # Using a raw string here to avoid having to escape
                # anything
                "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
160
            },
161
        }
162

163
    w = f"binary_{os_type}_{btype}"
164
    return {w: d}
165
166


167
168
169
170
171
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
172
173


174
def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None):
175
176
177
178
179
180
    d = {
        "name": f"{base_workflow_name}_upload",
        "context": "org-member",
        "requires": [base_workflow_name],
    }

181
182
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
183

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

194
195
196
    return {f"binary_{btype}_upload": d}


guyang3532's avatar
guyang3532 committed
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
212
    return {f"smoke_test_{os_type}_{pydistro}": d}
guyang3532's avatar
guyang3532 committed
213
214


215
def indent(indentation, data_list):
216
    return ("\n" + " " * indentation).join(yaml.dump(data_list, default_flow_style=False).splitlines())
217
218


219
220
def unittest_workflows(indentation=6):
    jobs = []
221
    for os_type in ["windows"]:
222
        for device_type in ["gpu"]:
Francisco Massa's avatar
Francisco Massa committed
223
224
            if os_type == "macos" and device_type == "gpu":
                continue
225

226
            for i, python_version in enumerate(PYTHON_VERSIONS):
227
228
229
230
231

                # Turn off unit tests for 3.11, unit test are not setup properly
                if python_version == "3.11":
                    continue

232
233
234
235
236
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
                    "python_version": python_version,
                }

237
                if device_type == "gpu":
238
                    if python_version != "3.8":
239
                        job["filters"] = gen_filter_branch_tree("main", "nightly")
240
                    job["cu_version"] = "cu117"
241
                else:
242
                    job["cu_version"] = "cpu"
243
244
245
246
247
248

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

    return indent(indentation, jobs)


249
250
def cmake_workflows(indentation=6):
    jobs = []
251
252
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
253
        # Skip OSX CUDA
254
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
255
        for device in device_types:
256
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
257

258
            job["cu_version"] = "cu117" if device == "gpu" else "cpu"
259
            if device == "gpu" and os_type == "linux":
260
                job["wheel_docker_image"] = "pytorch/manylinux-cuda117"
261
            jobs.append({f"cmake_{os_type}_{device}": job})
262
263
264
    return indent(indentation, jobs)


265
266
267
268
269
def ios_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""
270
271
    for arch, platform in [("x86_64", "SIMULATOR"), ("arm64", "OS")]:
        name = f"{name_prefix}binary_libtorchvision_ops_ios_12.0.0_{arch}"
272
273
        build_job_names.append(name)
        build_job = {
274
275
276
277
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-{arch}",
            "ios_arch": arch,
            "ios_platform": platform,
            "name": name,
278
279
        }
        if nightly:
280
281
            build_job["filters"] = gen_filter_branch_tree("nightly")
        jobs.append({"binary_ios_build": build_job})
282
283
284

    if nightly:
        upload_job = {
285
286
287
288
            "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,
289
        }
290
        jobs.append({"binary_ios_upload": upload_job})
291
292
293
    return indent(indentation, jobs)


294
295
296
297
298
299
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

300
    name = f"{name_prefix}binary_libtorchvision_ops_android"
301
302
    build_job_names.append(name)
    build_job = {
303
304
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
305
306
307
308
    }

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


320
321
322
323
324
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
325
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
326
        keep_trailing_newline=True,
327
328
    )

329
330
331
332
333
334
335
336
337
338
    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,
            )
        )