addon-sdk/source/python-lib/cuddlefish/xpi.py

changeset 0
6474c204b198
     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()

mercurial