| Left: | ||
| Right: |
| LEFT | RIGHT |
|---|---|
| 1 # This Source Code Form is subject to the terms of the Mozilla Public | 1 # This Source Code Form is subject to the terms of the Mozilla Public |
| 2 # License, v. 2.0. If a copy of the MPL was not distributed with this | 2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 4 | 4 |
| 5 from __future__ import print_function | |
| 6 | |
| 7 import argparse | 5 import argparse |
| 6 import logging | |
| 8 import os | 7 import os |
| 8 import re | |
| 9 import shutil | |
| 10 import subprocess | |
| 9 import sys | 11 import sys |
| 10 import re | |
| 11 import subprocess | |
| 12 import shutil | |
| 13 from functools import partial | 12 from functools import partial |
| 14 from StringIO import StringIO | 13 from StringIO import StringIO |
| 15 from zipfile import ZipFile | 14 from zipfile import ZipFile |
| 16 | 15 |
| 17 KNOWN_PLATFORMS = {'gecko', 'chrome', 'generic', 'edge'} | 16 KNOWN_PLATFORMS = {'chrome', 'gecko', 'edge', 'generic'} |
| 18 | 17 |
| 19 MAIN_PARSER = argparse.ArgumentParser( | 18 MAIN_PARSER = argparse.ArgumentParser( |
| 20 description=__doc__, | 19 description=__doc__, |
| 21 formatter_class=argparse.RawDescriptionHelpFormatter) | 20 formatter_class=argparse.RawDescriptionHelpFormatter) |
| 22 | 21 |
| 23 SUB_PARSERS = MAIN_PARSER.add_subparsers(title='Commands', dest='action', | 22 SUB_PARSERS = MAIN_PARSER.add_subparsers(title='Commands', dest='action', |
| 24 metavar='[command]') | 23 metavar='[command]') |
| 24 | |
| 25 ALL_COMMANDS = [] | |
| 25 | 26 |
| 26 | 27 |
| 27 def make_argument(*args, **kwargs): | 28 def make_argument(*args, **kwargs): |
| 28 def _make_argument(*args, **kwargs): | 29 def _make_argument(*args, **kwargs): |
| 29 parser = kwargs.pop('parser') | 30 parser = kwargs.pop('parser') |
| 30 parser.add_argument(*args, **kwargs) | 31 parser.add_argument(*args, **kwargs) |
| 31 | 32 |
| 32 return partial(_make_argument, *args, **kwargs) | 33 return partial(_make_argument, *args, **kwargs) |
| 33 | 34 |
| 34 | 35 |
| 35 def argparse_command(valid_platforms=None, arguments=()): | 36 def argparse_command(valid_platforms=None, arguments=()): |
| 36 def wrapper(func): | 37 def wrapper(func): |
| 37 def func_wrapper(*args, **kwargs): | 38 def func_wrapper(*args, **kwargs): |
| 38 return func(*args, **kwargs) | 39 return func(*args, **kwargs) |
| 39 | 40 |
| 40 short_desc, long_desc = func.__doc__.split(os.linesep + os.linesep, 1) | 41 short_desc, long_desc = func.__doc__.split(os.linesep + os.linesep, 1) |
| 41 | 42 |
| 42 new_parser = SUB_PARSERS.add_parser( | 43 ALL_COMMANDS.append({ |
| 43 func.__name__, | 44 'name': func.__name__, |
| 44 description=long_desc, | 45 'description': long_desc, |
| 45 formatter_class=argparse.RawDescriptionHelpFormatter, | 46 'help_text': short_desc, |
| 46 help=short_desc, | 47 'valid_platforms': valid_platforms or KNOWN_PLATFORMS, |
| 47 ) | 48 'function': func, |
| 48 | 49 'arguments': arguments, |
| 49 new_parser.__valid_platforms = valid_platforms or KNOWN_PLATFORMS | 50 }) |
| 50 | |
| 51 for argument in arguments: | |
| 52 argument(parser=new_parser) | |
| 53 | |
| 54 new_parser.set_defaults(function=func) | |
| 55 return func_wrapper | 51 return func_wrapper |
| 56 | 52 |
| 57 return wrapper | 53 return wrapper |
| 58 | 54 |
| 59 | 55 |
| 60 def collect_platforms(base_dir): | 56 def make_subcommand(name, description, help_text, function, arguments): |
| 61 """Construct valid platforms for each subcommand. | 57 new_parser = SUB_PARSERS.add_parser( |
| 58 name, description=description, help=help_text, | |
| 59 formatter_class=argparse.RawDescriptionHelpFormatter, | |
| 60 ) | |
| 61 | |
| 62 for argument in arguments: | |
| 63 argument(parser=new_parser) | |
| 64 | |
| 65 new_parser.set_defaults(function=function) | |
| 66 return new_parser | |
| 67 | |
| 68 | |
| 69 def build_available_subcommands(base_dir): | |
| 70 """Build subcommands, which are available for the repository in base_dir. | |
| 62 | 71 |
| 63 Search 'base_dir' for existing metadata.<type> files and make <type> an | 72 Search 'base_dir' for existing metadata.<type> files and make <type> an |
| 64 avaible choice for the subcommand, intersected with their respective valid | 73 avaible choice for the subcommands, intersected with their respective valid |
| 65 platforms. | 74 platforms. |
| 66 """ | 75 |
| 67 if collect_platforms._result is not None: | 76 If no valid platform is found for a subcommand, it get's omitted. |
| 77 """ | |
| 78 if build_available_subcommands._result is not None: | |
| 68 # Tests might run this code multiple times, make sure the collection | 79 # Tests might run this code multiple times, make sure the collection |
| 69 # of platforms is only run once. | 80 # of platforms is only run once. |
| 70 return collect_platforms._result | 81 return build_available_subcommands._result |
| 71 | 82 |
| 72 types = set() | 83 types = set() |
| 73 for p in KNOWN_PLATFORMS: | 84 for p in KNOWN_PLATFORMS: |
| 74 if os.path.exists(os.path.join(base_dir, 'metadata.' + p)): | 85 if os.path.exists(os.path.join(base_dir, 'metadata.' + p)): |
| 75 types.add(p) | 86 types.add(p) |
| 76 | 87 |
| 77 if len(types) == 0: | 88 if len(types) == 0: |
| 78 print('No metadata file found in this repository, a metadata file ' | 89 logging.error('No metadata file found in this repository. Expecting ' |
| 79 'metadata.* is required.', file=sys.stderr) | 90 'one or more of {} to be present.'.format( |
|
Wladimir Palant
2017/11/17 11:51:58
This message is confusing now, with only known pla
tlucas
2017/11/17 13:32:04
Done.
| |
| 80 collect_platforms._result = False | 91 ', '.join('metadata.' + p for p in KNOWN_PLATFORMS))) |
| 92 build_available_subcommands._result = False | |
| 81 return False | 93 return False |
| 82 | 94 |
| 83 for _, sub_parser in SUB_PARSERS.choices.items(): | 95 for command_params in ALL_COMMANDS: |
|
Wladimir Palant
2017/11/17 11:51:58
If you don't need the keys, why iterate over items
tlucas
2017/11/17 13:32:04
This will be obsolete with addressing the below co
| |
| 84 platforms = types.intersection(sub_parser.__valid_platforms) | 96 platforms = types.intersection(command_params.pop('valid_platforms')) |
| 85 if len(platforms) > 1: | 97 if len(platforms) > 1: |
| 86 sub_parser.add_argument('-t', '--type', dest='platform', | 98 command_params['arguments'] += ( |
| 87 choices=platforms) | 99 make_argument('-t', '--type', dest='platform', required=True, |
| 88 else: | 100 choices=platforms), |
| 101 ) | |
| 102 make_subcommand(**command_params) | |
| 103 elif len(platforms) == 1: | |
| 104 sub_parser = make_subcommand(**command_params) | |
| 89 sub_parser.set_defaults(platform=platforms.pop()) | 105 sub_parser.set_defaults(platform=platforms.pop()) |
|
Wladimir Palant
2017/11/17 11:51:58
What about len(platforms) == 0?
tlucas
2017/11/17 13:32:04
This was not trivial - but i came up with a soluti
| |
| 90 | 106 |
| 91 collect_platforms._result = True | 107 build_available_subcommands._result = True |
| 92 return True | 108 return True |
| 93 | 109 |
| 94 | 110 |
| 95 collect_platforms._result = None | 111 build_available_subcommands._result = None |
|
tlucas
2017/11/17 11:36:45
I did not see the necessity to introduce a fully f
| |
| 96 | 112 |
| 97 | 113 |
| 98 @argparse_command( | 114 @argparse_command( |
| 99 valid_platforms={'gecko', 'chrome', 'edge'}, | 115 valid_platforms={'chrome', 'gecko', 'edge'}, |
| 100 arguments=( | 116 arguments=( |
| 101 make_argument( | 117 make_argument( |
| 102 '-b', '--build-num', dest='build_num', | 118 '-b', '--build-num', dest='build_num', |
| 103 help=('Use given build number (if omitted the build number will ' | 119 help=('Use given build number (if omitted the build number will ' |
| 104 'be retrieved from Mercurial)')), | 120 'be retrieved from Mercurial)')), |
| 105 make_argument( | 121 make_argument( |
| 106 '-k', '--key', dest='key_file', | 122 '-k', '--key', dest='key_file', |
| 107 help=('File containing private key and certificates required to ' | 123 help=('File containing private key and certificates required to ' |
| 108 'sign the package')), | 124 'sign the package')), |
| 109 make_argument( | 125 make_argument( |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 273 locale_config = read_locale_config(base_dir, platform, metadata) | 289 locale_config = read_locale_config(base_dir, platform, metadata) |
| 274 | 290 |
| 275 import buildtools.localeTools as localeTools | 291 import buildtools.localeTools as localeTools |
| 276 localeTools.getTranslations(locale_config, basename, project_key) | 292 localeTools.getTranslations(locale_config, basename, project_key) |
| 277 | 293 |
| 278 | 294 |
| 279 @argparse_command( | 295 @argparse_command( |
| 280 valid_platforms={'chrome'}, | 296 valid_platforms={'chrome'}, |
| 281 arguments=( | 297 arguments=( |
| 282 make_argument('target_dir'), | 298 make_argument('target_dir'), |
| 283 make_argument('-q', '--quiet', help='Suppress JsDoc output'), | 299 make_argument('-q', '--quiet', help='Suppress JsDoc output', |
| 300 action='store_true', default=False), | |
| 284 ) | 301 ) |
| 285 ) | 302 ) |
| 286 def docs(base_dir, target_dir, quiet, platform, **kwargs): | 303 def docs(base_dir, target_dir, quiet, platform, **kwargs): |
| 287 """ | 304 """ |
| 288 Generate documentation (requires node.js). | 305 Generate documentation (requires node.js). |
| 289 | 306 |
| 290 Generate documentation files and write them into the specified directory. | 307 Generate documentation files and write them into the specified directory. |
| 291 """ | 308 """ |
| 292 source_dir = os.path.join(base_dir, 'lib') | 309 source_dir = os.path.join(base_dir, 'lib') |
| 293 | 310 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 345 Note: If you are not the project owner then you probably don't want to run | 362 Note: If you are not the project owner then you probably don't want to run |
| 346 this! | 363 this! |
| 347 | 364 |
| 348 Run release automation: create downloads for the new version, tag source | 365 Run release automation: create downloads for the new version, tag source |
| 349 code repository as well as downloads and buildtools repository. | 366 code repository as well as downloads and buildtools repository. |
| 350 """ | 367 """ |
| 351 if downloads_repository is None: | 368 if downloads_repository is None: |
| 352 downloads_repository = os.path.join(base_dir, os.pardir, 'downloads') | 369 downloads_repository = os.path.join(base_dir, os.pardir, 'downloads') |
| 353 | 370 |
| 354 if platform == 'chrome' and key_file is None: | 371 if platform == 'chrome' and key_file is None: |
| 355 raise ValueError('You must specify a key file for this release') | 372 logging.error('You must specify a key file for this release') |
| 373 return | |
| 356 | 374 |
| 357 import buildtools.releaseAutomation as releaseAutomation | 375 import buildtools.releaseAutomation as releaseAutomation |
| 358 releaseAutomation.run(base_dir, platform, version, key_file, | 376 releaseAutomation.run(base_dir, platform, version, key_file, |
| 359 downloads_repository) | 377 downloads_repository) |
| 360 | 378 |
| 361 | 379 |
| 362 @argparse_command(valid_platforms={'chrome'}) | 380 @argparse_command(valid_platforms={'chrome'}) |
| 363 def updatepsl(base_dir, **kwargs): | 381 def updatepsl(base_dir, **kwargs): |
| 364 """Update Public Suffix List. | 382 """Update Public Suffix List. |
| 365 | 383 |
| 366 Downloads Public Suffix List (see http://publicsuffix.org/) and generates | 384 Downloads Public Suffix List (see http://publicsuffix.org/) and generates |
| 367 lib/publicSuffixList.js from it. | 385 lib/publicSuffixList.js from it. |
| 368 """ | 386 """ |
| 369 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater | 387 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater |
| 370 publicSuffixListUpdater.updatePSL(base_dir) | 388 publicSuffixListUpdater.updatePSL(base_dir) |
| 371 | 389 |
| 372 | 390 |
| 373 def process_args(base_dir, *args): | 391 def process_args(base_dir, *args): |
| 374 if collect_platforms(base_dir): | 392 if build_available_subcommands(base_dir): |
| 375 MAIN_PARSER.set_defaults(base_dir=base_dir) | 393 MAIN_PARSER.set_defaults(base_dir=base_dir) |
| 376 | 394 |
| 377 # If no args are provided, this module is run directly from the command | 395 # If no args are provided, this module is run directly from the command |
| 378 # line. argparse will take care of consuming sys.argv. | 396 # line. argparse will take care of consuming sys.argv. |
| 379 arguments = MAIN_PARSER.parse_args(args if len(args) > 0 else None) | 397 arguments = MAIN_PARSER.parse_args(args if len(args) > 0 else None) |
| 380 | 398 |
| 381 function = arguments.function | 399 function = arguments.function |
| 382 del arguments.function | 400 del arguments.function |
| 383 function(**vars(arguments)) | 401 function(**vars(arguments)) |
| LEFT | RIGHT |