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: Use absolute paths for entry points Created Sept. 28, 2017, 1:09 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 import shutil
11 from StringIO import StringIO 11 from StringIO import StringIO
12 import struct 12 import struct
13 import subprocess 13 import subprocess
14 import sys 14 import sys
15 import tempfile 15
16
17 from ensure_dependencies import read_deps
18 from packager import (readMetadata, getDefaultFileName, getBuildVersion, 16 from packager import (readMetadata, getDefaultFileName, getBuildVersion,
19 getTemplate, Files) 17 getTemplate, Files)
20 18
21 defaultLocale = 'en_US' 19 defaultLocale = 'en_US'
22 20
23 21
24 def getIgnoredFiles(params): 22 def getIgnoredFiles(params):
25 return {'store.description'} 23 return {'store.description'}
26 24
27 25
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
135 # Normalize JSON structure 133 # Normalize JSON structure
136 licenseComment = re.compile(r'/\*.*?\*/', re.S) 134 licenseComment = re.compile(r'/\*.*?\*/', re.S)
137 data = json.loads(re.sub(licenseComment, '', manifest, 1)) 135 data = json.loads(re.sub(licenseComment, '', manifest, 1))
138 if '_dummy' in data: 136 if '_dummy' in data:
139 del data['_dummy'] 137 del data['_dummy']
140 manifest = json.dumps(data, sort_keys=True, indent=2) 138 manifest = json.dumps(data, sort_keys=True, indent=2)
141 139
142 return manifest.encode('utf-8') 140 return manifest.encode('utf-8')
143 141
144 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
145 def create_bundles(params, files): 150 def create_bundles(params, files):
146 base_extension_path = params['baseDir'] 151 base_extension_path = params['baseDir']
147 info_templates = { 152 info_templates = {
148 'chrome': 'chromeInfo.js.tmpl', 153 'chrome': 'chromeInfo.js.tmpl',
149 'edge': 'edgeInfo.js.tmpl', 154 'edge': 'edgeInfo.js.tmpl',
150 'gecko-webext': 'geckoInfo.js.tmpl' 155 'gecko-webext': 'geckoInfo.js.tmpl'
151 } 156 }
152 info_module = None
153 157
154 # 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
155 # 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
156 # 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
157 # 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.
158 resolve_paths = ' '.join( 162 resolve_paths = [os.path.join(base_extension_path, dir, 'lib')
159 [os.path.join(base_extension_path, dir, 'lib') 163 for dir in ['', 'adblockpluscore', 'adblockplusui']]
160 for dir in ['', 'adblockpluscore', 'adblockplusui']] 164
161 ) 165 info_template = getTemplate(info_templates[params['type']])
162 166 info_module = info_template.render(
163 temp_dir = tempfile.mkdtemp() 167 basename=params['metadata'].get('general', 'basename'),
164 try: 168 version=params['metadata'].get('general', 'version')
165 info_module = os.path.join(temp_dir, 'info.js') 169 ).encode('utf-8')
166 template = getTemplate(info_templates[params['type']]) 170
167 with open(info_module, 'w') as info_file: 171 configuration = {
168 info_file.write( 172 'bundles': [],
169 template.render( 173 'extension_path': base_extension_path,
170 basename=params['metadata'].get('general', 'basename'), 174 'info_module': info_module,
171 version=params['metadata'].get('general', 'version') 175 'resolve_paths': resolve_paths,
172 ).encode('utf-8') 176 }
173 ) 177
174 178 for item in params['metadata'].items('bundles'):
175 for item in params['metadata'].items('bundles'): 179 name, value = item
176 name, value = item 180 base_item_path = os.path.dirname(item.source)
177 base_item_path = os.path.dirname(item.source) 181
178 182 bundle_file = os.path.relpath(os.path.join(base_item_path, name),
179 bundle_file = os.path.relpath(os.path.join(base_item_path, name), 183 base_extension_path)
180 base_extension_path) 184 entry_files = [os.path.join(base_item_path, module_path)
181 entry_files = [os.path.join(base_item_path, module_path) 185 for module_path in value.split()]
182 for module_path in value.split()] 186 configuration['bundles'].append({
183 subprocess.check_call( 187 'bundle_name': bundle_file,
184 ['npm', 'run-script', 'webpack', '--silent'], 188 'entry_points': entry_files,
185 cwd=os.path.dirname(__file__), 189 })
186 env={ 190
187 'EXTENSION_PATH': base_extension_path, 191 cmd = ['node', os.path.join(os.path.dirname(__file__), 'webpack_runner.js')]
188 'ENTRY_POINTS': ' '.join(entry_files), 192 process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
189 'OUTPUT_PATH': temp_dir, 193 stdin=subprocess.PIPE)
190 'BUNDLE_NAME': bundle_file, 194 output = process.communicate(input=toJson(configuration))[0]
191 'RESOLVE_PATHS': resolve_paths, 195 if process.returncode != 0:
192 'INFO_PATH': info_module, 196 raise subprocess.CalledProcessError(process.returncode, cmd=cmd)
193 'PATH': os.environ['PATH'] 197
194 } 198 bundles = json.loads(output)
195 ) 199 for bundle in bundles:
196 for file_name in [bundle_file, bundle_file + '.map']: 200 files[bundle] = bundles[bundle].encode('utf-8')
197 with open(os.path.join(temp_dir, file_name), 'r') as f:
198 files[file_name] = f.read()
199 finally:
200 shutil.rmtree(temp_dir)
201
202
203 def toJson(data):
204 return json.dumps(
205 data, ensure_ascii=False, sort_keys=True,
206 indent=2, separators=(',', ': ')
207 ).encode('utf-8') + '\n'
208
209
210 def import_string_webext(data, key, source):
211 """Import a single translation from the source dictionary into data"""
212 data[key] = source
213
214
215 def import_string_gecko(data, key, value):
216 """Import Gecko-style locales into data.
217
218 Only sets {'message': value} in the data-dictionary, after stripping
219 undesired Gecko-style access keys.
220 """
221 match = re.search(r'^(.*?)\s*\(&.\)$', value)
222 if match:
223 value = match.group(1)
224 else:
225 index = value.find('&')
226 if index >= 0:
227 value = value[0:index] + value[index + 1:]
228
229 data[key] = {'message': value}
230 201
231 202
232 def import_locales(params, files): 203 def import_locales(params, files):
233 import localeTools 204 for item in params['metadata'].items('import_locales'):
234 205 filename, keys = item
235 # 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),
236 # separator instead. 207 *filename.split('/'))):
237 convert_locale_code = lambda code: code.replace('-', '_') 208 locale = sourceFile.split(os.path.sep)[-2]
238 209 targetFile = os.path.join('_locales', locale, 'messages.json')
239 # We need to map Chrome locales to Gecko locales. Start by mapping Chrome 210 data = json.loads(files.get(targetFile, '{}').decode('utf-8'))
240 # locales to themselves, merely with the dash as separator.
241 locale_mapping = {convert_locale_code(l): l for l in localeTools.chromeLocal es}
242
243 # Convert values to Crowdin locales first (use Chrome => Crowdin mapping).
244 for chrome_locale, crowdin_locale in localeTools.langMappingChrome.iteritems ():
245 locale_mapping[convert_locale_code(chrome_locale)] = crowdin_locale
246
247 # Now convert values to Gecko locales (use Gecko => Crowdin mapping).
248 reverse_mapping = {v: k for k, v in locale_mapping.iteritems()}
249 for gecko_locale, crowdin_locale in localeTools.langMappingGecko.iteritems() :
250 if crowdin_locale in reverse_mapping:
251 locale_mapping[reverse_mapping[crowdin_locale]] = gecko_locale
252
253 for target, source in locale_mapping.iteritems():
254 targetFile = '_locales/%s/messages.json' % target
255 if not targetFile in files:
256 continue
257
258 for item in params['metadata'].items('import_locales'):
259 fileName, keys = item
260 parts = map(lambda n: source if n == '*' else n, fileName.split('/') )
261 sourceFile = os.path.join(os.path.dirname(item.source), *parts)
262 incompleteMarker = os.path.join(os.path.dirname(sourceFile), '.incom plete')
263 if not os.path.exists(sourceFile) or os.path.exists(incompleteMarker ):
264 continue
265
266 data = json.loads(files[targetFile].decode('utf-8'))
267 211
268 try: 212 try:
269 # The WebExtensions (.json) and Gecko format provide 213 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
270 # translations differently and/or provide additional 214 sourceData = json.load(handle)
271 # information like e.g. "placeholders". We want to adhere to
272 # that and preserve the addtional info.
273 if sourceFile.endswith('.json'):
274 with io.open(sourceFile, 'r', encoding='utf-8') as handle:
275 sourceData = json.load(handle)
276 import_string = import_string_webext
277 else:
278 sourceData = localeTools.readFile(sourceFile)
279 import_string = import_string_gecko
280 215
281 # Resolve wildcard imports 216 # Resolve wildcard imports
282 if keys == '*' or keys == '=*': 217 if keys == '*':
283 importList = sourceData.keys() 218 importList = sourceData.keys()
284 importList = filter(lambda k: not k.startswith('_'), importL ist) 219 importList = filter(lambda k: not k.startswith('_'), importL ist)
285 if keys == '=*':
286 importList = map(lambda k: '=' + k, importList)
287 keys = ' '.join(importList) 220 keys = ' '.join(importList)
288 221
289 for stringID in keys.split(): 222 for stringID in keys.split():
290 noMangling = False
291 if stringID.startswith('='):
292 stringID = stringID[1:]
293 noMangling = True
294
295 if stringID in sourceData: 223 if stringID in sourceData:
296 if noMangling: 224 if stringID in data:
297 key = re.sub(r'\W', '_', stringID) 225 print ('Warning: locale string {} defined multiple'
298 else: 226 ' times').format(stringID)
299 key = re.sub(r'\..*', '', parts[-1]) + '_' + re.sub( r'\W', '_', stringID) 227
300 if key in data: 228 data[stringID] = sourceData[stringID]
301 print 'Warning: locale string %s defined multiple ti mes' % key
302
303 import_string(data, key, sourceData[stringID])
304 except Exception as e: 229 except Exception as e:
305 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)
306 231
307 files[targetFile] = toJson(data) 232 files[targetFile] = toJson(data)
308 233
309 234
310 def truncate(text, length_limit): 235 def truncate(text, length_limit):
311 if len(text) <= length_limit: 236 if len(text) <= length_limit:
312 return text 237 return text
313 return text[:length_limit - 1].rstrip() + u'\u2026' 238 return text[:length_limit - 1].rstrip() + u'\u2026'
314 239
315 240
316 def fixTranslationsForCWS(files): 241 def fix_translations_for_chrome(files):
317 # Chrome Web Store requires messages used in manifest.json to be present in
318 # all languages. It also enforces length limits for extension names and
319 # descriptions.
320 defaults = {} 242 defaults = {}
321 data = json.loads(files['_locales/%s/messages.json' % defaultLocale]) 243 data = json.loads(files['_locales/%s/messages.json' % defaultLocale])
322 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']): 244 for match in re.finditer(r'__MSG_(\S+)__', files['manifest.json']):
323 name = match.group(1) 245 name = match.group(1)
324 defaults[name] = data[name] 246 defaults[name] = data[name]
325 247
326 limits = {} 248 limits = {}
327 manifest = json.loads(files['manifest.json']) 249 manifest = json.loads(files['manifest.json'])
328 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)): 250 for key, limit in (('name', 45), ('description', 132), ('short_name', 12)):
329 match = re.search(r'__MSG_(\S+)__', manifest.get(key, '')) 251 match = re.search(r'__MSG_(\S+)__', manifest.get(key, ''))
330 if match: 252 if match:
331 limits[match.group(1)] = limit 253 limits[match.group(1)] = limit
332 254
333 for filename in files: 255 for path in list(files):
334 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:
335 continue 258 continue
336 259
337 data = json.loads(files[filename]) 260 # The Chrome Web Store requires messages used in manifest.json to
338 for name, info in defaults.iteritems(): 261 # be present in all languages, and enforces length limits on
339 data.setdefault(name, info) 262 # extension name and description.
340 for name, limit in limits.iteritems(): 263 is_latam, is_mexican, filename = match.groups()
341 if name in data: 264 if filename == 'messages.json':
342 data[name]['message'] = truncate(data[name]['message'], limit) 265 data = json.loads(files[path])
343 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
344 279
345 280
346 def signBinary(zipdata, keyFile): 281 def signBinary(zipdata, keyFile):
347 from Crypto.Hash import SHA 282 from Crypto.Hash import SHA
348 from Crypto.PublicKey import RSA 283 from Crypto.PublicKey import RSA
349 from Crypto.Signature import PKCS1_v1_5 284 from Crypto.Signature import PKCS1_v1_5
350 285
351 try: 286 try:
352 with open(keyFile, 'rb') as file: 287 with open(keyFile, 'rb') as file:
353 key = RSA.importKey(file.read()) 288 key = RSA.importKey(file.read())
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 files.preprocess( 348 files.preprocess(
414 [f for f, _ in metadata.items('preprocess')], 349 [f for f, _ in metadata.items('preprocess')],
415 {'needsExt': True} 350 {'needsExt': True}
416 ) 351 )
417 352
418 if metadata.has_section('import_locales'): 353 if metadata.has_section('import_locales'):
419 import_locales(params, files) 354 import_locales(params, files)
420 355
421 files['manifest.json'] = createManifest(params, files) 356 files['manifest.json'] = createManifest(params, files)
422 if type == 'chrome': 357 if type == 'chrome':
423 fixTranslationsForCWS(files) 358 fix_translations_for_chrome(files)
424 359
425 if devenv: 360 if devenv:
426 import buildtools 361 import buildtools
427 import random 362 import random
428 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')
429 files['devenvVersion__'] = str(random.random()) 364 files['devenvVersion__'] = str(random.random())
430 365
431 if metadata.has_option('general', 'testScripts'): 366 if metadata.has_option('general', 'testScripts'):
432 files['qunit/index.html'] = createScriptPage( 367 files['qunit/index.html'] = createScriptPage(
433 params, 'testIndex.html.tmpl', ('general', 'testScripts') 368 params, 'testIndex.html.tmpl', ('general', 'testScripts')
434 ) 369 )
435 370
436 zipdata = files.zipToString() 371 zipdata = files.zipToString()
437 signature = None 372 signature = None
438 pubkey = None 373 pubkey = None
439 if keyFile != None: 374 if keyFile != None:
440 signature = signBinary(zipdata, keyFile) 375 signature = signBinary(zipdata, keyFile)
441 pubkey = getPublicKey(keyFile) 376 pubkey = getPublicKey(keyFile)
442 writePackage(outFile, pubkey, signature, zipdata) 377 writePackage(outFile, pubkey, signature, zipdata)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld