| Left: | ||
| Right: |
| LEFT | RIGHT |
|---|---|
| 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 json | |
| 6 import os | 5 import os |
| 7 import shutil | 6 import shutil |
| 8 from StringIO import StringIO | 7 from StringIO import StringIO |
| 9 import subprocess | 8 import subprocess |
| 10 import tempfile | 9 import tempfile |
| 11 from xml.etree import ElementTree | 10 from xml.etree import ElementTree |
| 12 from zipfile import ZipFile | 11 from zipfile import ZipFile |
| 13 | 12 |
| 14 import packager | 13 import packager |
| 15 import packagerChrome | 14 import packagerChrome |
| 16 | 15 |
| 17 MANIFEST = 'appxmanifest.xml' | 16 MANIFEST = 'appxmanifest.xml' |
| 17 ASSETS_DIR = 'Assets' | |
| 18 | 18 |
| 19 defaultLocale = packagerChrome.defaultLocale | 19 defaultLocale = packagerChrome.defaultLocale |
| 20 | 20 |
| 21 | 21 |
| 22 def _get_template_for(filename): | 22 def _get_template_for(filename): |
| 23 return packager.getTemplate('edge/{}.tmpl'.format(filename)) | 23 return packager.getTemplate('edge/{}.tmpl'.format(filename)) |
| 24 | |
| 25 | |
| 26 def load_translation(files, locale): | |
| 27 """Load translation strings for locale from files.""" | |
| 28 path = '_locales/{}/messages.json'.format(locale) | |
| 29 return json.loads(files[path]) | |
| 30 | 24 |
| 31 | 25 |
| 32 def register_xml_namespaces(manifest_path): | 26 def register_xml_namespaces(manifest_path): |
| 33 """Register namespaces of the given file, in order to preserve defaults.""" | 27 """Register namespaces of the given file, in order to preserve defaults.""" |
| 34 with open(manifest_path, 'r') as fp: | 28 with open(manifest_path, 'r') as fp: |
| 35 ns = dict([node for _, node in ElementTree.iterparse( | 29 ns = dict([node for _, node in ElementTree.iterparse( |
| 36 fp, events=['start-ns'])]) | 30 fp, events=['start-ns'])]) |
| 37 for prefix, uri in ns.items(): | 31 for prefix, uri in ns.items(): |
| 38 ElementTree.register_namespace(prefix, uri) | 32 ElementTree.register_namespace(prefix, uri) |
| 39 | 33 |
| 34 # Make the default namespace available in an xpath expression | |
| 35 ns['_d'] = ns[''] | |
| 36 | |
| 40 return ns | 37 return ns |
| 41 | 38 |
| 42 | 39 |
| 43 def update_appx_manifest(manifest_path, files, metadata, release_build): | 40 def update_appx_manifest(manifest_path, base_dir, files, metadata, |
| 41 release_build): | |
| 44 namespaces = register_xml_namespaces(manifest_path) | 42 namespaces = register_xml_namespaces(manifest_path) |
|
Sebastian Noack
2018/07/12 14:43:10
I wonder if this is necessary? What happens if you
tlucas
2018/07/19 12:30:27
This is necessary so that xml.etree.ElementTree.wr
| |
| 45 | 43 |
| 46 def traverse(current_elem, overwrite): | |
| 47 if isinstance(overwrite, dict): | |
| 48 for key, value in overwrite.items(): | |
| 49 if isinstance(key, tuple): | |
| 50 prefix, element = key | |
| 51 next_elem = current_elem.find( | |
| 52 '{{{}}}{}'.format(namespaces[prefix], element)) | |
| 53 traverse(next_elem, value) | |
| 54 else: | |
| 55 current_elem.attrib.update(overwrite) | |
| 56 else: | |
| 57 current_elem.text = overwrite | |
| 58 | |
| 59 translation = load_translation(files, defaultLocale) | |
| 60 name_key = 'name' if release_build else 'name_devbuild' | |
| 61 v_min, v_max = metadata.get('compat', 'windows').split('/') | 44 v_min, v_max = metadata.get('compat', 'windows').split('/') |
| 62 | 45 |
| 63 overwrite = { | 46 filenames = [] |
|
Sebastian Noack
2018/07/12 14:43:10
Instead of a dictionaries you might want to use li
tlucas
2018/07/19 12:30:28
Since order doesn't matter in the resulting XML (o
Sebastian Noack
2018/07/25 19:18:41
Originally I thought that nodes may be created for
tlucas
2018/08/08 09:35:54
It looks like adding the default namespaces with a
| |
| 64 ('', 'Identity'): { | 47 |
| 65 'Name': packager.get_app_id(release_build, metadata), | 48 for name, path in metadata.items('appx_assets'): |
| 66 'Publisher': metadata.get('general', 'publisher_id'), | 49 path = os.path.join(base_dir, path) |
| 67 }, | 50 icon_path = '{}/{}'.format(ASSETS_DIR, name) |
| 68 ('', 'Properties'): { | 51 |
| 69 ('', 'DisplayName'): translation[name_key]['message'], | 52 files.read(path, icon_path) |
|
Sebastian Noack
2018/07/12 14:43:10
It seems we can just leave the DisplayName alone.
tlucas
2018/07/19 12:30:27
True indeed, removed this.
| |
| 70 ('', 'PublisherDisplayName'): metadata.get('general', 'author'), | 53 filenames.append(icon_path) |
| 71 ('', 'Logo'): 'Assets/logo_50.png', | 54 |
| 72 }, | 55 assets = packagerChrome.makeIcons(files, filenames) |
| 73 ('', 'Dependencies'): { | 56 |
| 74 ('', 'TargetDeviceFamily'): { | 57 author = metadata.get('general', 'author') |
| 75 'MaxVersionTested': v_max, | 58 |
| 76 'MinVersion': v_min, | 59 overrides = [ |
| 77 }, | 60 ('_d:Identity', None, [ |
| 78 }, | 61 ('Name', packager.get_app_id(release_build, metadata)), |
| 79 ('', 'Applications'): { | 62 ('Publisher', metadata.get('general', 'publisher_id')), |
| 80 ('', 'Application'): { | 63 ]), |
| 81 ('uap', 'VisualElements'): { | 64 ('_d:Properties/_d:PublisherDisplayName', author, []), |
|
Sebastian Noack
2018/07/12 14:43:10
Perhaps this structure is more readable if keys ar
tlucas
2018/07/19 12:30:27
You may be right about it being (only imho) a bit
Sebastian Noack
2018/07/25 19:18:41
What I meant is using "uap:VisualElements" as key
tlucas
2018/08/08 09:35:54
Done.
| |
| 82 'Square150x150Logo': 'Assets/logo_150.png', | 65 ('_d:Properties/_d:Logo', assets[50], []), |
| 83 'Square44x44Logo': 'Assets/logo_44.png', | 66 ('_d:Dependencies/_d:TargetDeviceFamily', None, [ |
|
Sebastian Noack
2018/07/12 14:43:10
Instead of hard-coding the assets, we could identi
tlucas
2018/07/19 12:30:27
Done.
| |
| 84 }, | 67 ('MaxVersionTested', v_max), |
| 85 }, | 68 ('MinVersion', v_min), |
| 86 }, | 69 ]), |
| 87 } | 70 ('_d:Applications/_d:Application/uap:VisualElements', None, [ |
| 71 ('Square150x150Logo', assets[150]), | |
| 72 ('Square44x44Logo', assets[44]), | |
| 73 ]), | |
| 74 ] | |
| 88 | 75 |
| 89 tree = ElementTree.parse(manifest_path) | 76 tree = ElementTree.parse(manifest_path) |
| 90 root = tree.getroot() | 77 root = tree.getroot() |
| 91 | 78 |
| 92 traverse(root, overwrite) | 79 for xpath, text, attributes in overrides: |
| 80 element = root.find(xpath, namespaces) | |
| 81 if text: | |
| 82 element.text = text | |
| 83 for attr, value in attributes: | |
| 84 element.set(attr, value) | |
| 93 | 85 |
| 94 tree.write(manifest_path, encoding='utf-8', xml_declaration=True) | 86 tree.write(manifest_path, encoding='utf-8', xml_declaration=True) |
| 95 | 87 |
| 96 | 88 |
| 97 def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API. | 89 def createBuild(baseDir, type='edge', outFile=None, # noqa: preserve API. |
| 98 buildNum=None, releaseBuild=False, keyFile=None, | 90 buildNum=None, releaseBuild=False, keyFile=None, |
| 99 devenv=False): | 91 devenv=False): |
| 100 | 92 |
| 101 metadata = packager.readMetadata(baseDir, type) | 93 metadata = packager.readMetadata(baseDir, type) |
| 102 version = packager.getBuildVersion(baseDir, metadata, releaseBuild, | 94 version = packager.getBuildVersion(baseDir, metadata, releaseBuild, |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 154 | 146 |
| 155 with ZipFile(zipped, 'r') as zip_file: | 147 with ZipFile(zipped, 'r') as zip_file: |
| 156 zip_file.extractall(src_dir) | 148 zip_file.extractall(src_dir) |
| 157 | 149 |
| 158 cmd_env = os.environ.copy() | 150 cmd_env = os.environ.copy() |
| 159 cmd_env['SRC_FOLDER'] = src_dir | 151 cmd_env['SRC_FOLDER'] = src_dir |
| 160 cmd_env['EXT_FOLDER'] = ext_dir | 152 cmd_env['EXT_FOLDER'] = ext_dir |
| 161 | 153 |
| 162 manifold_folder = os.path.join(ext_dir, 'MSGname', 'edgeextension') | 154 manifold_folder = os.path.join(ext_dir, 'MSGname', 'edgeextension') |
| 163 manifest_folder = os.path.join(manifold_folder, 'manifest') | 155 manifest_folder = os.path.join(manifold_folder, 'manifest') |
| 164 asset_folder = os.path.join(manifest_folder, 'Assets') | 156 asset_folder = os.path.join(manifest_folder, ASSETS_DIR) |
| 165 | 157 |
| 166 # prepare the extension with manifoldjs | 158 # prepare the extension with manifoldjs |
| 167 cmd = ['npm', 'run', '--silent', 'build-edge'] | 159 cmd = ['npm', 'run', '--silent', 'build-edge'] |
| 168 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__)) | 160 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__)) |
| 169 | 161 |
| 170 # update incomplete appxmanifest | 162 # update incomplete appxmanifest |
| 171 intermediate_manifest = os.path.join(manifest_folder, MANIFEST) | 163 intermediate_manifest = os.path.join(manifest_folder, MANIFEST) |
| 172 update_appx_manifest(intermediate_manifest, files, metadata, | 164 update_appx_manifest(intermediate_manifest, baseDir, files, metadata, |
| 173 releaseBuild) | 165 releaseBuild) |
| 174 | 166 |
| 175 # cleanup placeholders, copy actual images | 167 # cleanup placeholders, copy actual images |
| 176 shutil.rmtree(asset_folder) | 168 shutil.rmtree(asset_folder) |
| 177 os.mkdir(asset_folder) | 169 os.mkdir(asset_folder) |
| 178 if metadata.has_section('appx_assets'): | 170 if metadata.has_section('appx_assets'): |
| 179 for name, path in metadata.items('appx_assets'): | 171 for name, path in metadata.items('appx_assets'): |
| 180 path = os.path.join(baseDir, path) | 172 path = os.path.join(baseDir, path) |
| 181 target = os.path.join(asset_folder, name) | 173 target = os.path.join(asset_folder, name) |
| 182 shutil.copyfile(path, target) | 174 shutil.copyfile(path, target) |
| 183 | 175 |
| 184 # package app with manifoldjs | 176 # package app with manifoldjs |
| 185 cmd = ['npm', 'run', '--silent', 'package-edge'] | 177 cmd = ['npm', 'run', '--silent', 'package-edge'] |
| 186 | 178 |
| 187 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__)) | 179 subprocess.check_call(cmd, env=cmd_env, cwd=os.path.dirname(__file__)) |
| 188 | 180 |
| 189 package = os.path.join(manifold_folder, 'package', | 181 package = os.path.join(manifold_folder, 'package', |
| 190 'edgeExtension.appx') | 182 'edgeExtension.appx') |
| 191 | 183 |
| 192 shutil.copyfile(package, outfile) | 184 shutil.copyfile(package, outfile) |
| 193 finally: | 185 finally: |
| 194 shutil.rmtree(tmp_dir, ignore_errors=True) | 186 shutil.rmtree(tmp_dir, ignore_errors=True) |
| LEFT | RIGHT |