| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| 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 import argparse | |
| 5 import os | 6 import os |
| 6 import sys | 7 import sys |
| 7 import re | 8 import re |
| 8 import subprocess | 9 import subprocess |
| 9 import shutil | 10 import shutil |
| 10 from getopt import getopt, GetoptError | 11 from functools import partial |
| 11 from StringIO import StringIO | 12 from StringIO import StringIO |
| 12 from zipfile import ZipFile | 13 from zipfile import ZipFile |
| 13 | 14 |
| 14 knownTypes = {'gecko', 'chrome', 'generic', 'edge'} | 15 KNOWN_PLATFORMS = {'gecko', 'chrome', 'generic', 'edge'} |
| 16 | |
| 17 parser = argparse.ArgumentParser( | |
| 18 description=__doc__, | |
| 19 formatter_class=argparse.RawDescriptionHelpFormatter) | |
| 20 | |
| 21 subs = parser.add_subparsers(title='Commands', dest='action', | |
| 22 metavar='[command]') | |
| 15 | 23 |
| 16 | 24 |
| 17 class Command(object): | 25 def make_argument(*args, **kwargs): |
| 18 name = property(lambda self: self._name) | 26 def _make_argument(*args, **kwargs): |
| 19 shortDescription = property( | 27 parser = kwargs.pop('parser') |
| 20 lambda self: self._shortDescription, | 28 parser.add_argument(*args, **kwargs) |
| 21 lambda self, value: self.__dict__.update({'_shortDescription': value}) | |
| 22 ) | |
| 23 description = property( | |
| 24 lambda self: self._description, | |
| 25 lambda self, value: self.__dict__.update({'_description': value}) | |
| 26 ) | |
| 27 params = property( | |
| 28 lambda self: self._params, | |
| 29 lambda self, value: self.__dict__.update({'_params': value}) | |
| 30 ) | |
| 31 supportedTypes = property( | |
| 32 lambda self: self._supportedTypes, | |
| 33 lambda self, value: self.__dict__.update({'_supportedTypes': value}) | |
| 34 ) | |
| 35 options = property(lambda self: self._options) | |
| 36 | 29 |
| 37 def __init__(self, handler, name): | 30 return partial(_make_argument, *args, **kwargs) |
| 38 self._handler = handler | |
| 39 self._name = name | |
| 40 self._shortDescription = '' | |
| 41 self._description = '' | |
| 42 self._params = '' | |
| 43 self._supportedTypes = None | |
| 44 self._options = [] | |
| 45 self.addOption('Show this message and exit', short='h', long='help') | |
| 46 | |
| 47 def __enter__(self): | |
| 48 return self | |
| 49 | |
| 50 def __exit__(self, exc_type, exc_value, traceback): | |
| 51 pass | |
| 52 | |
| 53 def __call__(self, baseDir, scriptName, opts, args, type): | |
| 54 return self._handler(baseDir, scriptName, opts, args, type) | |
| 55 | |
| 56 def isSupported(self, type): | |
| 57 return self._supportedTypes == None or type in self._supportedTypes | |
| 58 | |
| 59 def addOption(self, description, short=None, long=None, value=None, types=No ne): | |
| 60 self._options.append((description, short, long, value, types)) | |
| 61 | |
| 62 def parseArgs(self, type, args): | |
| 63 shortOptions = map( | |
| 64 lambda o: o[1] + ':' if o[3] != None else o[1], | |
| 65 filter( | |
| 66 lambda o: o[1] != None and (o[4] == None or type in o[4]), | |
| 67 self._options | |
| 68 ) | |
| 69 ) | |
| 70 longOptions = map( | |
| 71 lambda o: o[2] + '=' if o[3] != None else o[2], | |
| 72 filter( | |
| 73 lambda o: o[2] != None and (o[4] == None or type in o[4]), | |
| 74 self._options | |
| 75 ) | |
| 76 ) | |
| 77 return getopt(args, ''.join(shortOptions), longOptions) | |
| 78 | 31 |
| 79 | 32 |
| 80 commandsList = [] | 33 def argparse_command(name=None, platforms=None, arguments=()): |
| 81 commands = {} | 34 def wrapper(func): |
| 35 def func_wrapper(*args, **kwargs): | |
| 36 return func(*args, **kwargs) | |
| 37 | |
| 38 short_desc, long_desc = func.__doc__.split(os.linesep + os.linesep, 1) | |
| 39 | |
| 40 new_parser = subs.add_parser( | |
| 41 name or func.__name__, | |
| 42 description=long_desc, | |
| 43 formatter_class=argparse.RawDescriptionHelpFormatter, | |
| 44 help=short_desc, | |
| 45 ) | |
| 46 | |
| 47 if len(platforms) > 1: | |
| 48 new_parser.add_argument('-t', '--type', dest='platform', | |
| 49 choices=platforms, required=True) | |
|
Wladimir Palant
2017/11/17 09:26:10
This is suboptimal for repositories where only one
tlucas
2017/11/17 11:36:45
Done. (See collect_platforms)
| |
| 50 elif len(platforms) == 1: | |
| 51 new_parser.set_defaults(platform=platforms.pop()) | |
| 52 | |
| 53 for argument in arguments: | |
| 54 argument(parser=new_parser) | |
| 55 | |
| 56 new_parser.set_defaults(function=func) | |
| 57 return func_wrapper | |
| 58 | |
| 59 return wrapper | |
| 82 | 60 |
| 83 | 61 |
| 84 def addCommand(handler, name): | 62 @argparse_command( |
| 85 if isinstance(name, basestring): | 63 name='build', platforms={'gecko', 'chrome', 'edge'}, |
| 86 aliases = () | 64 arguments=( |
| 87 else: | 65 make_argument( |
| 88 name, aliases = (name[0], name[1:]) | 66 '-b', '--build', |
| 67 help=('Use given build number (if omitted the build number will ' | |
| 68 'be retrieved from Mercurial)')), | |
| 69 make_argument( | |
| 70 '-k', '--key', dest='key_file', | |
| 71 help=('File containing private key and certificates required to ' | |
| 72 'sign the package')), | |
| 73 make_argument( | |
| 74 '-r', '--release', action='store_true', | |
| 75 help='Create a release build'), | |
| 76 make_argument('output_file', nargs='?') | |
| 77 ) | |
| 78 ) | |
| 79 def run_build(base_dir, build, key_file, release, output_file, platform, | |
|
Wladimir Palant
2017/11/17 09:26:10
Nit: Maybe rename this function into build and dro
tlucas
2017/11/17 11:36:44
Done.
| |
| 80 **kwargs): | |
| 81 """ | |
| 82 Create a build. | |
| 89 | 83 |
| 90 global commandsList, commands | 84 Creates an extension build with given file name. If output_file is missing |
| 91 command = Command(handler, name) | 85 a default name will be chosen. |
| 92 commandsList.append(command) | 86 """ |
| 93 commands[name] = command | 87 kwargs = {} |
| 94 for alias in aliases: | 88 if platform in {'chrome', 'gecko'}: |
| 95 commands[alias] = command | 89 import buildtools.packagerChrome as packager |
| 96 return command | 90 elif platform == 'edge': |
| 91 import buildtools.packagerEdge as packager | |
|
Wladimir Palant
2017/11/17 09:26:10
Nit: This is code you merely moved around but cons
tlucas
2017/11/17 11:36:44
Done.
| |
| 92 | |
| 93 kwargs['keyFile'] = key_file | |
| 94 kwargs['outFile'] = output_file | |
| 95 kwargs['releaseBuild'] = release | |
| 96 kwargs['buildNum'] = build | |
| 97 | |
| 98 packager.createBuild(base_dir, type=platform, **kwargs) | |
| 97 | 99 |
| 98 | 100 |
| 99 def splitByLength(string, maxLen): | 101 @argparse_command( |
| 100 parts = [] | 102 name='devenv', platforms={'chrome', 'gecko', 'edge'} |
| 101 currentPart = '' | 103 ) |
| 102 for match in re.finditer(r'\s*(\S+)', string): | 104 def create_devenv(base_dir, platform, **kwargs): |
| 103 if len(match.group(0)) + len(currentPart) < maxLen: | 105 """ |
| 104 currentPart += match.group(0) | 106 Set up a development environment. |
| 105 else: | |
| 106 parts.append(currentPart) | |
| 107 currentPart = match.group(1) | |
| 108 if len(currentPart): | |
| 109 parts.append(currentPart) | |
| 110 return parts | |
| 111 | 107 |
| 112 | 108 Will set up or update the devenv folder as an unpacked extension folder ' |
| 113 def usage(scriptName, type, commandName=None): | 109 for development. |
| 114 if commandName == None: | 110 """ |
| 115 global commandsList | 111 if platform == 'edge': |
| 116 descriptions = [] | |
| 117 for command in commandsList: | |
| 118 if not command.isSupported(type): | |
| 119 continue | |
| 120 commandText = ('%s %s' % (command.name, command.params)).ljust(39) | |
| 121 descriptionParts = splitByLength(command.shortDescription, 29) | |
| 122 descriptions.append(' %s [-t %s] %s %s' % (scriptName, type, comman dText, descriptionParts[0])) | |
| 123 for part in descriptionParts[1:]: | |
| 124 descriptions.append(' %s %s %s %s' % (' ' * len(scriptName ), ' ' * len(type), ' ' * len(commandText), part)) | |
| 125 print '''Usage: | |
| 126 | |
| 127 %(descriptions)s | |
| 128 | |
| 129 For details on a command run: | |
| 130 | |
| 131 %(scriptName)s [-t %(type)s] <command> --help | |
| 132 ''' % { | |
| 133 'scriptName': scriptName, | |
| 134 'type': type, | |
| 135 'descriptions': '\n'.join(descriptions) | |
| 136 } | |
| 137 else: | |
| 138 global commands | |
| 139 command = commands[commandName] | |
| 140 description = '\n'.join(map(lambda s: '\n'.join(splitByLength(s, 80)), c ommand.description.split('\n'))) | |
| 141 options = [] | |
| 142 for descr, short, long, value, types in command.options: | |
| 143 if types != None and type not in types: | |
| 144 continue | |
| 145 if short == None: | |
| 146 shortText = '' | |
| 147 elif value == None: | |
| 148 shortText = '-%s' % short | |
| 149 else: | |
| 150 shortText = '-%s %s' % (short, value) | |
| 151 if long == None: | |
| 152 longText = '' | |
| 153 elif value == None: | |
| 154 longText = '--%s' % long | |
| 155 else: | |
| 156 longText = '--%s=%s' % (long, value) | |
| 157 descrParts = splitByLength(descr, 46) | |
| 158 options.append(' %s %s %s' % (shortText.ljust(11), longText.ljust(1 9), descrParts[0])) | |
| 159 for part in descrParts[1:]: | |
| 160 options.append(' %s %s %s' % (' ' * 11, ' ' * 19, part)) | |
| 161 print '''%(scriptName)s [-t %(type)s] %(name)s %(params)s | |
| 162 | |
| 163 %(description)s | |
| 164 | |
| 165 Options: | |
| 166 %(options)s | |
| 167 ''' % { | |
| 168 'scriptName': scriptName, | |
| 169 'type': type, | |
| 170 'name': command.name, | |
| 171 'params': command.params, | |
| 172 'description': description, | |
| 173 'options': '\n'.join(options) | |
| 174 } | |
| 175 | |
| 176 | |
| 177 def runBuild(baseDir, scriptName, opts, args, type): | |
| 178 kwargs = {} | |
| 179 for option, value in opts: | |
| 180 if option in {'-b', '--build'}: | |
| 181 kwargs['buildNum'] = value | |
| 182 if type != 'gecko' and not kwargs['buildNum'].isdigit(): | |
| 183 raise TypeError('Build number must be numerical') | |
| 184 elif option in {'-k', '--key'}: | |
| 185 kwargs['keyFile'] = value | |
| 186 elif option in {'-r', '--release'}: | |
| 187 kwargs['releaseBuild'] = True | |
| 188 if len(args) > 0: | |
| 189 kwargs['outFile'] = args[0] | |
| 190 | |
| 191 if type in {'chrome', 'gecko'}: | |
| 192 import buildtools.packagerChrome as packager | |
| 193 elif type == 'edge': | |
| 194 import buildtools.packagerEdge as packager | |
| 195 | |
| 196 packager.createBuild(baseDir, type=type, **kwargs) | |
| 197 | |
| 198 | |
| 199 def createDevEnv(baseDir, scriptName, opts, args, type): | |
| 200 if type == 'edge': | |
| 201 import buildtools.packagerEdge as packager | 112 import buildtools.packagerEdge as packager |
| 202 else: | 113 else: |
| 203 import buildtools.packagerChrome as packager | 114 import buildtools.packagerChrome as packager |
| 204 | 115 |
| 205 file = StringIO() | 116 file = StringIO() |
| 206 packager.createBuild(baseDir, type=type, outFile=file, devenv=True, releaseB uild=True) | 117 packager.createBuild(base_dir, type=platform, outFile=file, devenv=True, |
| 118 releaseBuild=True) | |
| 207 | 119 |
| 208 from buildtools.packager import getDevEnvPath | 120 from buildtools.packager import getDevEnvPath |
| 209 devenv_dir = getDevEnvPath(baseDir, type) | 121 devenv_dir = getDevEnvPath(base_dir, platform) |
| 210 | 122 |
| 211 shutil.rmtree(devenv_dir, ignore_errors=True) | 123 shutil.rmtree(devenv_dir, ignore_errors=True) |
| 212 | 124 |
| 213 file.seek(0) | 125 file.seek(0) |
| 214 with ZipFile(file, 'r') as zip_file: | 126 with ZipFile(file, 'r') as zip_file: |
| 215 zip_file.extractall(devenv_dir) | 127 zip_file.extractall(devenv_dir) |
| 216 | 128 |
| 217 | 129 |
| 218 def readLocaleConfig(baseDir, type, metadata): | 130 def read_locale_config(base_dir, platform, metadata): |
| 219 if type != 'generic': | 131 if platform != 'generic': |
| 220 import buildtools.packagerChrome as packager | 132 import buildtools.packagerChrome as packager |
| 221 localeDir = os.path.join(baseDir, '_locales') | 133 locale_dir = os.path.join(base_dir, '_locales') |
| 222 localeConfig = { | 134 locale_config = { |
| 223 'default_locale': packager.defaultLocale, | 135 'default_locale': packager.defaultLocale, |
| 224 } | 136 } |
| 225 else: | 137 else: |
| 226 localeDir = os.path.join( | 138 locale_dir = os.path.join( |
| 227 baseDir, *metadata.get('locales', 'base_path').split('/') | 139 base_dir, *metadata.get('locales', 'base_path').split('/') |
| 228 ) | 140 ) |
| 229 localeConfig = { | 141 locale_config = { |
| 230 'default_locale': metadata.get('locales', 'default_locale') | 142 'default_locale': metadata.get('locales', 'default_locale') |
| 231 } | 143 } |
| 232 | 144 |
| 233 localeConfig['base_path'] = localeDir | 145 locale_config['base_path'] = locale_dir |
| 234 | 146 |
| 235 locales = [(locale.replace('_', '-'), os.path.join(localeDir, locale)) | 147 locales = [(locale.replace('_', '-'), os.path.join(locale_dir, locale)) |
| 236 for locale in os.listdir(localeDir)] | 148 for locale in os.listdir(locale_dir)] |
| 237 localeConfig['locales'] = dict(locales) | 149 locale_config['locales'] = dict(locales) |
| 238 | 150 |
| 239 return localeConfig | 151 return locale_config |
| 240 | 152 |
| 241 | 153 |
| 242 def setupTranslations(baseDir, scriptName, opts, args, type): | 154 project_key_argument = make_argument( |
| 243 if len(args) < 1: | 155 'project_key', help='The crowdin project key (required).', nargs='?' |
|
Wladimir Palant
2017/11/17 09:26:10
This is a required parameter, why not nargs=1? Thi
tlucas
2017/11/17 11:36:44
nargs=1 would produce a list of one value, leaving
| |
| 244 print 'Project key is required to update translation master files.' | 156 ) |
| 245 usage(scriptName, type, 'setuptrans') | |
| 246 return | |
| 247 | 157 |
| 248 key = args[0] | |
| 249 | 158 |
| 159 @argparse_command( | |
| 160 name='setuptrans', platforms=KNOWN_PLATFORMS, | |
| 161 arguments=(project_key_argument, ) | |
| 162 ) | |
| 163 def setup_translations(base_dir, project_key, platform, **kwargs): | |
| 164 """ | |
| 165 Set up translation languages. | |
| 166 | |
| 167 Set up translation languages for the project on crowdin.net. | |
|
Wladimir Palant
2017/11/17 09:26:10
Here and for the commands below: it's crowdin.com.
tlucas
2017/11/17 11:36:44
Done.
| |
| 168 """ | |
| 250 from buildtools.packager import readMetadata | 169 from buildtools.packager import readMetadata |
| 251 metadata = readMetadata(baseDir, type) | 170 metadata = readMetadata(base_dir, platform) |
| 252 | 171 |
| 253 basename = metadata.get('general', 'basename') | 172 basename = metadata.get('general', 'basename') |
| 254 localeConfig = readLocaleConfig(baseDir, type, metadata) | 173 locale_config = read_locale_config(base_dir, platform, metadata) |
| 255 | 174 |
| 256 import buildtools.localeTools as localeTools | 175 import buildtools.localeTools as localeTools |
| 257 localeTools.setupTranslations(localeConfig, basename, key) | 176 localeTools.setupTranslations(locale_config, basename, project_key) |
| 258 | 177 |
| 259 | 178 |
| 260 def updateTranslationMaster(baseDir, scriptName, opts, args, type): | 179 @argparse_command( |
| 261 if len(args) < 1: | 180 name='translate', platforms=KNOWN_PLATFORMS, |
| 262 print 'Project key is required to update translation master files.' | 181 arguments=(project_key_argument, ) |
| 263 usage(scriptName, type, 'translate') | 182 ) |
| 264 return | 183 def update_translation_master(base_dir, project_key, platform, **kwargs): |
| 184 """ | |
| 185 Update translation master files. | |
| 265 | 186 |
| 266 key = args[0] | 187 Update the translation master files in the project on crowdin.net. |
| 267 | 188 """ |
| 268 from buildtools.packager import readMetadata | 189 from buildtools.packager import readMetadata |
| 269 metadata = readMetadata(baseDir, type) | 190 metadata = readMetadata(base_dir, platform) |
| 270 | 191 |
| 271 basename = metadata.get('general', 'basename') | 192 basename = metadata.get('general', 'basename') |
| 272 localeConfig = readLocaleConfig(baseDir, type, metadata) | 193 locale_config = read_locale_config(base_dir, platform, metadata) |
| 273 | 194 |
| 274 defaultLocaleDir = os.path.join(localeConfig['base_path'], | 195 default_locale_dir = os.path.join(locale_config['base_path'], |
| 275 localeConfig['default_locale']) | 196 locale_config['default_locale']) |
| 276 | 197 |
| 277 import buildtools.localeTools as localeTools | 198 import buildtools.localeTools as localeTools |
| 278 localeTools.updateTranslationMaster(localeConfig, metadata, defaultLocaleDir , | 199 localeTools.updateTranslationMaster(locale_config, metadata, |
| 279 basename, key) | 200 default_locale_dir, basename, |
| 201 project_key) | |
| 280 | 202 |
| 281 | 203 |
| 282 def uploadTranslations(baseDir, scriptName, opts, args, type): | 204 @argparse_command( |
| 283 if len(args) < 1: | 205 name='uploadtrans', platforms=KNOWN_PLATFORMS, |
| 284 print 'Project key is required to upload existing translations.' | 206 arguments=(project_key_argument, ) |
| 285 usage(scriptName, type, 'uploadtrans') | 207 ) |
| 286 return | 208 def upload_translations(base_dir, project_key, platform, **kwargs): |
| 209 """ | |
| 210 Upload existing translations. | |
| 287 | 211 |
| 288 key = args[0] | 212 Upload already existing translations to the project on crowdin.net. |
| 289 | 213 """ |
| 290 from buildtools.packager import readMetadata | 214 from buildtools.packager import readMetadata |
| 291 metadata = readMetadata(baseDir, type) | 215 metadata = readMetadata(base_dir, platform) |
| 292 | 216 |
| 293 basename = metadata.get('general', 'basename') | 217 basename = metadata.get('general', 'basename') |
| 294 localeConfig = readLocaleConfig(baseDir, type, metadata) | 218 locale_config = read_locale_config(base_dir, platform, metadata) |
| 295 | 219 |
| 296 import buildtools.localeTools as localeTools | 220 import buildtools.localeTools as localeTools |
| 297 for locale, localeDir in localeConfig['locales'].iteritems(): | 221 for locale, locale_dir in locale_config['locales'].iteritems(): |
| 298 if locale != localeConfig['default_locale'].replace('_', '-'): | 222 if locale != locale_config['default_locale'].replace('_', '-'): |
| 299 localeTools.uploadTranslations(localeConfig, metadata, localeDir, lo cale, | 223 localeTools.uploadTranslations(locale_config, metadata, locale_dir, |
| 300 basename, key) | 224 locale, basename, project_key) |
| 301 | 225 |
| 302 | 226 |
| 303 def getTranslations(baseDir, scriptName, opts, args, type): | 227 @argparse_command( |
| 304 if len(args) < 1: | 228 name='gettranslations', platforms=KNOWN_PLATFORMS, |
| 305 print 'Project key is required to update translation master files.' | 229 arguments=(project_key_argument, ) |
| 306 usage(scriptName, type, 'translate') | 230 ) |
| 307 return | 231 def get_translations(base_dir, project_key, platform, **kwargs): |
| 232 """ | |
| 233 Download translation updates. | |
| 308 | 234 |
| 309 key = args[0] | 235 Download updated translations from crowdin.net. |
| 310 | 236 """ |
| 311 from buildtools.packager import readMetadata | 237 from buildtools.packager import readMetadata |
| 312 metadata = readMetadata(baseDir, type) | 238 metadata = readMetadata(base_dir, platform) |
| 313 | 239 |
| 314 basename = metadata.get('general', 'basename') | 240 basename = metadata.get('general', 'basename') |
| 315 localeConfig = readLocaleConfig(baseDir, type, metadata) | 241 locale_config = read_locale_config(base_dir, platform, metadata) |
| 316 | 242 |
| 317 import buildtools.localeTools as localeTools | 243 import buildtools.localeTools as localeTools |
| 318 localeTools.getTranslations(localeConfig, basename, key) | 244 localeTools.getTranslations(locale_config, basename, project_key) |
| 319 | 245 |
| 320 | 246 |
| 321 def generateDocs(baseDir, scriptName, opts, args, type): | 247 @argparse_command( |
| 322 if len(args) == 0: | 248 name='docs', platforms={'chrome'}, |
| 323 print 'No target directory specified for the documentation' | 249 arguments=( |
| 324 usage(scriptName, type, 'docs') | 250 make_argument('target_dir'), |
| 325 return | 251 make_argument('-q', '--quiet', help='Suppress JsDoc output'), |
| 326 targetDir = args[0] | 252 ) |
| 253 ) | |
| 254 def generate_docs(base_dir, target_dir, quiet, platform, **kwargs): | |
| 255 """ | |
| 256 Generate documentation (requires node.js). | |
| 327 | 257 |
| 328 source_dir = os.path.join(baseDir, 'lib') | 258 Generate documentation files and write them into the specified directory. |
| 259 """ | |
| 260 source_dir = os.path.join(base_dir, 'lib') | |
| 329 sources = [source_dir] | 261 sources = [source_dir] |
| 330 | 262 |
| 331 # JSDoc struggles wih huge objects: https://github.com/jsdoc3/jsdoc/issues/9 76 | 263 # JSDoc struggles wih huge objects: |
| 332 if type == 'chrome': | 264 # https://github.com/jsdoc3/jsdoc/issues/976 |
| 333 sources = [os.path.join(source_dir, filename) for filename in os.listdir (source_dir) if filename != 'publicSuffixList.js'] | 265 if platform == 'chrome': |
|
Wladimir Palant
2017/11/17 09:26:10
Nit: This check can be removed, we have no other p
tlucas
2017/11/17 11:36:44
Done.
| |
| 266 sources = [os.path.join(source_dir, filename) | |
| 267 for filename in os.listdir(source_dir) | |
| 268 if filename != 'publicSuffixList.js'] | |
| 334 | 269 |
| 335 buildtools_path = os.path.dirname(__file__) | 270 buildtools_path = os.path.dirname(__file__) |
| 336 config = os.path.join(buildtools_path, 'jsdoc.conf') | 271 config = os.path.join(buildtools_path, 'jsdoc.conf') |
| 337 | 272 |
| 338 command = ['npm', 'run-script', 'jsdoc', '--', '--destination', targetDir, | 273 command = ['npm', 'run-script', 'jsdoc', '--', '--destination', target_dir, |
| 339 '--configure', config] + sources | 274 '--configure', config] + sources |
| 340 if any(opt in {'-q', '--quiet'} for opt, _ in opts): | 275 if quiet: |
| 341 process = subprocess.Popen(command, stdout=subprocess.PIPE, | 276 process = subprocess.Popen(command, stdout=subprocess.PIPE, |
| 342 stderr=subprocess.PIPE, cwd=buildtools_path) | 277 stderr=subprocess.PIPE, cwd=buildtools_path) |
| 343 stderr = process.communicate()[1] | 278 stderr = process.communicate()[1] |
| 344 retcode = process.poll() | 279 retcode = process.poll() |
| 345 if retcode: | 280 if retcode: |
| 346 sys.stderr.write(stderr) | 281 sys.stderr.write(stderr) |
| 347 raise subprocess.CalledProcessError(command, retcode) | 282 raise subprocess.CalledProcessError(command, retcode) |
| 348 else: | 283 else: |
| 349 subprocess.check_call(command, cwd=buildtools_path) | 284 subprocess.check_call(command, cwd=buildtools_path) |
| 350 | 285 |
| 351 | 286 |
| 352 def runReleaseAutomation(baseDir, scriptName, opts, args, type): | 287 def valid_version_format(value): |
| 353 keyFile = None | 288 if re.search(r'[^\d\.]', value): |
| 354 downloadsRepo = os.path.join(baseDir, '..', 'downloads') | 289 raise argparse.ArgumentTypeError('Wrong version number format') |
| 355 for option, value in opts: | |
| 356 if option in {'-k', '--key'}: | |
| 357 keyFile = value | |
| 358 elif option in {'-d', '--downloads'}: | |
| 359 downloadsRepo = value | |
| 360 | 290 |
| 361 if len(args) == 0: | 291 return value |
| 362 print 'No version number specified for the release' | |
| 363 usage(scriptName, type, 'release') | |
| 364 return | |
| 365 version = args[0] | |
| 366 if re.search(r'[^\d\.]', version): | |
| 367 print 'Wrong version number format' | |
| 368 usage(scriptName, type, 'release') | |
| 369 return | |
| 370 | 292 |
| 371 if type == 'chrome' and keyFile is None: | 293 |
| 372 print >>sys.stderr, 'Error: you must specify a key file for this release ' | 294 @argparse_command( |
| 373 usage(scriptName, type, 'release') | 295 name='release', platforms={'chrome', 'edge'}, |
| 374 return | 296 arguments=( |
| 297 make_argument( | |
| 298 '-k', '--key', dest='key_file', | |
| 299 help=('File containing private key and certificates required to ' | |
| 300 'sign the release.')), | |
| 301 make_argument( | |
| 302 '-d', '--downloads-repository', dest='download_repository', | |
|
Wladimir Palant
2017/11/17 09:26:10
Is the naming download_repository rather than down
tlucas
2017/11/17 11:36:44
No, this was not intentional. Done.
| |
| 303 default='../downloads', | |
|
Wladimir Palant
2017/11/17 09:26:10
Please use os.path.join() here. Also, the default
tlucas
2017/11/17 11:36:44
Done.
| |
| 304 help=('Directory containing downloads repository (if omitted ' | |
| 305 '../downloads is assumed)')), | |
| 306 make_argument( | |
| 307 'version', nargs='?', help='Version number of the release', | |
|
Wladimir Palant
2017/11/17 09:26:10
This parameter isn't optional.
tlucas
2017/11/17 11:36:44
Done.
| |
| 308 type=valid_version_format) | |
| 309 ) | |
| 310 ) | |
| 311 def run_release_automation(base_dir, download_repository, key_file, platform, | |
| 312 version, **kwargs): | |
| 313 """ | |
| 314 Run release automation. | |
| 315 | |
| 316 Note: If you are not the project owner then you probably don't want to run | |
| 317 this! | |
| 318 | |
| 319 Run release automation: create downloads for the new version, tag source | |
| 320 code repository as well as downloads and buildtools repository. | |
| 321 """ | |
| 322 if platform == 'chrome' and key_file is None: | |
| 323 raise ValueError('You must specify a key file for this release') | |
| 375 | 324 |
| 376 import buildtools.releaseAutomation as releaseAutomation | 325 import buildtools.releaseAutomation as releaseAutomation |
| 377 releaseAutomation.run(baseDir, type, version, keyFile, downloadsRepo) | 326 releaseAutomation.run(base_dir, platform, version, key_file, |
| 327 download_repository) | |
| 378 | 328 |
| 379 | 329 |
| 380 def updatePSL(baseDir, scriptName, opts, args, type): | 330 @argparse_command(name='updatepsl', platforms={'chrome'}) |
| 331 def update_psl(base_dir, **kwargs): | |
| 332 """Update Public Suffix List. | |
| 333 | |
| 334 Downloads Public Suffix List (see http://publicsuffix.org/) and generates | |
| 335 lib/publicSuffixList.js from it. | |
| 336 """ | |
| 381 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater | 337 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater |
| 382 publicSuffixListUpdater.updatePSL(baseDir) | 338 publicSuffixListUpdater.updatePSL(base_dir) |
| 383 | 339 |
| 384 | 340 |
| 385 with addCommand(lambda baseDir, scriptName, opts, args, type: usage(scriptName, type), ('help', '-h', '--help')) as command: | 341 def process_args(base_dir, *args): |
| 386 command.shortDescription = 'Show this message' | 342 parser.set_defaults(base_dir=base_dir) |
| 387 | 343 |
| 388 with addCommand(runBuild, 'build') as command: | 344 # If no args are provided, this module is run directly from the command |
| 389 command.shortDescription = 'Create a build' | 345 # line. argparse will take care of consuming sys.argv. |
| 390 command.description = 'Creates an extension build with given file name. If o utput_file is missing a default name will be chosen.' | 346 arguments = parser.parse_args(args if len(args) > 0 else None) |
| 391 command.params = '[options] [output_file]' | |
| 392 command.addOption('Use given build number (if omitted the build number will be retrieved from Mercurial)', short='b', long='build', value='num') | |
| 393 command.addOption('File containing private key and certificates required to sign the package', short='k', long='key', value='file', types={'chrome'}) | |
| 394 command.addOption('Create a release build', short='r', long='release') | |
| 395 command.supportedTypes = {'gecko', 'chrome', 'edge'} | |
| 396 | 347 |
| 397 with addCommand(createDevEnv, 'devenv') as command: | 348 function = arguments.function |
| 398 command.shortDescription = 'Set up a development environment' | 349 del arguments.function |
| 399 command.description = 'Will set up or update the devenv folder as an unpacke d extension folder for development.' | 350 function(**vars(arguments)) |
| 400 command.supportedTypes = {'gecko', 'chrome', 'edge'} | |
| 401 | |
| 402 with addCommand(setupTranslations, 'setuptrans') as command: | |
| 403 command.shortDescription = 'Sets up translation languages' | |
| 404 command.description = 'Sets up translation languages for the project on crow din.net.' | |
| 405 command.params = '[options] project-key' | |
| 406 | |
| 407 with addCommand(updateTranslationMaster, 'translate') as command: | |
| 408 command.shortDescription = 'Updates translation master files' | |
| 409 command.description = 'Updates the translation master files in the project o n crowdin.net.' | |
| 410 command.params = '[options] project-key' | |
| 411 | |
| 412 with addCommand(uploadTranslations, 'uploadtrans') as command: | |
| 413 command.shortDescription = 'Uploads existing translations' | |
| 414 command.description = 'Uploads already existing translations to the project on crowdin.net.' | |
| 415 command.params = '[options] project-key' | |
| 416 | |
| 417 with addCommand(getTranslations, 'gettranslations') as command: | |
| 418 command.shortDescription = 'Downloads translation updates' | |
| 419 command.description = 'Downloads updated translations from crowdin.net.' | |
| 420 command.params = '[options] project-key' | |
| 421 | |
| 422 with addCommand(generateDocs, 'docs') as command: | |
| 423 command.shortDescription = 'Generate documentation (requires node.js)' | |
| 424 command.description = ('Generate documentation files and write them into ' | |
| 425 'the specified directory.') | |
| 426 command.addOption('Suppress JsDoc output', short='q', long='quiet') | |
| 427 command.params = '[options] <directory>' | |
| 428 command.supportedTypes = {'chrome'} | |
| 429 | |
| 430 with addCommand(runReleaseAutomation, 'release') as command: | |
| 431 command.shortDescription = 'Run release automation' | |
| 432 command.description = 'Note: If you are not the project owner then you ' "probably don't want to run this!\n\n" 'Runs release automation: crea tes downloads for the new version, tags ' 'source code repository as well as downloads and buildtools repository.' | |
| 433 command.addOption('File containing private key and certificates required to sign the release.', short='k', long='key', value='file', types={'chrome', 'edge' }) | |
| 434 command.addOption('Directory containing downloads repository (if omitted ../ downloads is assumed)', short='d', long='downloads', value='dir') | |
| 435 command.params = '[options] <version>' | |
| 436 command.supportedTypes = {'chrome', 'edge'} | |
| 437 | |
| 438 with addCommand(updatePSL, 'updatepsl') as command: | |
| 439 command.shortDescription = 'Updates Public Suffix List' | |
| 440 command.description = 'Downloads Public Suffix List (see http://publicsuffix .org/) and generates lib/publicSuffixList.js from it.' | |
| 441 command.supportedTypes = {'chrome'} | |
| 442 | |
| 443 | |
| 444 def getType(baseDir, scriptName, args): | |
| 445 # Look for an explicit type parameter (has to be the first parameter) | |
| 446 if len(args) >= 2 and args[0] == '-t': | |
| 447 type = args[1] | |
| 448 del args[1] | |
| 449 del args[0] | |
| 450 if type not in knownTypes: | |
| 451 print ''' | |
| 452 Unknown type %s specified, supported types are: %s | |
| 453 ''' % (type, ', '.join(knownTypes)) | |
| 454 return None | |
| 455 return type | |
| 456 | |
| 457 # Try to guess repository type | |
| 458 types = [] | |
| 459 for t in knownTypes: | |
| 460 if os.path.exists(os.path.join(baseDir, 'metadata.%s' % t)): | |
| 461 types.append(t) | |
| 462 | |
| 463 if len(types) == 1: | |
| 464 return types[0] | |
| 465 elif len(types) > 1: | |
| 466 print ''' | |
| 467 Ambiguous repository type, please specify -t parameter explicitly, e.g. | |
| 468 %s -t %s build | |
| 469 ''' % (scriptName, types[0]) | |
| 470 return None | |
| 471 else: | |
| 472 print ''' | |
| 473 No metadata file found in this repository, a metadata file like | |
| 474 metadata.* is required. | |
| 475 ''' | |
| 476 return None | |
| 477 | |
| 478 | |
| 479 def processArgs(baseDir, args): | |
| 480 global commands | |
| 481 | |
| 482 scriptName = os.path.basename(args[0]) | |
| 483 args = args[1:] | |
| 484 type = getType(baseDir, scriptName, args) | |
| 485 if type == None: | |
| 486 return | |
| 487 | |
| 488 if len(args) == 0: | |
| 489 args = ['build'] | |
| 490 print ''' | |
| 491 No command given, assuming "build". For a list of commands run: | |
| 492 | |
| 493 %s help | |
| 494 ''' % scriptName | |
| 495 | |
| 496 command = args[0] | |
| 497 if command in commands: | |
| 498 if commands[command].isSupported(type): | |
| 499 try: | |
| 500 opts, args = commands[command].parseArgs(type, args[1:]) | |
| 501 except GetoptError as e: | |
| 502 print str(e) | |
| 503 usage(scriptName, type, command) | |
| 504 sys.exit(2) | |
| 505 for option, value in opts: | |
| 506 if option in {'-h', '--help'}: | |
| 507 usage(scriptName, type, command) | |
| 508 sys.exit() | |
| 509 commands[command](baseDir, scriptName, opts, args, type) | |
| 510 else: | |
| 511 print 'Command %s is not supported for this application type' % comm and | |
| 512 usage(scriptName, type) | |
| 513 else: | |
| 514 print 'Command %s is unrecognized' % command | |
| 515 usage(scriptName, type) | |
| OLD | NEW |