1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/python-lib/cuddlefish/xpi.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,191 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +import os 1.9 +import zipfile 1.10 +import simplejson as json 1.11 +from cuddlefish.util import filter_filenames, filter_dirnames 1.12 + 1.13 +class HarnessOptionAlreadyDefinedError(Exception): 1.14 + """You cannot use --harness-option on keys that already exist in 1.15 + harness-options.json""" 1.16 + 1.17 +ZIPSEP = "/" # always use "/" in zipfiles 1.18 + 1.19 +def make_zipfile_path(localroot, localpath): 1.20 + return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep)) 1.21 + 1.22 +def mkzipdir(zf, path): 1.23 + dirinfo = zipfile.ZipInfo(path) 1.24 + dirinfo.external_attr = int("040755", 8) << 16L 1.25 + zf.writestr(dirinfo, "") 1.26 + 1.27 +def build_xpi(template_root_dir, manifest, xpi_path, 1.28 + harness_options, limit_to=None, extra_harness_options={}, 1.29 + bundle_sdk=True, pkgdir=""): 1.30 + IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf", 1.31 + "application.ini", xpi_path] 1.32 + 1.33 + files_to_copy = {} # maps zipfile path to local-disk abspath 1.34 + dirs_to_create = set() # zipfile paths, no trailing slash 1.35 + 1.36 + zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED) 1.37 + 1.38 + open('.install.rdf', 'w').write(str(manifest)) 1.39 + zf.write('.install.rdf', 'install.rdf') 1.40 + os.remove('.install.rdf') 1.41 + 1.42 + # Handle add-on icon 1.43 + if 'icon' in harness_options: 1.44 + zf.write(str(harness_options['icon']), 'icon.png') 1.45 + del harness_options['icon'] 1.46 + 1.47 + if 'icon64' in harness_options: 1.48 + zf.write(str(harness_options['icon64']), 'icon64.png') 1.49 + del harness_options['icon64'] 1.50 + 1.51 + # chrome.manifest 1.52 + if os.path.isfile(os.path.join(pkgdir, 'chrome.manifest')): 1.53 + files_to_copy['chrome.manifest'] = os.path.join(pkgdir, 'chrome.manifest') 1.54 + 1.55 + # chrome folder (would contain content, skin, and locale folders typically) 1.56 + folder = 'chrome' 1.57 + if os.path.exists(os.path.join(pkgdir, folder)): 1.58 + dirs_to_create.add('chrome') 1.59 + # cp -r folder 1.60 + abs_dirname = os.path.join(pkgdir, folder) 1.61 + for dirpath, dirnames, filenames in os.walk(abs_dirname): 1.62 + goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) 1.63 + dirnames[:] = filter_dirnames(dirnames) 1.64 + for dirname in dirnames: 1.65 + arcpath = make_zipfile_path(template_root_dir, 1.66 + os.path.join(dirpath, dirname)) 1.67 + dirs_to_create.add(arcpath) 1.68 + for filename in goodfiles: 1.69 + abspath = os.path.join(dirpath, filename) 1.70 + arcpath = ZIPSEP.join( 1.71 + [folder, 1.72 + make_zipfile_path(abs_dirname, os.path.join(dirpath, filename)), 1.73 + ]) 1.74 + files_to_copy[str(arcpath)] = str(abspath) 1.75 + 1.76 + # Handle simple-prefs 1.77 + if 'preferences' in harness_options: 1.78 + from options_xul import parse_options, validate_prefs 1.79 + 1.80 + validate_prefs(harness_options["preferences"]) 1.81 + 1.82 + opts_xul = parse_options(harness_options["preferences"], 1.83 + harness_options["jetpackID"], 1.84 + harness_options["preferencesBranch"]) 1.85 + open('.options.xul', 'wb').write(opts_xul.encode("utf-8")) 1.86 + zf.write('.options.xul', 'options.xul') 1.87 + os.remove('.options.xul') 1.88 + 1.89 + from options_defaults import parse_options_defaults 1.90 + prefs_js = parse_options_defaults(harness_options["preferences"], 1.91 + harness_options["preferencesBranch"]) 1.92 + open('.prefs.js', 'wb').write(prefs_js.encode("utf-8")) 1.93 + 1.94 + else: 1.95 + open('.prefs.js', 'wb').write("") 1.96 + 1.97 + zf.write('.prefs.js', 'defaults/preferences/prefs.js') 1.98 + os.remove('.prefs.js') 1.99 + 1.100 + 1.101 + for dirpath, dirnames, filenames in os.walk(template_root_dir): 1.102 + filenames = list(filter_filenames(filenames, IGNORED_FILES)) 1.103 + dirnames[:] = filter_dirnames(dirnames) 1.104 + for dirname in dirnames: 1.105 + arcpath = make_zipfile_path(template_root_dir, 1.106 + os.path.join(dirpath, dirname)) 1.107 + dirs_to_create.add(arcpath) 1.108 + for filename in filenames: 1.109 + abspath = os.path.join(dirpath, filename) 1.110 + arcpath = make_zipfile_path(template_root_dir, abspath) 1.111 + files_to_copy[arcpath] = abspath 1.112 + 1.113 + # `packages` attribute contains a dictionnary of dictionnary 1.114 + # of all packages sections directories 1.115 + for packageName in harness_options['packages']: 1.116 + base_arcpath = ZIPSEP.join(['resources', packageName]) 1.117 + # Eventually strip sdk files. We need to do that in addition to the 1.118 + # whilelist as the whitelist is only used for `cfx xpi`: 1.119 + if not bundle_sdk and packageName == 'addon-sdk': 1.120 + continue 1.121 + # Always write the top directory, even if it contains no files, since 1.122 + # the harness will try to access it. 1.123 + dirs_to_create.add(base_arcpath) 1.124 + for sectionName in harness_options['packages'][packageName]: 1.125 + abs_dirname = harness_options['packages'][packageName][sectionName] 1.126 + base_arcpath = ZIPSEP.join(['resources', packageName, sectionName]) 1.127 + # Always write the top directory, even if it contains no files, since 1.128 + # the harness will try to access it. 1.129 + dirs_to_create.add(base_arcpath) 1.130 + # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/ 1.131 + for dirpath, dirnames, filenames in os.walk(abs_dirname): 1.132 + goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) 1.133 + dirnames[:] = filter_dirnames(dirnames) 1.134 + for filename in goodfiles: 1.135 + abspath = os.path.join(dirpath, filename) 1.136 + if limit_to is not None and abspath not in limit_to: 1.137 + continue # strip unused files 1.138 + arcpath = ZIPSEP.join( 1.139 + ['resources', 1.140 + packageName, 1.141 + sectionName, 1.142 + make_zipfile_path(abs_dirname, 1.143 + os.path.join(dirpath, filename)), 1.144 + ]) 1.145 + files_to_copy[str(arcpath)] = str(abspath) 1.146 + del harness_options['packages'] 1.147 + 1.148 + locales_json_data = {"locales": []} 1.149 + mkzipdir(zf, "locale/") 1.150 + for language in sorted(harness_options['locale']): 1.151 + locales_json_data["locales"].append(language) 1.152 + locale = harness_options['locale'][language] 1.153 + # Be carefull about strings, we need to always ensure working with UTF-8 1.154 + jsonStr = json.dumps(locale, indent=1, sort_keys=True, ensure_ascii=False) 1.155 + info = zipfile.ZipInfo('locale/' + language + '.json') 1.156 + info.external_attr = 0644 << 16L 1.157 + zf.writestr(info, jsonStr.encode( "utf-8" )) 1.158 + del harness_options['locale'] 1.159 + 1.160 + jsonStr = json.dumps(locales_json_data, ensure_ascii=True) +"\n" 1.161 + info = zipfile.ZipInfo('locales.json') 1.162 + info.external_attr = 0644 << 16L 1.163 + zf.writestr(info, jsonStr.encode("utf-8")) 1.164 + 1.165 + # now figure out which directories we need: all retained files parents 1.166 + for arcpath in files_to_copy: 1.167 + bits = arcpath.split("/") 1.168 + for i in range(1,len(bits)): 1.169 + parentpath = ZIPSEP.join(bits[0:i]) 1.170 + dirs_to_create.add(parentpath) 1.171 + 1.172 + # Create zipfile in alphabetical order, with each directory before its 1.173 + # files 1.174 + for name in sorted(dirs_to_create.union(set(files_to_copy))): 1.175 + if name in dirs_to_create: 1.176 + mkzipdir(zf, name+"/") 1.177 + if name in files_to_copy: 1.178 + zf.write(files_to_copy[name], name) 1.179 + 1.180 + # Add extra harness options 1.181 + harness_options = harness_options.copy() 1.182 + for key,value in extra_harness_options.items(): 1.183 + if key in harness_options: 1.184 + msg = "Can't use --harness-option for existing key '%s'" % key 1.185 + raise HarnessOptionAlreadyDefinedError(msg) 1.186 + harness_options[key] = value 1.187 + 1.188 + # Write harness-options.json 1.189 + open('.options.json', 'w').write(json.dumps(harness_options, indent=1, 1.190 + sort_keys=True)) 1.191 + zf.write('.options.json', 'harness-options.json') 1.192 + os.remove('.options.json') 1.193 + 1.194 + zf.close()