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: Addressed Wladimir's feedback Created Oct. 4, 2017, 1:08 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 'EXTENSION_PATH': base_extension_path, 190
177 'ENTRY_POINTS': entry_files, 191 cmd = ['node', os.path.join(os.path.dirname(__file__), 'webpack_runner.js')]
178 'BUNDLE_NAME': bundle_file, 192 process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
179 'RESOLVE_PATHS': resolve_paths, 193 stdin=subprocess.PIPE)
180 'INFO_MODULE': info_module, 194 output = process.communicate(input=toJson(configuration))[0]
181 'PATH': os.environ['PATH'] 195 if process.returncode != 0:
182 })] 196 raise subprocess.CalledProcessError(process.returncode, cmd=cmd)
183 ) 197
184 198 bundles = json.loads(output)
185 199 for bundle in bundles:
186 def toJson(data): 200 files[bundle] = bundles[bundle].encode('utf-8')
187 return json.dumps(
188 data, ensure_ascii=False, sort_keys=True,
189 indent=2, separators=(',', ': ')
190 ).encode('utf-8') + '\n'
191
192
193 def import_string_webext(data, key, source):
194 """Import a single translation from the source dictionary into data"""
195 data[key] = source
196
197
198 def import_string_gecko(data, key, value):
199 """Import Gecko-style locales into data.
200
201 Only sets {'message': value} in the data-dictionary, after stripping
202 undesired Gecko-style access keys.
203 """
204 match = re.search(r'^(.*?)\s*\(&.\)$', value)
205 if match:
206 value = match.group(1)
207 else:
208 index = value.find('&')
209 if index >= 0:
210 value = value[0:index] + value[index + 1:]
211
212 data[key] = {'message': value}
213 201
214 202
215 def import_locales(params, files): 203 def import_locales(params, files):
216 import localeTools 204 for item in params['metadata'].items('import_locales'):
217 205 filename, keys = item
218 # 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),
219 # separator instead. 207 *filename.split('/'))):
220 convert_locale_code = lambda code: code.replace('-', '_') 208 locale = sourceFile.split(os.path.sep)[-2]
221 209 targetFile = os.path.join('_locales', locale, 'messages.json')
222 # We need to map Chrome locales to Gecko locales. Start by mapping Chrome 210 data = json.loads(files.get(targetFile, '{}').decode('utf-8'))
223 # locales to themselves, merely with the dash as separator.
224 locale_mapping = {convert_locale_code(l): l for l in localeTools.chromeLocal es}
225
226 # Convert values to Crowdin locales first (use Chrome => Crowdin mapping).
227 for chrome_locale, crowdin_locale in localeTools.langMappingChrome.iteritems ():
228 locale_mapping[convert_locale_code(chrome_locale)] = crowdin_locale
229
230 # Now convert values to Gecko locales (use Gecko => Crowdin mapping).
231 reverse_mapping = {v: k for k, v in locale_mapping.iteritems()}
232 for gecko_locale, crowdin_locale in localeTools.langMappingGecko.iteritems() :
233 if crowdin_locale in reverse_mapping:
234 locale_mapping[reverse_mapping[crowdin_locale]] = gecko_locale
235
236 for target, source in locale_mapping.iteritems():
237 targetFile = '_locales/%s/messages.json' % target
238 if not targetFile in files:
239 continue
240
241 for item in params['metadata'].items('import_locales'):
242 fileName, keys = item
243 parts = map(lambda n: source if n == '*' else n, fileName.split('/') )
244 sourceFile = os.path.join(os.path.dirname(item.source), *parts)
245 incompleteMarker = os.path.join(os.path.dirname(sourceFile), '.incom plete')
246 if not os.path.exists(sourceFile) or os.path.exists(incompleteMarker ):
247 continue
248
249 data = json.loads(files[targetFile].decode('utf-8'))
250 211
251 try: 212 try:
252 # The WebExtensions (.json) and Gecko format provide 213 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
253 # translations differently and/or provide additional 214 sourceData = json.load(handle)
254 # information like e.g. "placeholders". We want to adhere to
255 # that and preserve the addtional info.
256 if sourceFile.endswith('.json'):
257 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
258 sourceData = json.load(handle)
259 import_string = import_string_webext
260 else:
261 sourceData = localeTools.readFile(sourceFile)
262 import_string = import_string_gecko
263 215
264 # Resolve wildcard imports 216 # Resolve wildcard imports
265 if keys == '*' or keys == '=*': 217 if keys == '*':
266 importList = sourceData.keys() 218 importList = sourceData.keys()
267 importList = filter(lambda k: not k.startswith('_'), importL ist) 219 importList = filter(lambda k: not k.startswith('_'), importL ist)
268 if keys == '=*':
269 importList = map(lambda k: '=' + k, importList)
270 keys = ' '.join(importList) 220 keys = ' '.join(importList)
271 221
272 for stringID in keys.split(): 222 for stringID in keys.split():
273 noMangling = False
274 if stringID.startswith('='):
275 stringID = stringID[1:]
276 noMangling = True
277
278 if stringID in sourceData: 223 if stringID in sourceData:
279 if noMangling: 224 if stringID in data:
280 key = re.sub(r'\W', '_', stringID) 225 print ('Warning: locale string {} defined multiple'
281 else: 226 ' times').format(stringID)
282 key = re.sub(r'\..*', '', parts[-1]) + '_' + re.sub( r'\W', '_', stringID) 227
283 if key in data: 228 data[stringID] = sourceData[stringID]
284 print 'Warning: locale string %s defined multiple ti mes' % key
285
286 import_string(data, key, sourceData[stringID])
287 except Exception as e: 229 except Exception as e:
288 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)
289 231
290 files[targetFile] = toJson(data) 232 files[targetFile] = toJson(data)
291 233
292 234
293 def truncate(text, length_limit): 235 def truncate(text, length_limit):
294 if len(text) <= length_limit: 236 if len(text) <= length_limit:
295 return text 237 return text
296 return text[:length_limit - 1].rstrip() + u'\u2026' 238 return text[:length_limit - 1].rstrip() + u'\u2026'
297 239
298 240
299 def fixTranslationsForCWS(files): 241 def fix_translations_for_chrome(files):
300 # Chrome Web Store requires messages used in manifest.json to be present in
301 # all languages. It also enforces length limits for extension names and
302 # descriptions.
303 defaults = {} 242 defaults = {}
304 data = json.loads(files['_locales/%s/messages.json' % defaultLocale]) 243 data = json.loads(files['_locales/%s/messages.json' % defaultLocale])
305 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']): 244 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']):
306 name = match.group(1) 245 name = match.group(1)
307 defaults[name] = data[name] 246 defaults[name] = data[name]
308 247
309 limits = {} 248 limits = {}
310 manifest = json.loads(files['manifest.json']) 249 manifest = json.loads(files['manifest.json'])
311 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)): 250 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)):
312 match = re.search(r'__MSG_(\S+)__', manifest.get(key, '')) 251 match = re.search(r'__MSG_(\S+)__', manifest.get(key, ''))
313 if match: 252 if match:
314 limits[match.group(1)] = limit 253 limits[match.group(1)] = limit
315 254
316 for filename in files: 255 for path in list(files):
317 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:
318 continue 258 continue
319 259
320 data = json.loads(files[filename]) 260 # The Chrome Web Store requires messages used in manifest.json to
321 for name, info in defaults.iteritems(): 261 # be present in all languages, and enforces length limits on
322 data.setdefault(name, info) 262 # extension name and description.
323 for name, limit in limits.iteritems(): 263 is_latam, is_mexican, filename = match.groups()
324 if name in data: 264 if filename == 'messages.json':
325 data[name]['message'] = truncate(data[name]['message'], limit) 265 data = json.loads(files[path])
326 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
327 279
328 280
329 def signBinary(zipdata, keyFile): 281 def signBinary(zipdata, keyFile):
330 from Crypto.Hash import SHA 282 from Crypto.Hash import SHA
331 from Crypto.PublicKey import RSA 283 from Crypto.PublicKey import RSA
332 from Crypto.Signature import PKCS1_v1_5 284 from Crypto.Signature import PKCS1_v1_5
333 285
334 try: 286 try:
335 with open(keyFile, 'rb') as file: 287 with open(keyFile, 'rb') as file:
336 key = RSA.importKey(file.read()) 288 key = RSA.importKey(file.read())
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
396 files.preprocess( 348 files.preprocess(
397 [f for f, _ in metadata.items('preprocess')], 349 [f for f, _ in metadata.items('preprocess')],
398 {'needsExt': True} 350 {'needsExt': True}
399 ) 351 )
400 352
401 if metadata.has_section('import_locales'): 353 if metadata.has_section('import_locales'):
402 import_locales(params, files) 354 import_locales(params, files)
403 355
404 files['manifest.json'] = createManifest(params, files) 356 files['manifest.json'] = createManifest(params, files)
405 if type == 'chrome': 357 if type == 'chrome':
406 fixTranslationsForCWS(files) 358 fix_translations_for_chrome(files)
407 359
408 if devenv: 360 if devenv:
409 import buildtools 361 import buildtools
410 import random 362 import random
411 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')
412 files['devenvVersion__'] = str(random.random()) 364 files['devenvVersion__'] = str(random.random())
413 365
414 if metadata.has_option('general', 'testScripts'): 366 if metadata.has_option('general', 'testScripts'):
415 files['qunit/index.html'] = createScriptPage( 367 files['qunit/index.html'] = createScriptPage(
416 params, 'testIndex.html.tmpl', ('general', 'testScripts') 368 params, 'testIndex.html.tmpl', ('general', 'testScripts')
417 ) 369 )
418 370
419 zipdata = files.zipToString() 371 zipdata = files.zipToString()
420 signature = None 372 signature = None
421 pubkey = None 373 pubkey = None
422 if keyFile != None: 374 if keyFile != None:
423 signature = signBinary(zipdata, keyFile) 375 signature = signBinary(zipdata, keyFile)
424 pubkey = getPublicKey(keyFile) 376 pubkey = getPublicKey(keyFile)
425 writePackage(outFile, pubkey, signature, zipdata) 377 writePackage(outFile, pubkey, signature, zipdata)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld