##// END OF EJS Templates
automation: support running against Python 3.9...
Gregory Szorc -
r46285:64a94234 default
parent child Browse files
Show More
@@ -1,548 +1,548 b''
1 1 # cli.py - Command line interface for automation
2 2 #
3 3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # no-check-code because Python 3 native.
9 9
10 10 import argparse
11 11 import concurrent.futures as futures
12 12 import os
13 13 import pathlib
14 14 import time
15 15
16 16 from . import (
17 17 aws,
18 18 HGAutomation,
19 19 linux,
20 20 try_server,
21 21 windows,
22 22 )
23 23
24 24
25 25 SOURCE_ROOT = pathlib.Path(
26 26 os.path.abspath(__file__)
27 27 ).parent.parent.parent.parent
28 28 DIST_PATH = SOURCE_ROOT / 'dist'
29 29
30 30
31 31 def bootstrap_linux_dev(
32 32 hga: HGAutomation, aws_region, distros=None, parallel=False
33 33 ):
34 34 c = hga.aws_connection(aws_region)
35 35
36 36 if distros:
37 37 distros = distros.split(',')
38 38 else:
39 39 distros = sorted(linux.DISTROS)
40 40
41 41 # TODO There is a wonky interaction involving KeyboardInterrupt whereby
42 42 # the context manager that is supposed to terminate the temporary EC2
43 43 # instance doesn't run. Until we fix this, make parallel building opt-in
44 44 # so we don't orphan instances.
45 45 if parallel:
46 46 fs = []
47 47
48 48 with futures.ThreadPoolExecutor(len(distros)) as e:
49 49 for distro in distros:
50 50 fs.append(e.submit(aws.ensure_linux_dev_ami, c, distro=distro))
51 51
52 52 for f in fs:
53 53 f.result()
54 54 else:
55 55 for distro in distros:
56 56 aws.ensure_linux_dev_ami(c, distro=distro)
57 57
58 58
59 59 def bootstrap_windows_dev(hga: HGAutomation, aws_region, base_image_name):
60 60 c = hga.aws_connection(aws_region)
61 61 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
62 62 print('Windows development AMI available as %s' % image.id)
63 63
64 64
65 65 def build_inno(
66 66 hga: HGAutomation,
67 67 aws_region,
68 68 python_version,
69 69 arch,
70 70 revision,
71 71 version,
72 72 base_image_name,
73 73 ):
74 74 c = hga.aws_connection(aws_region)
75 75 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
76 76 DIST_PATH.mkdir(exist_ok=True)
77 77
78 78 with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts:
79 79 instance = insts[0]
80 80
81 81 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
82 82
83 83 for py_version in python_version:
84 84 for a in arch:
85 85 windows.build_inno_installer(
86 86 instance.winrm_client,
87 87 py_version,
88 88 a,
89 89 DIST_PATH,
90 90 version=version,
91 91 )
92 92
93 93
94 94 def build_wix(
95 95 hga: HGAutomation,
96 96 aws_region,
97 97 python_version,
98 98 arch,
99 99 revision,
100 100 version,
101 101 base_image_name,
102 102 ):
103 103 c = hga.aws_connection(aws_region)
104 104 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
105 105 DIST_PATH.mkdir(exist_ok=True)
106 106
107 107 with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts:
108 108 instance = insts[0]
109 109
110 110 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
111 111
112 112 for py_version in python_version:
113 113 for a in arch:
114 114 windows.build_wix_installer(
115 115 instance.winrm_client,
116 116 py_version,
117 117 a,
118 118 DIST_PATH,
119 119 version=version,
120 120 )
121 121
122 122
123 123 def build_windows_wheel(
124 124 hga: HGAutomation,
125 125 aws_region,
126 126 python_version,
127 127 arch,
128 128 revision,
129 129 base_image_name,
130 130 ):
131 131 c = hga.aws_connection(aws_region)
132 132 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
133 133 DIST_PATH.mkdir(exist_ok=True)
134 134
135 135 with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts:
136 136 instance = insts[0]
137 137
138 138 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
139 139
140 140 for py_version in python_version:
141 141 for a in arch:
142 142 windows.build_wheel(
143 143 instance.winrm_client, py_version, a, DIST_PATH
144 144 )
145 145
146 146
147 147 def build_all_windows_packages(
148 148 hga: HGAutomation, aws_region, revision, version, base_image_name
149 149 ):
150 150 c = hga.aws_connection(aws_region)
151 151 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
152 152 DIST_PATH.mkdir(exist_ok=True)
153 153
154 154 with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts:
155 155 instance = insts[0]
156 156
157 157 winrm_client = instance.winrm_client
158 158
159 159 windows.synchronize_hg(SOURCE_ROOT, revision, instance)
160 160
161 for py_version in ("2.7", "3.7", "3.8"):
161 for py_version in ("2.7", "3.7", "3.8", "3.9"):
162 162 for arch in ("x86", "x64"):
163 163 windows.purge_hg(winrm_client)
164 164 windows.build_wheel(
165 165 winrm_client,
166 166 python_version=py_version,
167 167 arch=arch,
168 168 dest_path=DIST_PATH,
169 169 )
170 170
171 171 for py_version in (2, 3):
172 172 for arch in ('x86', 'x64'):
173 173 windows.purge_hg(winrm_client)
174 174 windows.build_inno_installer(
175 175 winrm_client, py_version, arch, DIST_PATH, version=version
176 176 )
177 177 windows.build_wix_installer(
178 178 winrm_client, py_version, arch, DIST_PATH, version=version
179 179 )
180 180
181 181
182 182 def terminate_ec2_instances(hga: HGAutomation, aws_region):
183 183 c = hga.aws_connection(aws_region, ensure_ec2_state=False)
184 184 aws.terminate_ec2_instances(c.ec2resource)
185 185
186 186
187 187 def purge_ec2_resources(hga: HGAutomation, aws_region):
188 188 c = hga.aws_connection(aws_region, ensure_ec2_state=False)
189 189 aws.remove_resources(c)
190 190
191 191
192 192 def run_tests_linux(
193 193 hga: HGAutomation,
194 194 aws_region,
195 195 instance_type,
196 196 python_version,
197 197 test_flags,
198 198 distro,
199 199 filesystem,
200 200 ):
201 201 c = hga.aws_connection(aws_region)
202 202 image = aws.ensure_linux_dev_ami(c, distro=distro)
203 203
204 204 t_start = time.time()
205 205
206 206 ensure_extra_volume = filesystem not in ('default', 'tmpfs')
207 207
208 208 with aws.temporary_linux_dev_instances(
209 209 c, image, instance_type, ensure_extra_volume=ensure_extra_volume
210 210 ) as insts:
211 211
212 212 instance = insts[0]
213 213
214 214 linux.prepare_exec_environment(
215 215 instance.ssh_client, filesystem=filesystem
216 216 )
217 217 linux.synchronize_hg(SOURCE_ROOT, instance, '.')
218 218 t_prepared = time.time()
219 219 linux.run_tests(instance.ssh_client, python_version, test_flags)
220 220 t_done = time.time()
221 221
222 222 t_setup = t_prepared - t_start
223 223 t_all = t_done - t_start
224 224
225 225 print(
226 226 'total time: %.1fs; setup: %.1fs; tests: %.1fs; setup overhead: %.1f%%'
227 227 % (t_all, t_setup, t_done - t_prepared, t_setup / t_all * 100.0)
228 228 )
229 229
230 230
231 231 def run_tests_windows(
232 232 hga: HGAutomation,
233 233 aws_region,
234 234 instance_type,
235 235 python_version,
236 236 arch,
237 237 test_flags,
238 238 base_image_name,
239 239 ):
240 240 c = hga.aws_connection(aws_region)
241 241 image = aws.ensure_windows_dev_ami(c, base_image_name=base_image_name)
242 242
243 243 with aws.temporary_windows_dev_instances(
244 244 c, image, instance_type, disable_antivirus=True
245 245 ) as insts:
246 246 instance = insts[0]
247 247
248 248 windows.synchronize_hg(SOURCE_ROOT, '.', instance)
249 249 windows.run_tests(
250 250 instance.winrm_client, python_version, arch, test_flags
251 251 )
252 252
253 253
254 254 def publish_windows_artifacts(
255 255 hg: HGAutomation,
256 256 aws_region,
257 257 version: str,
258 258 pypi: bool,
259 259 mercurial_scm_org: bool,
260 260 ssh_username: str,
261 261 ):
262 262 windows.publish_artifacts(
263 263 DIST_PATH,
264 264 version,
265 265 pypi=pypi,
266 266 mercurial_scm_org=mercurial_scm_org,
267 267 ssh_username=ssh_username,
268 268 )
269 269
270 270
271 271 def run_try(hga: HGAutomation, aws_region: str, rev: str):
272 272 c = hga.aws_connection(aws_region, ensure_ec2_state=False)
273 273 try_server.trigger_try(c, rev=rev)
274 274
275 275
276 276 def get_parser():
277 277 parser = argparse.ArgumentParser()
278 278
279 279 parser.add_argument(
280 280 '--state-path',
281 281 default='~/.hgautomation',
282 282 help='Path for local state files',
283 283 )
284 284 parser.add_argument(
285 285 '--aws-region', help='AWS region to use', default='us-west-2',
286 286 )
287 287
288 288 subparsers = parser.add_subparsers()
289 289
290 290 sp = subparsers.add_parser(
291 291 'bootstrap-linux-dev', help='Bootstrap Linux development environments',
292 292 )
293 293 sp.add_argument(
294 294 '--distros', help='Comma delimited list of distros to bootstrap',
295 295 )
296 296 sp.add_argument(
297 297 '--parallel',
298 298 action='store_true',
299 299 help='Generate AMIs in parallel (not CTRL-c safe)',
300 300 )
301 301 sp.set_defaults(func=bootstrap_linux_dev)
302 302
303 303 sp = subparsers.add_parser(
304 304 'bootstrap-windows-dev',
305 305 help='Bootstrap the Windows development environment',
306 306 )
307 307 sp.add_argument(
308 308 '--base-image-name',
309 309 help='AMI name of base image',
310 310 default=aws.WINDOWS_BASE_IMAGE_NAME,
311 311 )
312 312 sp.set_defaults(func=bootstrap_windows_dev)
313 313
314 314 sp = subparsers.add_parser(
315 315 'build-all-windows-packages', help='Build all Windows packages',
316 316 )
317 317 sp.add_argument(
318 318 '--revision', help='Mercurial revision to build', default='.',
319 319 )
320 320 sp.add_argument(
321 321 '--version', help='Mercurial version string to use',
322 322 )
323 323 sp.add_argument(
324 324 '--base-image-name',
325 325 help='AMI name of base image',
326 326 default=aws.WINDOWS_BASE_IMAGE_NAME,
327 327 )
328 328 sp.set_defaults(func=build_all_windows_packages)
329 329
330 330 sp = subparsers.add_parser(
331 331 'build-inno', help='Build Inno Setup installer(s)',
332 332 )
333 333 sp.add_argument(
334 334 '--python-version',
335 335 help='Which version of Python to target',
336 336 choices={2, 3},
337 337 type=int,
338 338 nargs='*',
339 339 default=[3],
340 340 )
341 341 sp.add_argument(
342 342 '--arch',
343 343 help='Architecture to build for',
344 344 choices={'x86', 'x64'},
345 345 nargs='*',
346 346 default=['x64'],
347 347 )
348 348 sp.add_argument(
349 349 '--revision', help='Mercurial revision to build', default='.',
350 350 )
351 351 sp.add_argument(
352 352 '--version', help='Mercurial version string to use in installer',
353 353 )
354 354 sp.add_argument(
355 355 '--base-image-name',
356 356 help='AMI name of base image',
357 357 default=aws.WINDOWS_BASE_IMAGE_NAME,
358 358 )
359 359 sp.set_defaults(func=build_inno)
360 360
361 361 sp = subparsers.add_parser(
362 362 'build-windows-wheel', help='Build Windows wheel(s)',
363 363 )
364 364 sp.add_argument(
365 365 '--python-version',
366 366 help='Python version to build for',
367 choices={'2.7', '3.7', '3.8'},
367 choices={'2.7', '3.7', '3.8', '3.9'},
368 368 nargs='*',
369 369 default=['3.8'],
370 370 )
371 371 sp.add_argument(
372 372 '--arch',
373 373 help='Architecture to build for',
374 374 choices={'x86', 'x64'},
375 375 nargs='*',
376 376 default=['x64'],
377 377 )
378 378 sp.add_argument(
379 379 '--revision', help='Mercurial revision to build', default='.',
380 380 )
381 381 sp.add_argument(
382 382 '--base-image-name',
383 383 help='AMI name of base image',
384 384 default=aws.WINDOWS_BASE_IMAGE_NAME,
385 385 )
386 386 sp.set_defaults(func=build_windows_wheel)
387 387
388 388 sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)')
389 389 sp.add_argument(
390 390 '--python-version',
391 391 help='Which version of Python to target',
392 392 choices={2, 3},
393 393 type=int,
394 394 nargs='*',
395 395 default=[3],
396 396 )
397 397 sp.add_argument(
398 398 '--arch',
399 399 help='Architecture to build for',
400 400 choices={'x86', 'x64'},
401 401 nargs='*',
402 402 default=['x64'],
403 403 )
404 404 sp.add_argument(
405 405 '--revision', help='Mercurial revision to build', default='.',
406 406 )
407 407 sp.add_argument(
408 408 '--version', help='Mercurial version string to use in installer',
409 409 )
410 410 sp.add_argument(
411 411 '--base-image-name',
412 412 help='AMI name of base image',
413 413 default=aws.WINDOWS_BASE_IMAGE_NAME,
414 414 )
415 415 sp.set_defaults(func=build_wix)
416 416
417 417 sp = subparsers.add_parser(
418 418 'terminate-ec2-instances',
419 419 help='Terminate all active EC2 instances managed by us',
420 420 )
421 421 sp.set_defaults(func=terminate_ec2_instances)
422 422
423 423 sp = subparsers.add_parser(
424 424 'purge-ec2-resources', help='Purge all EC2 resources managed by us',
425 425 )
426 426 sp.set_defaults(func=purge_ec2_resources)
427 427
428 428 sp = subparsers.add_parser('run-tests-linux', help='Run tests on Linux',)
429 429 sp.add_argument(
430 430 '--distro',
431 431 help='Linux distribution to run tests on',
432 432 choices=linux.DISTROS,
433 433 default='debian10',
434 434 )
435 435 sp.add_argument(
436 436 '--filesystem',
437 437 help='Filesystem type to use',
438 438 choices={'btrfs', 'default', 'ext3', 'ext4', 'jfs', 'tmpfs', 'xfs'},
439 439 default='default',
440 440 )
441 441 sp.add_argument(
442 442 '--instance-type',
443 443 help='EC2 instance type to use',
444 444 default='c5.9xlarge',
445 445 )
446 446 sp.add_argument(
447 447 '--python-version',
448 448 help='Python version to use',
449 449 choices={
450 450 'system2',
451 451 'system3',
452 452 '2.7',
453 453 '3.5',
454 454 '3.6',
455 455 '3.7',
456 456 '3.8',
457 457 'pypy',
458 458 'pypy3.5',
459 459 'pypy3.6',
460 460 },
461 461 default='system2',
462 462 )
463 463 sp.add_argument(
464 464 'test_flags',
465 465 help='Extra command line flags to pass to run-tests.py',
466 466 nargs='*',
467 467 )
468 468 sp.set_defaults(func=run_tests_linux)
469 469
470 470 sp = subparsers.add_parser(
471 471 'run-tests-windows', help='Run tests on Windows',
472 472 )
473 473 sp.add_argument(
474 474 '--instance-type', help='EC2 instance type to use', default='t3.medium',
475 475 )
476 476 sp.add_argument(
477 477 '--python-version',
478 478 help='Python version to use',
479 choices={'2.7', '3.5', '3.6', '3.7', '3.8'},
479 choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9'},
480 480 default='2.7',
481 481 )
482 482 sp.add_argument(
483 483 '--arch',
484 484 help='Architecture to test',
485 485 choices={'x86', 'x64'},
486 486 default='x64',
487 487 )
488 488 sp.add_argument(
489 489 '--test-flags', help='Extra command line flags to pass to run-tests.py',
490 490 )
491 491 sp.add_argument(
492 492 '--base-image-name',
493 493 help='AMI name of base image',
494 494 default=aws.WINDOWS_BASE_IMAGE_NAME,
495 495 )
496 496 sp.set_defaults(func=run_tests_windows)
497 497
498 498 sp = subparsers.add_parser(
499 499 'publish-windows-artifacts',
500 500 help='Publish built Windows artifacts (wheels, installers, etc)',
501 501 )
502 502 sp.add_argument(
503 503 '--no-pypi',
504 504 dest='pypi',
505 505 action='store_false',
506 506 default=True,
507 507 help='Skip uploading to PyPI',
508 508 )
509 509 sp.add_argument(
510 510 '--no-mercurial-scm-org',
511 511 dest='mercurial_scm_org',
512 512 action='store_false',
513 513 default=True,
514 514 help='Skip uploading to www.mercurial-scm.org',
515 515 )
516 516 sp.add_argument(
517 517 '--ssh-username', help='SSH username for mercurial-scm.org',
518 518 )
519 519 sp.add_argument(
520 520 'version', help='Mercurial version string to locate local packages',
521 521 )
522 522 sp.set_defaults(func=publish_windows_artifacts)
523 523
524 524 sp = subparsers.add_parser(
525 525 'try', help='Run CI automation against a custom changeset'
526 526 )
527 527 sp.add_argument('-r', '--rev', default='.', help='Revision to run CI on')
528 528 sp.set_defaults(func=run_try)
529 529
530 530 return parser
531 531
532 532
533 533 def main():
534 534 parser = get_parser()
535 535 args = parser.parse_args()
536 536
537 537 local_state_path = pathlib.Path(os.path.expanduser(args.state_path))
538 538 automation = HGAutomation(local_state_path)
539 539
540 540 if not hasattr(args, 'func'):
541 541 parser.print_help()
542 542 return
543 543
544 544 kwargs = dict(vars(args))
545 545 del kwargs['func']
546 546 del kwargs['state_path']
547 547
548 548 args.func(automation, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now