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

Wei Wang's avatar
Wei Wang committed
24
PYTHON_VERSIONS = ["3.8", "3.9", "3.10"]
25
CU_VERSIONS_DICT = {
26
27
    "linux": ["cpu", "cu117", "cu118", "rocm5.2", "rocm5.3"],
    "windows": ["cpu", "cu117", "cu118"],
28
29
    "macos": ["cpu"],
}
Nikita Shulga's avatar
Nikita Shulga committed
30

moto's avatar
moto committed
31

32
DOC_VERSION = ("linux", "3.8")
33

moto's avatar
moto committed
34

35
def build_workflows(prefix="", upload=False, filter_branch=None, indentation=6):
36
    w = []
37
    w += build_download_job(filter_branch)
38
39
    for os_type in ["linux", "macos", "windows"]:
        w += build_ffmpeg_job(os_type, filter_branch)
40
    for btype in ["wheel", "conda"]:
peterjc123's avatar
peterjc123 committed
41
        for os_type in ["linux", "macos", "windows"]:
moto's avatar
moto committed
42
            for python_version in PYTHON_VERSIONS:
Nikita Shulga's avatar
Nikita Shulga committed
43
                for cu_version in CU_VERSIONS_DICT[os_type]:
Matti Picus's avatar
Matti Picus committed
44
                    fb = filter_branch
45
46
47
                    if (
                        (cu_version.startswith("rocm") and btype == "conda")
                        or (os_type == "linux" and btype == "wheel")
48
49
50
                        or (
                            os_type == "linux"
                            and btype == "conda"
51
                            and (python_version != "3.8" or cu_version != "cu117")
52
                        )
53
                        or os_type == "macos"
54
                    ):
Matti Picus's avatar
Matti Picus committed
55
                        continue
56

57
58
59
                    if not fb and (
                        os_type == "linux" and btype == "wheel" and python_version == "3.8" and cu_version == "cpu"
                    ):
Matti Picus's avatar
Matti Picus committed
60
                        # the fields must match the build_docs "requires" dependency
61
                        fb = "/.*/"
62

63
                    if os_type == "linux" and btype == "conda" and python_version == "3.8" and cu_version == "cu117":
64
65
66
                        w += build_workflow_pair(btype, os_type, python_version, cu_version, fb, prefix, False)
                        continue

Matti Picus's avatar
Matti Picus committed
67
                    w += build_workflow_pair(btype, os_type, python_version, cu_version, fb, prefix, upload)
68

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

74
75
76
    return indent(indentation, w)


77
78
def build_download_job(filter_branch):
    job = {
79
        "name": "download_third_parties",
80
81
82
83
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
84
85
86
87
88
89
90
91
92
93
94
95
96
    return [{"download_third_parties": job}]


def build_ffmpeg_job(os_type, filter_branch):
    job = {
        "name": f"build_ffmpeg_{os_type}",
        "requires": ["download_third_parties"],
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    job["python_version"] = "foo"
    return [{f"build_ffmpeg_{os_type}": job}]
97
98


99
def build_workflow_pair(btype, os_type, python_version, cu_version, filter_branch, prefix="", upload=False):
100
101

    w = []
Nikita Shulga's avatar
Nikita Shulga committed
102
103
    base_workflow_name = f"{prefix}binary_{os_type}_{btype}_py{python_version}_{cu_version}"
    w.append(generate_base_workflow(base_workflow_name, python_version, cu_version, filter_branch, os_type, btype))
104
105

    if upload:
106
        w.append(generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version))
107

moto's avatar
moto committed
108
109
110
111
112
    if os_type != "macos":
        pydistro = "pip" if btype == "wheel" else "conda"
        w.append(
            generate_smoketest_workflow(
                pydistro, base_workflow_name, filter_branch, python_version, cu_version, os_type
113
            )
moto's avatar
moto committed
114
        )
115
116
117

    return w

moto's avatar
moto committed
118

119
120
121
122
def build_doc_job(filter_branch):
    job = {
        "name": "build_docs",
        "python_version": "3.8",
123
        "cuda_version": "cu117",
124
        "requires": [
125
            "binary_linux_conda_py3.8_cu117",
126
        ],
127
128
129
130
131
132
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    return [{"build_docs": job}]

moto's avatar
moto committed
133

134
135
136
def upload_doc_job(filter_branch):
    job = {
        "name": "upload_docs",
137
        "context": "org-member",
138
        "python_version": "3.8",
139
140
141
        "requires": [
            "build_docs",
        ],
142
143
144
145
146
147
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    return [{"upload_docs": job}]

148

149
150
151
152
def docstring_parameters_sync_job(filter_branch):
    job = {
        "name": "docstring_parameters_sync",
        "python_version": "3.8",
153
154
155
        "requires": [
            "binary_linux_wheel_py3.8_cpu",
        ],
156
157
158
159
160
161
162
    }

    if filter_branch:
        job["filters"] = gen_filter_branch_tree(filter_branch)
    return [{"docstring_parameters_sync": job}]


Nikita Shulga's avatar
Nikita Shulga committed
163
def generate_base_workflow(base_workflow_name, python_version, cu_version, filter_branch, os_type, btype):
164
165
166
167

    d = {
        "name": base_workflow_name,
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
168
        "cuda_version": cu_version,
169
        "requires": [f"build_ffmpeg_{os_type}"],
170
171
    }

172
173
174
175
176
    if btype == "conda":
        d["conda_docker_image"] = f'pytorch/conda-builder:{cu_version.replace("cu1","cuda1")}'
    elif cu_version.startswith("cu"):
        d["wheel_docker_image"] = f'pytorch/manylinux-{cu_version.replace("cu1","cuda1")}'
    elif cu_version.startswith("rocm"):
177
        d["wheel_docker_image"] = f"pytorch/manylinux-rocm:{cu_version[len('rocm'):]}"
178

179
180
181
    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

182
    return {f"binary_{os_type}_{btype}": d}
183
184


moto's avatar
moto committed
185
def gen_filter_branch_tree(*branches):
186
187
188
189
190
191
192
193
    return {
        "branches": {
            "only": list(branches),
        },
        "tags": {
            # Using a raw string here to avoid having to escape
            # anything
            "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/"
194
        },
195
    }
196
197


198
def generate_upload_workflow(base_workflow_name, filter_branch, os_type, btype, cu_version):
199
200
201
202
203
204
    d = {
        "name": "{base_workflow_name}_upload".format(base_workflow_name=base_workflow_name),
        "context": "org-member",
        "requires": [base_workflow_name],
    }

205
206
    if btype == "wheel":
        d["subfolder"] = "" if os_type == "macos" else cu_version + "/"
207

208
209
210
211
212
213
    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

    return {"binary_{btype}_upload".format(btype=btype): d}


Nikita Shulga's avatar
Nikita Shulga committed
214
def generate_smoketest_workflow(pydistro, base_workflow_name, filter_branch, python_version, cu_version, os_type):
215

Nikita Shulga's avatar
Nikita Shulga committed
216
    smoke_suffix = f"smoke_test_{pydistro}".format(pydistro=pydistro)
217
    d = {
Nikita Shulga's avatar
Nikita Shulga committed
218
        "name": f"{base_workflow_name}_{smoke_suffix}",
moto's avatar
moto committed
219
        "requires": [base_workflow_name],
220
        "python_version": python_version,
Nikita Shulga's avatar
Nikita Shulga committed
221
        "cuda_version": cu_version,
222
223
224
225
226
    }

    if filter_branch:
        d["filters"] = gen_filter_branch_tree(filter_branch)

Caroline Chen's avatar
Caroline Chen committed
227
    smoke_name = f"smoke_test_{os_type}_{pydistro}"
228
    if pydistro == "conda" and (os_type == "linux" or os_type == "windows") and cu_version != "cpu":
Caroline Chen's avatar
Caroline Chen committed
229
230
        smoke_name += "_gpu"
    return {smoke_name: d}
231
232
233
234
235
236


def indent(indentation, data_list):
    return ("\n" + " " * indentation).join(yaml.dump(data_list).splitlines())


237
238
239
240
241
242
243
244
def unittest_python_versions(os):
    return {
        "windows": PYTHON_VERSIONS[:1],
        "macos": PYTHON_VERSIONS[:1],
        "linux": PYTHON_VERSIONS,
    }.get(os)


moto's avatar
moto committed
245
def unittest_workflows(indentation=6):
moto's avatar
moto committed
246
    jobs = []
247
    jobs += build_download_job(None)
moto's avatar
moto committed
248
    for os_type in ["linux", "windows", "macos"]:
moto's avatar
moto committed
249
        for device_type in ["cpu", "gpu"]:
moto's avatar
moto committed
250
251
252
            if os_type == "macos" and device_type == "gpu":
                continue

253
            for i, python_version in enumerate(unittest_python_versions(os_type)):
moto's avatar
moto committed
254
255
                job = {
                    "name": f"unittest_{os_type}_{device_type}_py{python_version}",
moto's avatar
moto committed
256
                    "python_version": python_version,
257
                    "cuda_version": "cpu" if device_type == "cpu" else "cu117",
258
                    "requires": ["download_third_parties"],
moto's avatar
moto committed
259
                }
moto's avatar
moto committed
260
261

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

                if i == 0 and os_type == "linux" and device_type == "cpu":
264
265
266
267
268
269
270
                    jobs.append(
                        {
                            "stylecheck": {
                                "name": f"stylecheck_py{python_version}",
                                "python_version": python_version,
                                "cuda_version": "cpu",
                            }
271
                        }
272
                    )
moto's avatar
moto committed
273
    return indent(indentation, jobs)
moto's avatar
moto committed
274
275


276
277
278
279
280
if __name__ == "__main__":
    d = os.path.dirname(__file__)
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(d),
        lstrip_blocks=True,
281
        autoescape=select_autoescape(enabled_extensions=("html", "xml")),
282
283
    )

284
285
286
287
288
289
290
    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,
            )
        )
moto's avatar
moto committed
291
        f.write("\n")