Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: packagerEdge.py

Issue 29825555: Issue 6291 - add ManifoldJS packaging for Edge (Closed) Base URL: https://hg.adblockplus.org/buildtools/file/9a56d76cd951
Patch Set: Created July 9, 2018, 1:20 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « package.json ('k') | templates/edge/AppxBlockMap.xml.tmpl » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # This Source Code Form is subject to the terms of the Mozilla Public 1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 4
5 import base64 5 import datetime
6 import hashlib
7 import json 6 import json
8 import mimetypes
9 import os 7 import os
10 import zipfile 8 import shutil
9 from StringIO import StringIO
10 import subprocess
11 import tempfile
12 from zipfile import ZipFile
11 13
12 import packager 14 import packager
13 import packagerChrome 15 import packagerChrome
14 16
15 # Files and directories expected inside of the .APPX archive. 17 # Files and directories expected inside of the .APPX archive.
16 MANIFEST = 'AppxManifest.xml' 18 MANIFEST = 'appxmanifest.xml'
17 CONTENT_TYPES = '[Content_Types].xml'
18 BLOCKMAP = 'AppxBlockMap.xml'
19 EXTENSION_DIR = 'Extension' 19 EXTENSION_DIR = 'Extension'
20 ASSETS_DIR = 'Assets' 20 ASSETS_DIR = 'Assets'
21 21
22 # Size of uncompressed block in the APPX block map. 22 # Size of uncompressed block in the APPX block map.
23 BLOCKSIZE = 64 * 1024 23 BLOCKSIZE = 64 * 1024
24 24
25 defaultLocale = packagerChrome.defaultLocale 25 defaultLocale = packagerChrome.defaultLocale
26 26
27 27
28 def _get_template_for(filename): 28 def _get_template_for(filename):
29 return packager.getTemplate('edge/{}.tmpl'.format(filename)) 29 return packager.getTemplate('edge/{}.tmpl'.format(filename))
30 30
31 31
32 def _lfh_size(filename):
33 """Compute the size of zip local file header for `filename`."""
34 try:
35 filename = filename.encode('utf-8')
36 except UnicodeDecodeError:
37 pass # filename is already a byte string.
38 return zipfile.sizeFileHeader + len(filename)
39
40
41 def _make_blockmap_entry(filename, data):
42 blocks = [data[i:i + BLOCKSIZE] for i in range(0, len(data), BLOCKSIZE)]
43 return {
44 'name': filename.replace('/', '\\'),
45 'size': len(data),
46 'lfh_size': _lfh_size(filename),
47 'blocks': [
48 {'hash': base64.b64encode(hashlib.sha256(block).digest())}
49 for block in blocks
50 ],
51 }
52
53
54 def create_appx_blockmap(files):
55 """Create APPX blockmap for the list of files."""
56 # We don't support AppxBlockmap.xml generation for compressed zip files at
57 # the moment. The only way to reliably calculate the compressed size of
58 # each 64k chunk in the zip file is to override the relevant parts of
59 # `zipfile` library. We have chosen to not do it so we produce an
60 # uncompressed zip file that is later repackaged by Windows Store with
61 # compression.
62 template = _get_template_for(BLOCKMAP)
63 files = [_make_blockmap_entry(n, d) for n, d in files.items()]
64 return template.render(files=files).encode('utf-8')
65
66
67 def load_translation(files, locale): 32 def load_translation(files, locale):
68 """Load translation strings for locale from files.""" 33 """Load translation strings for locale from files."""
69 path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale) 34 path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale)
70 return json.loads(files[path]) 35 return json.loads(files[path])
71 36
72 37
73 def get_appx_version(metadata, build_num): 38 def get_appx_version(metadata, build_num):
74 """Get the version number for usage in AppxManifest.xml. 39 """Get the version number for usage in AppxManifest.xml.
75 40
76 As required by the Windows Store, the returned version string has four 41 As required by the Windows Store, the returned version string has four
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 def move_files_to_extension(files): 84 def move_files_to_extension(files):
120 """Move all files into `Extension` folder for APPX packaging.""" 85 """Move all files into `Extension` folder for APPX packaging."""
121 # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. 86 # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'.
122 # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its 87 # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its
123 # original content would be lost. 88 # original content would be lost.
124 names = sorted(files.keys(), key=len, reverse=True) 89 names = sorted(files.keys(), key=len, reverse=True)
125 for filename in names: 90 for filename in names:
126 files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename) 91 files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename)
127 92
128 93
129 def create_content_types_map(filenames):
130 """Create [Content_Types].xml -- a mime type map."""
131 params = {'defaults': {}, 'overrides': {}}
132 overrides = {
133 BLOCKMAP: 'application/vnd.ms-appx.blockmap+xml',
134 MANIFEST: 'application/vnd.ms-appx.manifest+xml',
135 }
136 types = mimetypes.MimeTypes()
137 types.add_type('application/octet-stream', '.otf')
138 for filename in filenames:
139 ext = os.path.splitext(filename)[1]
140 if ext:
141 content_type = types.guess_type(filename, strict=False)[0]
142 if content_type is not None:
143 params['defaults'][ext[1:]] = content_type
144 if filename in overrides:
145 params['overrides']['/' + filename] = overrides[filename]
146 content_types_template = _get_template_for(CONTENT_TYPES)
147 return content_types_template.render(params).encode('utf-8')
148
149
150 def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API. 94 def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API.
151 buildNum=None, releaseBuild=False, keyFile=None, 95 buildNum=None, releaseBuild=False, keyFile=None,
152 devenv=False): 96 devenv=False):
153 97
154 metadata = packager.readMetadata(baseDir, type) 98 metadata = packager.readMetadata(baseDir, type)
155 version = packager.getBuildVersion(baseDir, metadata, releaseBuild, 99 version = packager.getBuildVersion(baseDir, metadata, releaseBuild,
156 buildNum) 100 buildNum)
157 101
158 outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx') 102 outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx')
159 103
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
191 if devenv: 135 if devenv:
192 packagerChrome.add_devenv_requirements(files, metadata, params) 136 packagerChrome.add_devenv_requirements(files, metadata, params)
193 137
194 move_files_to_extension(files) 138 move_files_to_extension(files)
195 139
196 if metadata.has_section('appx_assets'): 140 if metadata.has_section('appx_assets'):
197 for name, path in metadata.items('appx_assets'): 141 for name, path in metadata.items('appx_assets'):
198 path = os.path.join(baseDir, path) 142 path = os.path.join(baseDir, path)
199 files.read(path, '{}/{}'.format(ASSETS_DIR, name)) 143 files.read(path, '{}/{}'.format(ASSETS_DIR, name))
200 144
201 files[MANIFEST] = create_appx_manifest(params, files, 145 files[MANIFEST] = create_appx_manifest(params, files,
Sebastian Noack 2018/07/09 14:30:55 How does that even work, since you removed templat
tlucas 2018/07/09 14:56:07 I merely renamed it to the lower-case version - an
Sebastian Noack 2018/07/09 15:36:57 Gotya. Though, I wonder whether generating a compl
202 buildNum, releaseBuild) 146 buildNum, releaseBuild)
203 files[BLOCKMAP] = create_appx_blockmap(files)
204 files[CONTENT_TYPES] = create_content_types_map(files.keys() + [BLOCKMAP])
205 147
206 files.zip(outfile, compression=zipfile.ZIP_STORED) 148 zipped = StringIO()
149 files.zip(zipped)
150
151 tmp_dir = tempfile.mkdtemp('adblockplus_package')
152 cwtd = os.path.join(tmp_dir, 'edgeextension/manifest')
153
154 zipped.seek(0)
155 with ZipFile(zipped, 'r') as zip_file:
156 zip_file.extractall(cwtd)
157
158 try:
159 # mock a generationInfo.json, as it would be created by manifoldjs
160 generation_info = packager.getTemplate('generationInfo.json.tmpl')
161 with open(os.path.join(tmp_dir, 'edgeextension',
162 'generationInfo.json'),
163 'w') as fp:
164 fp.write(generation_info.render(
165 {'generation_time': datetime.datetime.now()}))
166
167 # package app with manifoldjs
168 cmd = ['npm', 'run', '--silent', 'package-edge']
169
170 cmd_env = os.environ.copy()
171 cmd_env['SRC_FOLDER'] = tmp_dir
172 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__))
173
174 package = os.path.join(tmp_dir, 'edgeextension', 'package',
175 'edgeExtension.appx')
176
177 if isinstance(outfile, basestring):
178 shutil.copyfile(package, outfile)
179 else:
180 with open(package, 'rb') as fp:
181 outfile.write(fp.read())
Sebastian Noack 2018/07/09 14:30:55 Since you already use shutil, you might want to us
tlucas 2018/07/09 14:56:07 Done.
182 finally:
183 shutil.rmtree(tmp_dir, ignore_errors=True)
OLDNEW
« no previous file with comments | « package.json ('k') | templates/edge/AppxBlockMap.xml.tmpl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld