 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,139 @@ | 
| +# 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 os | 
| +import zipfile | 
| + | 
| +import packager | 
| +import packagerChrome | 
| + | 
| +MANIFEST = 'AppxManifest.xml' | 
| +CONTENT_TYPES = '[Content_Types].xml' | 
| +BLOCKMAP = 'AppxBlockMap.xml' | 
| +BLOCKSIZE = 65536 | 
| 
Wladimir Palant
2016/06/17 09:53:46
Maybe more obvious if hexadecimal - 0x10000?
 
Vasily Kuznetsov
2016/06/17 17:53:26
I'm not sure if it's actually more obvious. Becaus
 | 
| + | 
| +# Size of the fixed part of Zip local file header. | 
| +# See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers | 
| +LFH_FIXED_SIZE = 30 | 
| 
Wladimir Palant
2016/06/17 09:53:46
zipfile.sizeFileHeader?
 
Vasily Kuznetsov
2016/06/17 17:53:27
Oh, I didn't know about this constant. It's not me
 | 
| + | 
| + | 
| +def _get_template_for(filename): | 
| + return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 
| + | 
| + | 
| +def _SHA256(block): | 
| + h = hashlib.new('sha256') | 
| + h.update(block) | 
| + return base64.b64encode(h.digest()) | 
| + | 
| + | 
| +def _make_blockmap_entry(filename, data): | 
| + blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)] | 
| + return { | 
| + 'name': filename.replace('/', '\\'), | 
| + 'size': str(len(data)), | 
| + 'lfh_size': str(LFH_FIXED_SIZE + len(filename)), | 
| 
Wladimir Palant
2016/06/17 09:53:46
Assumption here: filename is ASCII. Maybe enforce
 
Vasily Kuznetsov
2016/06/17 17:53:26
If it's not ASCII, it gets encoded as utf-8. I imp
 | 
| + 'blocks': [{'hash': _SHA256(block), 'compressed_size': str(len(block))} | 
| + for block in blocks] | 
| 
Wladimir Palant
2016/06/17 09:53:46
No point stringifying all the numbers, the represe
 
Vasily Kuznetsov
2016/06/17 17:53:27
Done.
 | 
| + } | 
| + | 
| + | 
| +def createAppxBlockmap(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 createAppxManifest(params): | 
| + """Create AppxManifest.xml.""" | 
| + template = _get_template_for(MANIFEST) | 
| + params = dict(params) | 
| + metadata = params['metadata'] | 
| + params['package_identity'] = dict(metadata.items('package_identity')) | 
| + params.update(metadata.items('general')) | 
| + for size in ['44', '50', '150']: | 
| + params['logo_{}'.format(size)] = 'Assets\\logo_{}.png'.format(size) | 
| 
Wladimir Palant
2016/06/17 09:53:46
We need to verify that these files actually exist.
 
Vasily Kuznetsov
2016/06/17 17:53:27
I started checking that they exist.
 | 
| + return template.render(params).encode('utf-8') | 
| + | 
| + | 
| +def convertToAppx(files): | 
| + """Move all files into `Extension` folder for APPX packaging.""" | 
| + names = sorted(files.keys(), key=len, reverse=True) | 
| 
Wladimir Palant
2016/06/17 09:53:45
Why sort here? If the point is creating a copy of
 
Vasily Kuznetsov
2016/06/17 17:53:26
Sorting is to make sure that moving 'foo' to 'Exte
 
Wladimir Palant
2016/06/29 19:33:44
This makes sense but it's also non-obvious. You be
 
Vasily Kuznetsov
2016/07/01 19:51:27
Done
 | 
| + for filename in names: | 
| + files['Extension/' + filename] = files[filename] | 
| + del files[filename] | 
| + content_types_template = _get_template_for(CONTENT_TYPES) | 
| + files[CONTENT_TYPES] = content_types_template.render().encode('utf-8') | 
| 
Wladimir Palant
2016/06/17 09:53:46
This function does two things - prefix file names
 
Vasily Kuznetsov
2016/06/17 17:53:27
Yeah, makes sense. I broke it up.
 | 
| + | 
| + | 
| +class Files(packager.Files): | 
| + """Files subclass that zips without compression.""" | 
| + | 
| + # 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 zip() below | 
| + # doesn't perform any compression. | 
| + | 
| + def zip(self, outFile, sortKey=None): | 
| 
Vasily Kuznetsov
2016/06/13 12:57:31
This can eventually be replaced with a compressing
 
Wladimir Palant
2016/06/17 09:53:46
Maybe add a TODO comment on that, so it's obvious?
 
Vasily Kuznetsov
2016/06/17 17:53:27
Done.
 | 
| + """Pack files into zip archive producing matching appx block map.""" | 
| + zf = zipfile.ZipFile(outFile, 'w', zipfile.ZIP_STORED) | 
| + for name in sorted(self, key=sortKey): | 
| + zf.writestr(name, self[name]) | 
| + zf.writestr(BLOCKMAP, createAppxBlockmap(self)) | 
| + zf.close() | 
| + | 
| + | 
| +def createBuild(baseDir, type='edge', outFile=None, buildNum=None, | 
| 
Vasily Kuznetsov
2016/06/13 12:57:31
This function has been copied from `packagerChrome
 | 
| + releaseBuild=False, keyFile=None, devenv=False): | 
| + | 
| + metadata = packager.readMetadata(baseDir, type) | 
| + version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 
| + buildNum) | 
| + | 
| + if outFile is None: | 
| + outFile = packager.getDefaultFileName(metadata, version, 'appx') | 
| + | 
| + params = { | 
| + 'type': type, | 
| + 'baseDir': baseDir, | 
| + 'releaseBuild': releaseBuild, | 
| + 'version': version, | 
| + 'devenv': devenv, | 
| + 'metadata': metadata, | 
| + } | 
| + | 
| + files = 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) | 
| + | 
| + convertToAppx(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/{}'.format(name)) | 
| + | 
| + files[MANIFEST] = createAppxManifest(params) | 
| + files.zip(outFile) |