regenerate.py 10.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 = {
ptrblck's avatar
ptrblck committed
35
36
                "linux": ["cpu", "cu117", "cu118", "cu121", "rocm5.2", "rocm5.3"],
                "win": ["cpu", "cu117", "cu118", "cu121"],
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",
ptrblck's avatar
ptrblck committed
112
    "cu121": "pytorch/manylinux-cuda121",
113
114
115
116
}


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


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


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

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

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

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

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

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


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


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

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

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

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


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


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


220
221
def cmake_workflows(indentation=6):
    jobs = []
222
223
    python_version = "3.8"
    for os_type in ["linux", "windows", "macos"]:
224
        # Skip OSX CUDA
225
        device_types = ["cpu", "gpu"] if os_type != "macos" else ["cpu"]
226
        for device in device_types:
227
            job = {"name": f"cmake_{os_type}_{device}", "python_version": python_version}
228

229
            job["cu_version"] = "cu117" if device == "gpu" else "cpu"
230
            if device == "gpu" and os_type == "linux":
231
                job["wheel_docker_image"] = "pytorch/manylinux-cuda117"
232
            jobs.append({f"cmake_{os_type}_{device}": job})
233
234
235
    return indent(indentation, jobs)


236
237
238
239
240
def ios_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""
241
242
    for arch, platform in [("x86_64", "SIMULATOR"), ("arm64", "OS")]:
        name = f"{name_prefix}binary_libtorchvision_ops_ios_12.0.0_{arch}"
243
244
        build_job_names.append(name)
        build_job = {
245
246
247
248
            "build_environment": f"{env_prefix}binary-libtorchvision_ops-ios-12.0.0-{arch}",
            "ios_arch": arch,
            "ios_platform": platform,
            "name": name,
249
250
        }
        if nightly:
251
252
            build_job["filters"] = gen_filter_branch_tree("nightly")
        jobs.append({"binary_ios_build": build_job})
253
254
255

    if nightly:
        upload_job = {
256
257
258
259
            "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,
260
        }
261
        jobs.append({"binary_ios_upload": upload_job})
262
263
264
    return indent(indentation, jobs)


265
266
267
268
269
270
def android_workflows(indentation=6, nightly=False):
    jobs = []
    build_job_names = []
    name_prefix = "nightly_" if nightly else ""
    env_prefix = "nightly-" if nightly else ""

271
    name = f"{name_prefix}binary_libtorchvision_ops_android"
272
273
    build_job_names.append(name)
    build_job = {
274
275
        "build_environment": f"{env_prefix}binary-libtorchvision_ops-android",
        "name": name,
276
277
278
279
    }

    if nightly:
        upload_job = {
280
281
282
283
            "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",
284
        }
285
        jobs.append({"binary_android_upload": upload_job})
286
    else:
287
        jobs.append({"binary_android_build": build_job})
288
289
290
    return indent(indentation, jobs)


291
292
293
294
295
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
296
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
297
        keep_trailing_newline=True,
298
299
    )

300
301
302
303
304
305
306
307
308
    with open(os.path.join(d, "config.yml"), "w") as f:
        f.write(
            env.get_template("config.yml.in").render(
                build_workflows=build_workflows,
                cmake_workflows=cmake_workflows,
                ios_workflows=ios_workflows,
                android_workflows=android_workflows,
            )
        )