Index: packagerEdge.py |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/packagerEdge.py |
@@ -0,0 +1,156 @@ |
+# 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 = 64 * 1024 |
+ |
+ |
+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 _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': _SHA256(block), 'compressed_size': len(block)} |
+ for block in blocks] |
+ } |
+ |
+ |
+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, files): |
+ """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']: |
+ path = 'Assets/logo_{}.png'.format(size) |
+ if path not in files: |
+ raise KeyError('{} is not found in files'.format(path)) |
+ params['logo_{}'.format(size)] = path.replace('/', '\\') |
+ return template.render(params).encode('utf-8') |
+ |
+ |
+def moveFilesToExtension(files): |
+ """Move all files into `Extension` folder for APPX packaging.""" |
+ names = sorted(files.keys(), key=len, reverse=True) |
+ for filename in names: |
+ files['Extension/' + filename] = files[filename] |
+ del files[filename] |
+ |
+ |
+def createContentTypesMap(): |
+ """Create [Content_Types].xml -- a mime type map.""" |
+ content_types_template = _get_template_for(CONTENT_TYPES) |
+ return content_types_template.render().encode('utf-8') |
+ |
+ |
+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. |
+ |
+ # TODO: Replace zip() below with a compressing version: |
+ # https://issues.adblockplus.org/ticket/4149 |
+ |
+ def zip(self, outFile, sortKey=None): |
+ """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, |
+ 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) |
+ |
+ moveFilesToExtension(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) |
+ files[CONTENT_TYPES] = createContentTypesMap() |
+ |
+ files.zip(outFile) |