Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: build.py

Issue 29609559: Issue 6021 - Refactoring build.py (Closed) Base URL: https://hg.adblockplus.org/buildtools/file/79688f4a4aff
Patch Set: Only construct subcommands if they would actually be available Created Nov. 17, 2017, 1:30 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tests/test_packagerWebExt.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
6 import logging
5 import os 7 import os
8 import re
9 import shutil
10 import subprocess
6 import sys 11 import sys
7 import re 12 from functools import partial
8 import subprocess
9 import shutil
10 from getopt import getopt, GetoptError
11 from StringIO import StringIO 13 from StringIO import StringIO
12 from zipfile import ZipFile 14 from zipfile import ZipFile
13 15
14 knownTypes = {'gecko', 'chrome', 'generic', 'edge'} 16 KNOWN_PLATFORMS = {'chrome', 'gecko', 'edge', 'generic'}
17
18 MAIN_PARSER = argparse.ArgumentParser(
19 description=__doc__,
20 formatter_class=argparse.RawDescriptionHelpFormatter)
21
22 SUB_PARSERS = MAIN_PARSER.add_subparsers(title='Commands', dest='action',
23 metavar='[command]')
24
25 ALL_COMMANDS = []
15 26
16 27
17 class Command(object): 28 def make_argument(*args, **kwargs):
18 name = property(lambda self: self._name) 29 def _make_argument(*args, **kwargs):
19 shortDescription = property( 30 parser = kwargs.pop('parser')
20 lambda self: self._shortDescription, 31 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 32
37 def __init__(self, handler, name): 33 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 34
79 35
80 commandsList = [] 36 def argparse_command(valid_platforms=None, arguments=()):
81 commands = {} 37 def wrapper(func):
38 def func_wrapper(*args, **kwargs):
39 return func(*args, **kwargs)
40
41 short_desc, long_desc = func.__doc__.split(os.linesep + os.linesep, 1)
42
43 ALL_COMMANDS.append({
44 'name': func.__name__,
45 'description': long_desc,
46 'help_text': short_desc,
47 'valid_platforms': valid_platforms or KNOWN_PLATFORMS,
48 'function': func,
49 'arguments': arguments,
50 })
51 return func_wrapper
52
53 return wrapper
82 54
83 55
84 def addCommand(handler, name): 56 def make_subcommand(name, description, help_text, function, arguments):
85 if isinstance(name, basestring): 57 new_parser = SUB_PARSERS.add_parser(
86 aliases = () 58 name, description=description, help=help_text,
87 else: 59 formatter_class=argparse.RawDescriptionHelpFormatter,
88 name, aliases = (name[0], name[1:]) 60 )
89 61
90 global commandsList, commands 62 for argument in arguments:
91 command = Command(handler, name) 63 argument(parser=new_parser)
92 commandsList.append(command) 64
93 commands[name] = command 65 new_parser.set_defaults(function=function)
94 for alias in aliases: 66 return new_parser
95 commands[alias] = command
96 return command
97 67
98 68
99 def splitByLength(string, maxLen): 69 def build_available_subcommands(base_dir):
100 parts = [] 70 """Build subcommands, which are available for the repository in base_dir.
101 currentPart = '' 71
102 for match in re.finditer(r'\s*(\S+)', string): 72 Search 'base_dir' for existing metadata.<type> files and make <type> an
103 if len(match.group(0)) + len(currentPart) < maxLen: 73 avaible choice for the subcommands, intersected with their respective valid
104 currentPart += match.group(0) 74 platforms.
105 else: 75
106 parts.append(currentPart) 76 If no valid platform is found for a subcommand, it get's omitted.
107 currentPart = match.group(1) 77 """
108 if len(currentPart): 78 if build_available_subcommands._result is not None:
109 parts.append(currentPart) 79 # Tests might run this code multiple times, make sure the collection
110 return parts 80 # of platforms is only run once.
81 return build_available_subcommands._result
82
83 types = set()
84 for p in KNOWN_PLATFORMS:
85 if os.path.exists(os.path.join(base_dir, 'metadata.' + p)):
86 types.add(p)
87
88 if len(types) == 0:
89 logging.error('No metadata file found in this repository. Expecting '
90 'one or more of {} to be present.'.format(
91 ', '.join('metadata.' + p for p in KNOWN_PLATFORMS)))
92 build_available_subcommands._result = False
93 return False
94
95 for command_params in ALL_COMMANDS:
96 platforms = types.intersection(command_params.pop('valid_platforms'))
97 if len(platforms) > 1:
98 command_params['arguments'] += (
99 make_argument('-t', '--type', dest='platform',
100 choices=platforms),
101 )
102 make_subcommand(**command_params)
103 elif len(platforms) == 1:
104 sub_parser = make_subcommand(**command_params)
105 sub_parser.set_defaults(platform=platforms.pop())
106
107 build_available_subcommands._result = True
108 return True
111 109
112 110
113 def usage(scriptName, type, commandName=None): 111 build_available_subcommands._result = None
114 if commandName == None:
115 global commandsList
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 112
176 113
177 def runBuild(baseDir, scriptName, opts, args, type): 114 @argparse_command(
115 valid_platforms={'chrome', 'gecko', 'edge'},
116 arguments=(
117 make_argument(
118 '-b', '--build-num', dest='build_num',
119 help=('Use given build number (if omitted the build number will '
120 'be retrieved from Mercurial)')),
121 make_argument(
122 '-k', '--key', dest='key_file',
123 help=('File containing private key and certificates required to '
124 'sign the package')),
125 make_argument(
126 '-r', '--release', action='store_true',
127 help='Create a release build'),
128 make_argument('output_file', nargs='?')
129 )
130 )
131 def build(base_dir, build_num, key_file, release, output_file, platform,
132 **kwargs):
133 """
134 Create a build.
135
136 Creates an extension build with given file name. If output_file is missing
137 a default name will be chosen.
138 """
178 kwargs = {} 139 kwargs = {}
179 for option, value in opts: 140 if platform == 'edge':
180 if option in {'-b', '--build'}: 141 import buildtools.packagerEdge as packager
181 kwargs['buildNum'] = value 142 else:
182 if type != 'gecko' and not kwargs['buildNum'].isdigit(): 143 import buildtools.packagerChrome as packager
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 144
191 if type in {'chrome', 'gecko'}: 145 kwargs['keyFile'] = key_file
192 import buildtools.packagerChrome as packager 146 kwargs['outFile'] = output_file
193 elif type == 'edge': 147 kwargs['releaseBuild'] = release
194 import buildtools.packagerEdge as packager 148 kwargs['buildNum'] = build_num
195 149
196 packager.createBuild(baseDir, type=type, **kwargs) 150 packager.createBuild(base_dir, type=platform, **kwargs)
197 151
198 152
199 def createDevEnv(baseDir, scriptName, opts, args, type): 153 @argparse_command(
200 if type == 'edge': 154 valid_platforms={'chrome', 'gecko', 'edge'}
155 )
156 def devenv(base_dir, platform, **kwargs):
157 """
158 Set up a development environment.
159
160 Will set up or update the devenv folder as an unpacked extension folder '
161 for development.
162 """
163 if platform == 'edge':
201 import buildtools.packagerEdge as packager 164 import buildtools.packagerEdge as packager
202 else: 165 else:
203 import buildtools.packagerChrome as packager 166 import buildtools.packagerChrome as packager
204 167
205 file = StringIO() 168 file = StringIO()
206 packager.createBuild(baseDir, type=type, outFile=file, devenv=True, releaseB uild=True) 169 packager.createBuild(base_dir, type=platform, outFile=file, devenv=True,
170 releaseBuild=True)
207 171
208 from buildtools.packager import getDevEnvPath 172 from buildtools.packager import getDevEnvPath
209 devenv_dir = getDevEnvPath(baseDir, type) 173 devenv_dir = getDevEnvPath(base_dir, platform)
210 174
211 shutil.rmtree(devenv_dir, ignore_errors=True) 175 shutil.rmtree(devenv_dir, ignore_errors=True)
212 176
213 file.seek(0) 177 file.seek(0)
214 with ZipFile(file, 'r') as zip_file: 178 with ZipFile(file, 'r') as zip_file:
215 zip_file.extractall(devenv_dir) 179 zip_file.extractall(devenv_dir)
216 180
217 181
218 def readLocaleConfig(baseDir, type, metadata): 182 def read_locale_config(base_dir, platform, metadata):
219 if type != 'generic': 183 if platform != 'generic':
220 import buildtools.packagerChrome as packager 184 import buildtools.packagerChrome as packager
221 localeDir = os.path.join(baseDir, '_locales') 185 locale_dir = os.path.join(base_dir, '_locales')
222 localeConfig = { 186 locale_config = {
223 'default_locale': packager.defaultLocale, 187 'default_locale': packager.defaultLocale,
224 } 188 }
225 else: 189 else:
226 localeDir = os.path.join( 190 locale_dir = os.path.join(
227 baseDir, *metadata.get('locales', 'base_path').split('/') 191 base_dir, *metadata.get('locales', 'base_path').split('/')
228 ) 192 )
229 localeConfig = { 193 locale_config = {
230 'default_locale': metadata.get('locales', 'default_locale') 194 'default_locale': metadata.get('locales', 'default_locale')
231 } 195 }
232 196
233 localeConfig['base_path'] = localeDir 197 locale_config['base_path'] = locale_dir
234 198
235 locales = [(locale.replace('_', '-'), os.path.join(localeDir, locale)) 199 locales = [(locale.replace('_', '-'), os.path.join(locale_dir, locale))
236 for locale in os.listdir(localeDir)] 200 for locale in os.listdir(locale_dir)]
237 localeConfig['locales'] = dict(locales) 201 locale_config['locales'] = dict(locales)
238 202
239 return localeConfig 203 return locale_config
240 204
241 205
242 def setupTranslations(baseDir, scriptName, opts, args, type): 206 project_key_argument = make_argument(
243 if len(args) < 1: 207 'project_key', help='The crowdin project key.'
244 print 'Project key is required to update translation master files.' 208 )
245 usage(scriptName, type, 'setuptrans')
246 return
247 209
248 key = args[0]
249 210
211 @argparse_command(
212 arguments=(project_key_argument, )
213 )
214 def setuptrans(base_dir, project_key, platform, **kwargs):
215 """
216 Set up translation languages.
217
218 Set up translation languages for the project on crowdin.com.
219 """
250 from buildtools.packager import readMetadata 220 from buildtools.packager import readMetadata
251 metadata = readMetadata(baseDir, type) 221 metadata = readMetadata(base_dir, platform)
252 222
253 basename = metadata.get('general', 'basename') 223 basename = metadata.get('general', 'basename')
254 localeConfig = readLocaleConfig(baseDir, type, metadata) 224 locale_config = read_locale_config(base_dir, platform, metadata)
255 225
256 import buildtools.localeTools as localeTools 226 import buildtools.localeTools as localeTools
257 localeTools.setupTranslations(localeConfig, basename, key) 227 localeTools.setupTranslations(locale_config, basename, project_key)
258 228
259 229
260 def updateTranslationMaster(baseDir, scriptName, opts, args, type): 230 @argparse_command(
261 if len(args) < 1: 231 arguments=(project_key_argument, )
262 print 'Project key is required to update translation master files.' 232 )
263 usage(scriptName, type, 'translate') 233 def translate(base_dir, project_key, platform, **kwargs):
264 return 234 """
235 Update translation master files.
265 236
266 key = args[0] 237 Update the translation master files in the project on crowdin.com.
267 238 """
268 from buildtools.packager import readMetadata 239 from buildtools.packager import readMetadata
269 metadata = readMetadata(baseDir, type) 240 metadata = readMetadata(base_dir, platform)
270 241
271 basename = metadata.get('general', 'basename') 242 basename = metadata.get('general', 'basename')
272 localeConfig = readLocaleConfig(baseDir, type, metadata) 243 locale_config = read_locale_config(base_dir, platform, metadata)
273 244
274 defaultLocaleDir = os.path.join(localeConfig['base_path'], 245 default_locale_dir = os.path.join(locale_config['base_path'],
275 localeConfig['default_locale']) 246 locale_config['default_locale'])
276 247
277 import buildtools.localeTools as localeTools 248 import buildtools.localeTools as localeTools
278 localeTools.updateTranslationMaster(localeConfig, metadata, defaultLocaleDir , 249 localeTools.updateTranslationMaster(locale_config, metadata,
279 basename, key) 250 default_locale_dir, basename,
251 project_key)
280 252
281 253
282 def uploadTranslations(baseDir, scriptName, opts, args, type): 254 @argparse_command(
283 if len(args) < 1: 255 arguments=(project_key_argument, )
284 print 'Project key is required to upload existing translations.' 256 )
285 usage(scriptName, type, 'uploadtrans') 257 def uploadtrans(base_dir, project_key, platform, **kwargs):
286 return 258 """
259 Upload existing translations.
287 260
288 key = args[0] 261 Upload already existing translations to the project on crowdin.com.
289 262 """
290 from buildtools.packager import readMetadata 263 from buildtools.packager import readMetadata
291 metadata = readMetadata(baseDir, type) 264 metadata = readMetadata(base_dir, platform)
292 265
293 basename = metadata.get('general', 'basename') 266 basename = metadata.get('general', 'basename')
294 localeConfig = readLocaleConfig(baseDir, type, metadata) 267 locale_config = read_locale_config(base_dir, platform, metadata)
295 268
296 import buildtools.localeTools as localeTools 269 import buildtools.localeTools as localeTools
297 for locale, localeDir in localeConfig['locales'].iteritems(): 270 for locale, locale_dir in locale_config['locales'].iteritems():
298 if locale != localeConfig['default_locale'].replace('_', '-'): 271 if locale != locale_config['default_locale'].replace('_', '-'):
299 localeTools.uploadTranslations(localeConfig, metadata, localeDir, lo cale, 272 localeTools.uploadTranslations(locale_config, metadata, locale_dir,
300 basename, key) 273 locale, basename, project_key)
301 274
302 275
303 def getTranslations(baseDir, scriptName, opts, args, type): 276 @argparse_command(
304 if len(args) < 1: 277 arguments=(project_key_argument, )
305 print 'Project key is required to update translation master files.' 278 )
306 usage(scriptName, type, 'translate') 279 def gettranslations(base_dir, project_key, platform, **kwargs):
307 return 280 """
281 Download translation updates.
308 282
309 key = args[0] 283 Download updated translations from crowdin.com.
310 284 """
311 from buildtools.packager import readMetadata 285 from buildtools.packager import readMetadata
312 metadata = readMetadata(baseDir, type) 286 metadata = readMetadata(base_dir, platform)
313 287
314 basename = metadata.get('general', 'basename') 288 basename = metadata.get('general', 'basename')
315 localeConfig = readLocaleConfig(baseDir, type, metadata) 289 locale_config = read_locale_config(base_dir, platform, metadata)
316 290
317 import buildtools.localeTools as localeTools 291 import buildtools.localeTools as localeTools
318 localeTools.getTranslations(localeConfig, basename, key) 292 localeTools.getTranslations(locale_config, basename, project_key)
319 293
320 294
321 def generateDocs(baseDir, scriptName, opts, args, type): 295 @argparse_command(
322 if len(args) == 0: 296 valid_platforms={'chrome'},
323 print 'No target directory specified for the documentation' 297 arguments=(
324 usage(scriptName, type, 'docs') 298 make_argument('target_dir'),
325 return 299 make_argument('-q', '--quiet', help='Suppress JsDoc output'),
326 targetDir = args[0] 300 )
301 )
302 def docs(base_dir, target_dir, quiet, platform, **kwargs):
303 """
304 Generate documentation (requires node.js).
327 305
328 source_dir = os.path.join(baseDir, 'lib') 306 Generate documentation files and write them into the specified directory.
329 sources = [source_dir] 307 """
308 source_dir = os.path.join(base_dir, 'lib')
330 309
331 # JSDoc struggles wih huge objects: https://github.com/jsdoc3/jsdoc/issues/9 76 310 # JSDoc struggles wih huge objects:
332 if type == 'chrome': 311 # 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'] 312 sources = [os.path.join(source_dir, filename)
313 for filename in os.listdir(source_dir)
314 if filename != 'publicSuffixList.js']
334 315
335 buildtools_path = os.path.dirname(__file__) 316 buildtools_path = os.path.dirname(__file__)
336 config = os.path.join(buildtools_path, 'jsdoc.conf') 317 config = os.path.join(buildtools_path, 'jsdoc.conf')
337 318
338 command = ['npm', 'run-script', 'jsdoc', '--', '--destination', targetDir, 319 command = ['npm', 'run-script', 'jsdoc', '--', '--destination', target_dir,
339 '--configure', config] + sources 320 '--configure', config] + sources
340 if any(opt in {'-q', '--quiet'} for opt, _ in opts): 321 if quiet:
341 process = subprocess.Popen(command, stdout=subprocess.PIPE, 322 process = subprocess.Popen(command, stdout=subprocess.PIPE,
342 stderr=subprocess.PIPE, cwd=buildtools_path) 323 stderr=subprocess.PIPE, cwd=buildtools_path)
343 stderr = process.communicate()[1] 324 stderr = process.communicate()[1]
344 retcode = process.poll() 325 retcode = process.poll()
345 if retcode: 326 if retcode:
346 sys.stderr.write(stderr) 327 sys.stderr.write(stderr)
347 raise subprocess.CalledProcessError(command, retcode) 328 raise subprocess.CalledProcessError(command, retcode)
348 else: 329 else:
349 subprocess.check_call(command, cwd=buildtools_path) 330 subprocess.check_call(command, cwd=buildtools_path)
350 331
351 332
352 def runReleaseAutomation(baseDir, scriptName, opts, args, type): 333 def valid_version_format(value):
353 keyFile = None 334 if re.search(r'[^\d\.]', value):
354 downloadsRepo = os.path.join(baseDir, '..', 'downloads') 335 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 336
361 if len(args) == 0: 337 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 338
371 if type == 'chrome' and keyFile is None: 339
372 print >>sys.stderr, 'Error: you must specify a key file for this release ' 340 @argparse_command(
373 usage(scriptName, type, 'release') 341 valid_platforms={'chrome', 'edge'},
342 arguments=(
343 make_argument(
344 '-k', '--key', dest='key_file',
345 help=('File containing private key and certificates required to '
346 'sign the release.')),
347 make_argument(
348 '-d', '--downloads-repository', dest='downloads_repository',
349 help=('Directory containing downloads repository (if omitted '
350 '../downloads is assumed)')),
351 make_argument(
352 'version', help='Version number of the release',
353 type=valid_version_format)
354 )
355 )
356 def release(base_dir, downloads_repository, key_file, platform, version,
357 **kwargs):
358 """
359 Run release automation.
360
361 Note: If you are not the project owner then you probably don't want to run
362 this!
363
364 Run release automation: create downloads for the new version, tag source
365 code repository as well as downloads and buildtools repository.
366 """
367 if downloads_repository is None:
368 downloads_repository = os.path.join(base_dir, os.pardir, 'downloads')
369
370 if platform == 'chrome' and key_file is None:
371 logging.error('You must specify a key file for this release')
374 return 372 return
375 373
376 import buildtools.releaseAutomation as releaseAutomation 374 import buildtools.releaseAutomation as releaseAutomation
377 releaseAutomation.run(baseDir, type, version, keyFile, downloadsRepo) 375 releaseAutomation.run(base_dir, platform, version, key_file,
376 downloads_repository)
378 377
379 378
380 def updatePSL(baseDir, scriptName, opts, args, type): 379 @argparse_command(valid_platforms={'chrome'})
380 def updatepsl(base_dir, **kwargs):
381 """Update Public Suffix List.
382
383 Downloads Public Suffix List (see http://publicsuffix.org/) and generates
384 lib/publicSuffixList.js from it.
385 """
381 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater 386 import buildtools.publicSuffixListUpdater as publicSuffixListUpdater
382 publicSuffixListUpdater.updatePSL(baseDir) 387 publicSuffixListUpdater.updatePSL(base_dir)
383 388
384 389
385 with addCommand(lambda baseDir, scriptName, opts, args, type: usage(scriptName, type), ('help', '-h', '--help')) as command: 390 def process_args(base_dir, *args):
386 command.shortDescription = 'Show this message' 391 if build_available_subcommands(base_dir):
392 MAIN_PARSER.set_defaults(base_dir=base_dir)
387 393
388 with addCommand(runBuild, 'build') as command: 394 # If no args are provided, this module is run directly from the command
389 command.shortDescription = 'Create a build' 395 # 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.' 396 arguments = MAIN_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 397
397 with addCommand(createDevEnv, 'devenv') as command: 398 function = arguments.function
398 command.shortDescription = 'Set up a development environment' 399 del arguments.function
399 command.description = 'Will set up or update the devenv folder as an unpacke d extension folder for development.' 400 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)
OLDNEW
« no previous file with comments | « no previous file | tests/test_packagerWebExt.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld