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 11, 2018, 12:05 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
6 import hashlib
7 import json 5 import json
8 import mimetypes
9 import os 6 import os
10 import zipfile 7 import shutil
8 from StringIO import StringIO
9 import subprocess
10 import tempfile
11 from zipfile import ZipFile
11 12
12 import packager 13 import packager
13 import packagerChrome 14 import packagerChrome
14 15
15 # Files and directories expected inside of the .APPX archive. 16 # Files and directories expected inside of the .APPX archive.
16 MANIFEST = 'AppxManifest.xml' 17 MANIFEST = 'appxmanifest.xml'
17 CONTENT_TYPES = '[Content_Types].xml'
18 BLOCKMAP = 'AppxBlockMap.xml'
19 EXTENSION_DIR = 'Extension' 18 EXTENSION_DIR = 'Extension'
20 ASSETS_DIR = 'Assets' 19 ASSETS_DIR = 'Assets'
21 20
22 # Size of uncompressed block in the APPX block map. 21 # Size of uncompressed block in the APPX block map.
23 BLOCKSIZE = 64 * 1024 22 BLOCKSIZE = 64 * 1024
24 23
25 defaultLocale = packagerChrome.defaultLocale 24 defaultLocale = packagerChrome.defaultLocale
26 25
27 26
28 def _get_template_for(filename): 27 def _get_template_for(filename):
29 return packager.getTemplate('edge/{}.tmpl'.format(filename)) 28 return packager.getTemplate('edge/{}.tmpl'.format(filename))
30 29
31 30
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): 31 def load_translation(files, locale):
68 """Load translation strings for locale from files.""" 32 """Load translation strings for locale from files."""
69 path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale) 33 path = '{}/_locales/{}/messages.json'.format(EXTENSION_DIR, locale)
70 return json.loads(files[path]) 34 return json.loads(files[path])
71 35
72 36
73 def get_appx_version(metadata, build_num): 37 def get_appx_version(metadata, build_num):
74 """Get the version number for usage in AppxManifest.xml. 38 """Get the version number for usage in AppxManifest.xml.
75 39
76 As required by the Windows Store, the returned version string has four 40 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): 83 def move_files_to_extension(files):
120 """Move all files into `Extension` folder for APPX packaging.""" 84 """Move all files into `Extension` folder for APPX packaging."""
121 # We sort the files to ensure that 'Extension/xyz' is moved before 'xyz'. 85 # 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 86 # If 'xyz' is moved first, it would overwrite 'Extension/xyz' and its
123 # original content would be lost. 87 # original content would be lost.
124 names = sorted(files.keys(), key=len, reverse=True) 88 names = sorted(files.keys(), key=len, reverse=True)
125 for filename in names: 89 for filename in names:
126 files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename) 90 files['{}/{}'.format(EXTENSION_DIR, filename)] = files.pop(filename)
127 91
128 92
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. 93 def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API.
151 buildNum=None, releaseBuild=False, keyFile=None, 94 buildNum=None, releaseBuild=False, keyFile=None,
152 devenv=False): 95 devenv=False):
153 96
154 metadata = packager.readMetadata(baseDir, type) 97 metadata = packager.readMetadata(baseDir, type)
155 version = packager.getBuildVersion(baseDir, metadata, releaseBuild, 98 version = packager.getBuildVersion(baseDir, metadata, releaseBuild,
156 buildNum) 99 buildNum)
157 100
158 outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx') 101 outfile = outFile or packager.getDefaultFileName(metadata, version, 'appx')
159 102
(...skipping 24 matching lines...) Expand all
184 files.preprocess(metadata.options('preprocess'), {'needsExt': True}) 127 files.preprocess(metadata.options('preprocess'), {'needsExt': True})
185 128
186 if metadata.has_section('import_locales'): 129 if metadata.has_section('import_locales'):
187 packagerChrome.import_locales(params, files) 130 packagerChrome.import_locales(params, files)
188 131
189 files['manifest.json'] = packagerChrome.createManifest(params, files) 132 files['manifest.json'] = packagerChrome.createManifest(params, files)
190 133
191 if devenv: 134 if devenv:
192 packagerChrome.add_devenv_requirements(files, metadata, params) 135 packagerChrome.add_devenv_requirements(files, metadata, params)
193 136
194 move_files_to_extension(files) 137 move_files_to_extension(files)
Sebastian Noack 2018/07/11 16:26:47 Since we skip the packaging for devenv now, there
tlucas 2018/07/12 09:31:54 Turns out this is not necessary anymore at all - r
195 138
196 if metadata.has_section('appx_assets'): 139 if metadata.has_section('appx_assets'):
197 for name, path in metadata.items('appx_assets'): 140 for name, path in metadata.items('appx_assets'):
198 path = os.path.join(baseDir, path) 141 path = os.path.join(baseDir, path)
199 files.read(path, '{}/{}'.format(ASSETS_DIR, name)) 142 files.read(path, '{}/{}'.format(ASSETS_DIR, name))
200 143
201 files[MANIFEST] = create_appx_manifest(params, files, 144 if not devenv:
tlucas 2018/07/11 12:18:03 It turned out that devenvs for Edge don't need the
Oleksandr 2018/07/11 14:23:46 I can confirm that for devenv only the `Extension`
202 buildNum, releaseBuild) 145 files[MANIFEST] = create_appx_manifest(params, files,
Sebastian Noack 2018/07/11 16:26:47 As discussed before, I would not override the APPX
tlucas 2018/07/12 09:31:55 Done (new function update_appx_manifest)
203 files[BLOCKMAP] = create_appx_blockmap(files) 146 buildNum, releaseBuild)
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 zipped.seek(0)
152
153 if devenv:
154 shutil.copyfileobj(zipped, outfile)
Sebastian Noack 2018/07/11 16:26:46 Don't we have to extract the contents for the deve
tlucas 2018/07/12 09:31:54 Yes - but that's done in build.py, with all 3 exte
155 return
156
157 tmp_dir = tempfile.mkdtemp('adblockplus_package')
158 src_dir = os.path.join(tmp_dir, 'src')
159 ext_dir = os.path.join(tmp_dir, 'ext')
160
161 with ZipFile(zipped, 'r') as zip_file:
162 zip_file.extractall(src_dir)
163
164 try:
Sebastian Noack 2018/07/11 16:26:46 This try-block should probably start right after t
tlucas 2018/07/12 09:31:54 Done.
165 cmd_env = os.environ.copy()
166 cmd_env['SRC_FOLDER'] = src_dir
167 cmd_env['EXT_FOLDER'] = ext_dir
168
169 manifold_folder = os.path.join(ext_dir, 'MSGname', 'edgeextension')
170 manifest_folder = os.path.join(manifold_folder, 'manifest')
171 asset_folder = os.path.join(manifest_folder, 'Assets')
172
173 # prepare the extension with manifoldjs
174 cmd = ['npm', 'run', '--silent', 'build-edge']
175 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__))
176
177 # overwrite incomplete appxmanifest
178 intermediate_manifest = os.path.join(manifest_folder, MANIFEST)
179 shutil.copyfile(os.path.join(src_dir, MANIFEST), intermediate_manifest)
180
181 # cleanup placeholders, copy actual images
182 shutil.rmtree(asset_folder)
183 os.mkdir(asset_folder)
184 if metadata.has_section('appx_assets'):
185 for name, path in metadata.items('appx_assets'):
186 path = os.path.join(baseDir, path)
187 target = os.path.join(asset_folder, name)
188 shutil.copyfile(path, target)
189
190 # package app with manifoldjs
191 cmd = ['npm', 'run', '--silent', 'package-edge']
192
193 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__))
194
195 package = os.path.join(manifold_folder, 'package',
196 'edgeExtension.appx')
197
198 if isinstance(outfile, basestring):
199 shutil.copyfile(package, outfile)
200 else:
201 with open(package, 'rb') as fp:
Sebastian Noack 2018/07/11 16:26:46 This is essentially dead code now, since only in c
tlucas 2018/07/12 09:31:55 That's not an assumption, at this point 'outfile'
202 shutil.copyfileobj(fp, outfile)
203 finally:
204 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