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

Delta Between Two Patch Sets: packagerChrome.py

Issue 29549786: Issue 5535 - Replace our module system with webpack (Closed)
Left Patch Set: Tidy up JSON passed from packagerChrome.py Created Oct. 4, 2017, 1:23 p.m.
Right Patch Set: Addressed final nits Created Oct. 10, 2017, 5:02 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « package-lock.json ('k') | packagerEdge.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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 errno 5 import errno
6 import glob
6 import io 7 import io
7 import json 8 import json
8 import os 9 import os
9 import re 10 import re
10 from StringIO import StringIO 11 from StringIO import StringIO
11 import struct 12 import struct
12 import subprocess 13 import subprocess
13 import sys 14 import sys
14 15
15 from ensure_dependencies import read_deps
16 from packager import (readMetadata, getDefaultFileName, getBuildVersion, 16 from packager import (readMetadata, getDefaultFileName, getBuildVersion,
17 getTemplate, Files) 17 getTemplate, Files)
18 18
19 defaultLocale = 'en_US' 19 defaultLocale = 'en_US'
20 20
21 21
22 def getIgnoredFiles(params): 22 def getIgnoredFiles(params):
23 return {'store.description'} 23 return {'store.description'}
24 24
25 25
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
133 # Normalize JSON structure 133 # Normalize JSON structure
134 licenseComment = re.compile(r'/\*.*?\*/', re.S) 134 licenseComment = re.compile(r'/\*.*?\*/', re.S)
135 data = json.loads(re.sub(licenseComment, '', manifest, 1)) 135 data = json.loads(re.sub(licenseComment, '', manifest, 1))
136 if '_dummy' in data: 136 if '_dummy' in data:
137 del data['_dummy'] 137 del data['_dummy']
138 manifest = json.dumps(data, sort_keys=True, indent=2) 138 manifest = json.dumps(data, sort_keys=True, indent=2)
139 139
140 return manifest.encode('utf-8') 140 return manifest.encode('utf-8')
141 141
142 142
143 def toJson(data):
144 return json.dumps(
145 data, ensure_ascii=False, sort_keys=True,
146 indent=2, separators=(',', ': ')
147 ).encode('utf-8') + '\n'
148
149
143 def create_bundles(params, files): 150 def create_bundles(params, files):
144 base_extension_path = params['baseDir'] 151 base_extension_path = params['baseDir']
145 info_templates = { 152 info_templates = {
146 'chrome': 'chromeInfo.js.tmpl', 153 'chrome': 'chromeInfo.js.tmpl',
147 'edge': 'edgeInfo.js.tmpl', 154 'edge': 'edgeInfo.js.tmpl',
148 'gecko-webext': 'geckoInfo.js.tmpl' 155 'gecko-webext': 'geckoInfo.js.tmpl'
149 } 156 }
150 157
151 # Historically we didn't use relative paths when requiring modules, so in 158 # Historically we didn't use relative paths when requiring modules, so in
152 # order for webpack to know where to find them we need to pass in a list of 159 # order for webpack to know where to find them we need to pass in a list of
153 # resolve paths. Going forward we should always use relative paths, once we 160 # resolve paths. Going forward we should always use relative paths, once we
154 # do that consistently this can be removed. See issues 5760, 5761 and 5762. 161 # do that consistently this can be removed. See issues 5760, 5761 and 5762.
155 resolve_paths = [os.path.join(base_extension_path, dir, 'lib') 162 resolve_paths = [os.path.join(base_extension_path, dir, 'lib')
156 for dir in ['', 'adblockpluscore', 'adblockplusui']] 163 for dir in ['', 'adblockpluscore', 'adblockplusui']]
157 164
158 info_template = getTemplate(info_templates[params['type']]) 165 info_template = getTemplate(info_templates[params['type']])
159 info_module = info_template.render( 166 info_module = info_template.render(
160 basename=params['metadata'].get('general', 'basename'), 167 basename=params['metadata'].get('general', 'basename'),
161 version=params['metadata'].get('general', 'version') 168 version=params['metadata'].get('general', 'version')
162 ).encode('utf-8') 169 ).encode('utf-8')
163 170
171 configuration = {
172 'bundles': [],
173 'extension_path': base_extension_path,
174 'info_module': info_module,
175 'resolve_paths': resolve_paths,
176 }
177
164 for item in params['metadata'].items('bundles'): 178 for item in params['metadata'].items('bundles'):
165 name, value = item 179 name, value = item
166 base_item_path = os.path.dirname(item.source) 180 base_item_path = os.path.dirname(item.source)
167 181
168 bundle_file = os.path.relpath(os.path.join(base_item_path, name), 182 bundle_file = os.path.relpath(os.path.join(base_item_path, name),
169 base_extension_path) 183 base_extension_path)
170 entry_files = [os.path.join(base_item_path, module_path) 184 entry_files = [os.path.join(base_item_path, module_path)
171 for module_path in value.split()] 185 for module_path in value.split()]
172 files[bundle_file] = subprocess.check_output( 186 configuration['bundles'].append({
173 ['node', 187 'bundle_name': bundle_file,
174 os.path.join(os.path.dirname(__file__), 'webpack_runner.js'), 188 'entry_points': entry_files,
175 toJson({ 189 })
176 'BUNDLE_NAME': bundle_file, 190
177 'ENTRY_POINTS': entry_files, 191 cmd = ['node', os.path.join(os.path.dirname(__file__), 'webpack_runner.js')]
178 'EXTENSION_PATH': base_extension_path, 192 process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
179 'INFO_MODULE': info_module, 193 stdin=subprocess.PIPE)
180 'RESOLVE_PATHS': resolve_paths 194 output = process.communicate(input=toJson(configuration))[0]
181 })] 195 if process.returncode != 0:
182 ) 196 raise subprocess.CalledProcessError(process.returncode, cmd=cmd)
183 197
184 198 bundles = json.loads(output)
185 def toJson(data): 199 for bundle in bundles:
186 return json.dumps( 200 files[bundle] = bundles[bundle].encode('utf-8')
187 data, ensure_ascii=False, sort_keys=True,
188 indent=2, separators=(',', ': ')
189 ).encode('utf-8') + '\n'
190
191
192 def import_string_webext(data, key, source):
193 """Import a single translation from the source dictionary into data"""
194 data[key] = source
195
196
197 def import_string_gecko(data, key, value):
198 """Import Gecko-style locales into data.
199
200 Only sets {'message': value} in the data-dictionary, after stripping
201 undesired Gecko-style access keys.
202 """
203 match = re.search(r'^(.*?)\s*\(&.\)$', value)
204 if match:
205 value = match.group(1)
206 else:
207 index = value.find('&')
208 if index >= 0:
209 value = value[0:index] + value[index + 1:]
210
211 data[key] = {'message': value}
212 201
213 202
214 def import_locales(params, files): 203 def import_locales(params, files):
215 import localeTools 204 for item in params['metadata'].items('import_locales'):
216 205 filename, keys = item
217 # FIXME: localeTools doesn't use real Chrome locales, it uses dash as 206 for sourceFile in glob.glob(os.path.join(os.path.dirname(item.source),
218 # separator instead. 207 *filename.split('/'))):
219 convert_locale_code = lambda code: code.replace('-', '_') 208 locale = sourceFile.split(os.path.sep)[-2]
220 209 targetFile = os.path.join('_locales', locale, 'messages.json')
221 # We need to map Chrome locales to Gecko locales. Start by mapping Chrome 210 data = json.loads(files.get(targetFile, '{}').decode('utf-8'))
222 # locales to themselves, merely with the dash as separator.
223 locale_mapping = {convert_locale_code(l): l for l in localeTools.chromeLocal es}
224
225 # Convert values to Crowdin locales first (use Chrome => Crowdin mapping).
226 for chrome_locale, crowdin_locale in localeTools.langMappingChrome.iteritems ():
227 locale_mapping[convert_locale_code(chrome_locale)] = crowdin_locale
228
229 # Now convert values to Gecko locales (use Gecko => Crowdin mapping).
230 reverse_mapping = {v: k for k, v in locale_mapping.iteritems()}
231 for gecko_locale, crowdin_locale in localeTools.langMappingGecko.iteritems() :
232 if crowdin_locale in reverse_mapping:
233 locale_mapping[reverse_mapping[crowdin_locale]] = gecko_locale
234
235 for target, source in locale_mapping.iteritems():
236 targetFile = '_locales/%s/messages.json' % target
237 if not targetFile in files:
238 continue
239
240 for item in params['metadata'].items('import_locales'):
241 fileName, keys = item
242 parts = map(lambda n: source if n == '*' else n, fileName.split('/') )
243 sourceFile = os.path.join(os.path.dirname(item.source), *parts)
244 incompleteMarker = os.path.join(os.path.dirname(sourceFile), '.incom plete')
245 if not os.path.exists(sourceFile) or os.path.exists(incompleteMarker ):
246 continue
247
248 data = json.loads(files[targetFile].decode('utf-8'))
249 211
250 try: 212 try:
251 # The WebExtensions (.json) and Gecko format provide 213 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
252 # translations differently and/or provide additional 214 sourceData = json.load(handle)
253 # information like e.g. "placeholders". We want to adhere to
254 # that and preserve the addtional info.
255 if sourceFile.endswith('.json'):
256 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
257 sourceData = json.load(handle)
258 import_string = import_string_webext
259 else:
260 sourceData = localeTools.readFile(sourceFile)
261 import_string = import_string_gecko
262 215
263 # Resolve wildcard imports 216 # Resolve wildcard imports
264 if keys == '*' or keys == '=*': 217 if keys == '*':
265 importList = sourceData.keys() 218 importList = sourceData.keys()
266 importList = filter(lambda k: not k.startswith('_'), importL ist) 219 importList = filter(lambda k: not k.startswith('_'), importL ist)
267 if keys == '=*':
268 importList = map(lambda k: '=' + k, importList)
269 keys = ' '.join(importList) 220 keys = ' '.join(importList)
270 221
271 for stringID in keys.split(): 222 for stringID in keys.split():
272 noMangling = False
273 if stringID.startswith('='):
274 stringID = stringID[1:]
275 noMangling = True
276
277 if stringID in sourceData: 223 if stringID in sourceData:
278 if noMangling: 224 if stringID in data:
279 key = re.sub(r'\W', '_', stringID) 225 print ('Warning: locale string {} defined multiple'
280 else: 226 ' times').format(stringID)
281 key = re.sub(r'\..*', '', parts[-1]) + '_' + re.sub( r'\W', '_', stringID) 227
282 if key in data: 228 data[stringID] = sourceData[stringID]
283 print 'Warning: locale string %s defined multiple ti mes' % key
284
285 import_string(data, key, sourceData[stringID])
286 except Exception as e: 229 except Exception as e:
287 print 'Warning: error importing locale data from %s: %s' % (sour ceFile, e) 230 print 'Warning: error importing locale data from %s: %s' % (sour ceFile, e)
288 231
289 files[targetFile] = toJson(data) 232 files[targetFile] = toJson(data)
290 233
291 234
292 def truncate(text, length_limit): 235 def truncate(text, length_limit):
293 if len(text) <= length_limit: 236 if len(text) <= length_limit:
294 return text 237 return text
295 return text[:length_limit - 1].rstrip() + u'\u2026' 238 return text[:length_limit - 1].rstrip() + u'\u2026'
296 239
297 240
298 def fixTranslationsForCWS(files): 241 def fix_translations_for_chrome(files):
299 # Chrome Web Store requires messages used in manifest.json to be present in
300 # all languages. It also enforces length limits for extension names and
301 # descriptions.
302 defaults = {} 242 defaults = {}
303 data = json.loads(files['_locales/%s/messages.json' % defaultLocale]) 243 data = json.loads(files['_locales/%s/messages.json' % defaultLocale])
304 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']): 244 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']):
305 name = match.group(1) 245 name = match.group(1)
306 defaults[name] = data[name] 246 defaults[name] = data[name]
307 247
308 limits = {} 248 limits = {}
309 manifest = json.loads(files['manifest.json']) 249 manifest = json.loads(files['manifest.json'])
310 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)): 250 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)):
311 match = re.search(r'__MSG_(\S+)__', manifest.get(key, '')) 251 match = re.search(r'__MSG_(\S+)__', manifest.get(key, ''))
312 if match: 252 if match:
313 limits[match.group(1)] = limit 253 limits[match.group(1)] = limit
314 254
315 for filename in files: 255 for path in list(files):
316 if not filename.startswith('_locales/') or not filename.endswith('/messa ges.json'): 256 match = re.search(r'^_locales/(?:es_(AR|CL|(MX))|[^/]+)/(.*)', path)
257 if not match:
317 continue 258 continue
318 259
319 data = json.loads(files[filename]) 260 # The Chrome Web Store requires messages used in manifest.json to
320 for name, info in defaults.iteritems(): 261 # be present in all languages, and enforces length limits on
321 data.setdefault(name, info) 262 # extension name and description.
322 for name, limit in limits.iteritems(): 263 is_latam, is_mexican, filename = match.groups()
323 if name in data: 264 if filename == 'messages.json':
324 data[name]['message'] = truncate(data[name]['message'], limit) 265 data = json.loads(files[path])
325 files[filename] = toJson(data) 266 for name, info in defaults.iteritems():
267 data.setdefault(name, info)
268 for name, limit in limits.iteritems():
269 info = data.get(name)
270 if info:
271 info['message'] = truncate(info['message'], limit)
272 files[path] = toJson(data)
273
274 # Chrome combines Latin American dialects of Spanish into es-419.
275 if is_latam:
276 data = files.pop(path)
277 if is_mexican:
278 files['_locales/es_419/' + filename] = data
326 279
327 280
328 def signBinary(zipdata, keyFile): 281 def signBinary(zipdata, keyFile):
329 from Crypto.Hash import SHA 282 from Crypto.Hash import SHA
330 from Crypto.PublicKey import RSA 283 from Crypto.PublicKey import RSA
331 from Crypto.Signature import PKCS1_v1_5 284 from Crypto.Signature import PKCS1_v1_5
332 285
333 try: 286 try:
334 with open(keyFile, 'rb') as file: 287 with open(keyFile, 'rb') as file:
335 key = RSA.importKey(file.read()) 288 key = RSA.importKey(file.read())
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
395 files.preprocess( 348 files.preprocess(
396 [f for f, _ in metadata.items('preprocess')], 349 [f for f, _ in metadata.items('preprocess')],
397 {'needsExt': True} 350 {'needsExt': True}
398 ) 351 )
399 352
400 if metadata.has_section('import_locales'): 353 if metadata.has_section('import_locales'):
401 import_locales(params, files) 354 import_locales(params, files)
402 355
403 files['manifest.json'] = createManifest(params, files) 356 files['manifest.json'] = createManifest(params, files)
404 if type == 'chrome': 357 if type == 'chrome':
405 fixTranslationsForCWS(files) 358 fix_translations_for_chrome(files)
406 359
407 if devenv: 360 if devenv:
408 import buildtools 361 import buildtools
409 import random 362 import random
410 files.read(os.path.join(buildtools.__path__[0], 'chromeDevenvPoller__.js '), relpath='devenvPoller__.js') 363 files.read(os.path.join(buildtools.__path__[0], 'chromeDevenvPoller__.js '), relpath='devenvPoller__.js')
411 files['devenvVersion__'] = str(random.random()) 364 files['devenvVersion__'] = str(random.random())
412 365
413 if metadata.has_option('general', 'testScripts'): 366 if metadata.has_option('general', 'testScripts'):
414 files['qunit/index.html'] = createScriptPage( 367 files['qunit/index.html'] = createScriptPage(
415 params, 'testIndex.html.tmpl', ('general', 'testScripts') 368 params, 'testIndex.html.tmpl', ('general', 'testScripts')
416 ) 369 )
417 370
418 zipdata = files.zipToString() 371 zipdata = files.zipToString()
419 signature = None 372 signature = None
420 pubkey = None 373 pubkey = None
421 if keyFile != None: 374 if keyFile != None:
422 signature = signBinary(zipdata, keyFile) 375 signature = signBinary(zipdata, keyFile)
423 pubkey = getPublicKey(keyFile) 376 pubkey = getPublicKey(keyFile)
424 writePackage(outFile, pubkey, signature, zipdata) 377 writePackage(outFile, pubkey, signature, zipdata)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld