| Index: packagerEdge.py |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/packagerEdge.py |
| @@ -0,0 +1,159 @@ |
| +# 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') |
|
Sebastian Noack
2016/07/05 14:30:37
According to the Python documentation the named co
Vasily Kuznetsov
2016/07/07 16:23:49
Changed the constructor. About one-liner: it seems
Sebastian Noack
2016/07/08 13:59:11
You can just pass the data to the constructor:
Vasily Kuznetsov
2016/07/08 16:47:39
Somehow I missed the section in the documentation
|
| + 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)} |
|
Sebastian Noack
2016/07/05 14:30:38
From the documentation it seems we can simply omit
Vasily Kuznetsov
2016/07/07 16:23:50
Done.
|
| + 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')) |
| + params.update(metadata.items('general')) |
| + for size in ['44', '50', '150']: |
| + path = 'Assets/logo_{}.png'.format(size) |
|
Sebastian Noack
2016/07/05 14:30:38
Where does those files come from? Don't you move e
Vasily Kuznetsov
2016/07/07 16:23:50
These are separately added after everything is mov
|
| + if path not in files: |
| + raise KeyError('{} is not found in files'.format(path)) |
| + params['logo_{}'.format(size)] = path.replace('/', '\\') |
|
Sebastian Noack
2016/07/05 14:30:38
As per our coding style please use the + operator
Vasily Kuznetsov
2016/07/07 16:23:51
Done.
|
| + 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[filename] |
|
Sebastian Noack
2016/07/05 14:30:38
If you use files.pop() this would make the next li
Vasily Kuznetsov
2016/07/07 16:23:49
Wow! I didn't realize you can `.pop()` from a dict
|
| + del files[filename] |
| + |
| + |
| +def create_content_types_map(): |
| + """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): # noqa: preserve API. |
|
Sebastian Noack
2016/07/05 14:30:38
The code duplication here isn't great. Perhaps jus
Vasily Kuznetsov
2016/07/07 16:23:50
We'll have to redo this again when we implement ou
|
| + """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, create_appx_blockmap(self)) |
| + zf.close() |
| + |
| + |
| +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 = 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/{}'.format(name)) |
| + |
| + files[MANIFEST] = create_appx_manifest(params, files) |
| + files[CONTENT_TYPES] = create_content_types_map() |
| + |
| + files.zip(outfile) |