 Issue 29345751:
  Issue 4028 - Add support for Edge extensions to buildtools  (Closed)
    
  
    Issue 29345751:
  Issue 4028 - Add support for Edge extensions to buildtools  (Closed) 
  | Index: packagerEdge.py | 
| =================================================================== | 
| new file mode 100644 | 
| --- /dev/null | 
| +++ b/packagerEdge.py | 
| @@ -0,0 +1,165 @@ | 
| +# This Source Code Form is subject to the terms of the Mozilla Public | 
| +# License, v. 2.0. If a copy of the MPL was not distributed with this | 
| +# file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
| + | 
| +import base64 | 
| +import hashlib | 
| +import mimetypes | 
| +import os | 
| +import zipfile | 
| + | 
| +import packager | 
| +import packagerChrome | 
| + | 
| +MANIFEST = 'AppxManifest.xml' | 
| +CONTENT_TYPES = '[Content_Types].xml' | 
| +BLOCKMAP = 'AppxBlockMap.xml' | 
| +BLOCKSIZE = 64 * 1024 | 
| + | 
| + | 
| +def _get_template_for(filename): | 
| + return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
| + | 
| + | 
| +def _lfh_size(filename): | 
| + """Compute the size of zip local file header for `filename`.""" | 
| + try: | 
| + filename = filename.encode('utf-8') | 
| + except UnicodeDecodeError: | 
| + pass # filename is already a byte string. | 
| + return zipfile.sizeFileHeader + len(filename) | 
| + | 
| + | 
| +def _make_blockmap_entry(filename, data): | 
| + blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)] | 
| + return { | 
| + 'name': filename.replace('/', '\\'), | 
| + 'size': len(data), | 
| + 'lfh_size': _lfh_size(filename), | 
| + 'blocks': [ | 
| + {'hash': base64.b64encode(hashlib.sha256(block).digest())} | 
| 
Sebastian Noack
2016/07/11 16:29:45
Wrapping the value into a dict seems unnecessary.
 
Vasily Kuznetsov
2016/07/12 09:38:09
The dictionary might also contain the `compressed_
 | 
| + for block in blocks | 
| + ] | 
| + } | 
| + | 
| + | 
| +def create_appx_blockmap(files): | 
| + """Create APPX blockmap for the list of files.""" | 
| + template = _get_template_for(BLOCKMAP) | 
| + files = [_make_blockmap_entry(n, d) for n, d in files.items()] | 
| + return template.render(files=files).encode('utf-8') | 
| + | 
| + | 
| +def create_appx_manifest(params, files): | 
| + """Create AppxManifest.xml.""" | 
| + template = _get_template_for(MANIFEST) | 
| + params = dict(params) | 
| + metadata = params['metadata'] | 
| + params['package_identity'] = dict(metadata.items('package_identity')) | 
| + w = params['windows_version'] = {} | 
| + w['min'], w['max'] = metadata.get('compat', 'windows').split('/') | 
| + params.update(metadata.items('general')) | 
| + for size in ['44', '50', '150']: | 
| + path = 'Assets/logo_{}.png'.format(size) | 
| + if path not in files: | 
| + raise KeyError('{} is not found in files'.format(path)) | 
| 
Sebastian Noack
2016/07/11 16:29:45
Use the + operator here?
 
Vasily Kuznetsov
2016/07/12 09:38:09
Done.
 | 
| + params['logo_' + size] = path.replace('/', '\\') | 
| + return template.render(params).encode('utf-8') | 
| + | 
| + | 
| +def move_files_to_extension(files): | 
| + """Move all files into `Extension` folder for APPX packaging.""" | 
| + # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. | 
| + # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its | 
| + # original content would be lost. | 
| + names = sorted(files.keys(), key=len, reverse=True) | 
| + for filename in names: | 
| + files['Extension/' + filename] = files.pop(filename) | 
| + | 
| + | 
| +def create_content_types_map(files): | 
| + """Create [Content_Types].xml -- a mime type map.""" | 
| + # We always have a manifest and a blockmap, so we include those by default. | 
| + params = { | 
| + 'defaults': { | 
| + 'xml': 'text/xml' | 
| 
Sebastian Noack
2016/07/11 16:29:44
Isn't that overridden below?
 
Vasily Kuznetsov
2016/07/12 09:38:08
Indeed it is, but since I'm not sure how the conte
 
Sebastian Noack
2016/07/12 21:15:25
I mean, isn't the value here overridden by the log
 
Vasily Kuznetsov
2016/07/13 10:35:31
My thinking was that we are normally adding the co
 
Sebastian Noack
2016/07/13 16:29:22
Do we even need to consider AppxBlockMap.xml and A
 
Vasily Kuznetsov
2016/07/13 17:36:45
Yeah, this is possible, but we'll later have other
 
Sebastian Noack
2016/07/13 17:42:39
The package is signed by the Windows Store. In fac
 | 
| + }, | 
| + 'overrides': { | 
| + '/AppxBlockMap.xml': 'application/vnd.ms-appx.blockmap+xml', | 
| + '/AppxManifest.xml': 'application/vnd.ms-appx.manifest+xml' | 
| + } | 
| + } | 
| + for filename in files: | 
| + if '.' in filename[1:]: | 
| + ext = filename.split('.')[-1] | 
| 
Sebastian Noack
2016/07/11 16:29:45
Use os.path.splitext?
 
Vasily Kuznetsov
2016/07/12 09:38:10
Done.
 | 
| + content_type = mimetypes.guess_type(filename, strict=False) | 
| + # The return value can be a string or a tuple: | 
| 
Sebastian Noack
2016/07/11 16:29:44
Can it? I think it is always a tuple, or do I miss
 
Vasily Kuznetsov
2016/07/12 09:38:09
"The return value is a tuple (type, encoding) wher
 | 
| + # https://docs.python.org/2/library/mimetypes.html#mimetypes.guess_type | 
| + if isinstance(content_type, tuple): | 
| + content_type = content_type[0] | 
| + if content_type is not None: | 
| + params['defaults'][ext] = content_type | 
| + content_types_template = _get_template_for(CONTENT_TYPES) | 
| + return content_types_template.render(params).encode('utf-8') | 
| + | 
| + | 
| +def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API. | 
| + buildNum=None, releaseBuild=False, keyFile=None, | 
| + devenv=False): | 
| + | 
| + metadata = packager.readMetadata(baseDir, type) | 
| + version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 
| + buildNum) | 
| + | 
| + outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx') | 
| + | 
| + params = { | 
| + 'type': type, | 
| + 'baseDir': baseDir, | 
| + 'releaseBuild': releaseBuild, | 
| + 'version': version, | 
| + 'devenv': devenv, | 
| + 'metadata': metadata, | 
| + } | 
| + | 
| + files = packager.Files(packagerChrome.getPackageFiles(params), | 
| + packagerChrome.getIgnoredFiles(params)) | 
| + | 
| + if metadata.has_section('mapping'): | 
| + mapped = metadata.items('mapping') | 
| + files.readMappedFiles(mapped) | 
| + files.read(baseDir, skip=[filename for filename, _ in mapped]) | 
| + else: | 
| + files.read(baseDir) | 
| + | 
| + if metadata.has_section('convert_js'): | 
| + packagerChrome.convertJS(params, files) | 
| + | 
| + if metadata.has_section('preprocess'): | 
| + files.preprocess(metadata.options('preprocess'), {'needsExt': True}) | 
| + | 
| + if metadata.has_section('import_locales'): | 
| + packagerChrome.importGeckoLocales(params, files) | 
| + | 
| + files['manifest.json'] = packagerChrome.createManifest(params, files) | 
| + | 
| + move_files_to_extension(files) | 
| + | 
| + if metadata.has_section('appx_assets'): | 
| + for name, path in metadata.items('appx_assets'): | 
| + path = os.path.join(baseDir, path) | 
| + files.read(path, 'Assets/' + name) | 
| + | 
| + files[MANIFEST] = create_appx_manifest(params, files) | 
| + files[CONTENT_TYPES] = create_content_types_map(files) | 
| + | 
| + # We don't support AppxBlockmap.xml generation for compressed zip files at | 
| + # the moment. The only way to reliably calculate the compressed size of | 
| + # each 64k chunk in the zip file is to override the relevant parts of | 
| + # `zipfile` library. We have chosen to not do it for now, so we produce | 
| + # an uncompressed zip file. | 
| + files[BLOCKMAP] = create_appx_blockmap(files) | 
| + | 
| + # TODO: Implement AppxBlockmap.xml generation for compressed zip files. | 
| + # https://issues.adblockplus.org/ticket/4149 | 
| + files.zip(outfile, compress=False) |