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) |