xpi.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. import os
  5. import zipfile
  6. import simplejson as json
  7. from cuddlefish.util import filter_filenames, filter_dirnames
  8. class HarnessOptionAlreadyDefinedError(Exception):
  9. """You cannot use --harness-option on keys that already exist in
  10. harness-options.json"""
  11. ZIPSEP = "/" # always use "/" in zipfiles
  12. def make_zipfile_path(localroot, localpath):
  13. return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep))
  14. def mkzipdir(zf, path):
  15. dirinfo = zipfile.ZipInfo(path)
  16. dirinfo.external_attr = int("040755", 8) << 16L
  17. zf.writestr(dirinfo, "")
  18. def build_xpi(template_root_dir, manifest, xpi_path,
  19. harness_options, limit_to=None, extra_harness_options={},
  20. bundle_sdk=True, pkgdir=""):
  21. IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf",
  22. "application.ini", xpi_path]
  23. files_to_copy = {} # maps zipfile path to local-disk abspath
  24. dirs_to_create = set() # zipfile paths, no trailing slash
  25. zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED)
  26. open('.install.rdf', 'w').write(str(manifest))
  27. zf.write('.install.rdf', 'install.rdf')
  28. os.remove('.install.rdf')
  29. # Handle add-on icon
  30. if 'icon' in harness_options:
  31. zf.write(str(harness_options['icon']), 'icon.png')
  32. del harness_options['icon']
  33. if 'icon64' in harness_options:
  34. zf.write(str(harness_options['icon64']), 'icon64.png')
  35. del harness_options['icon64']
  36. # chrome.manifest
  37. if os.path.isfile(os.path.join(pkgdir, 'chrome.manifest')):
  38. files_to_copy['chrome.manifest'] = os.path.join(pkgdir, 'chrome.manifest')
  39. # chrome folder (would contain content, skin, and locale folders typically)
  40. folder = 'chrome'
  41. if os.path.exists(os.path.join(pkgdir, folder)):
  42. dirs_to_create.add('chrome')
  43. # cp -r folder
  44. abs_dirname = os.path.join(pkgdir, folder)
  45. for dirpath, dirnames, filenames in os.walk(abs_dirname):
  46. goodfiles = list(filter_filenames(filenames, IGNORED_FILES))
  47. dirnames[:] = filter_dirnames(dirnames)
  48. for dirname in dirnames:
  49. arcpath = make_zipfile_path(template_root_dir,
  50. os.path.join(dirpath, dirname))
  51. dirs_to_create.add(arcpath)
  52. for filename in goodfiles:
  53. abspath = os.path.join(dirpath, filename)
  54. arcpath = ZIPSEP.join(
  55. [folder,
  56. make_zipfile_path(abs_dirname, os.path.join(dirpath, filename)),
  57. ])
  58. files_to_copy[str(arcpath)] = str(abspath)
  59. # Handle simple-prefs
  60. if 'preferences' in harness_options:
  61. from options_xul import parse_options, validate_prefs
  62. validate_prefs(harness_options["preferences"])
  63. opts_xul = parse_options(harness_options["preferences"],
  64. harness_options["jetpackID"],
  65. harness_options["preferencesBranch"])
  66. open('.options.xul', 'wb').write(opts_xul.encode("utf-8"))
  67. zf.write('.options.xul', 'options.xul')
  68. os.remove('.options.xul')
  69. from options_defaults import parse_options_defaults
  70. prefs_js = parse_options_defaults(harness_options["preferences"],
  71. harness_options["preferencesBranch"])
  72. open('.prefs.js', 'wb').write(prefs_js.encode("utf-8"))
  73. else:
  74. open('.prefs.js', 'wb').write("")
  75. zf.write('.prefs.js', 'defaults/preferences/prefs.js')
  76. os.remove('.prefs.js')
  77. for dirpath, dirnames, filenames in os.walk(template_root_dir):
  78. filenames = list(filter_filenames(filenames, IGNORED_FILES))
  79. dirnames[:] = filter_dirnames(dirnames)
  80. for dirname in dirnames:
  81. arcpath = make_zipfile_path(template_root_dir,
  82. os.path.join(dirpath, dirname))
  83. dirs_to_create.add(arcpath)
  84. for filename in filenames:
  85. abspath = os.path.join(dirpath, filename)
  86. arcpath = make_zipfile_path(template_root_dir, abspath)
  87. files_to_copy[arcpath] = abspath
  88. # `packages` attribute contains a dictionnary of dictionnary
  89. # of all packages sections directories
  90. for packageName in harness_options['packages']:
  91. base_arcpath = ZIPSEP.join(['resources', packageName])
  92. # Eventually strip sdk files. We need to do that in addition to the
  93. # whilelist as the whitelist is only used for `cfx xpi`:
  94. if not bundle_sdk and packageName == 'addon-sdk':
  95. continue
  96. # Always write the top directory, even if it contains no files, since
  97. # the harness will try to access it.
  98. dirs_to_create.add(base_arcpath)
  99. for sectionName in harness_options['packages'][packageName]:
  100. abs_dirname = harness_options['packages'][packageName][sectionName]
  101. base_arcpath = ZIPSEP.join(['resources', packageName, sectionName])
  102. # Always write the top directory, even if it contains no files, since
  103. # the harness will try to access it.
  104. dirs_to_create.add(base_arcpath)
  105. # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/
  106. for dirpath, dirnames, filenames in os.walk(abs_dirname):
  107. goodfiles = list(filter_filenames(filenames, IGNORED_FILES))
  108. dirnames[:] = filter_dirnames(dirnames)
  109. for filename in goodfiles:
  110. abspath = os.path.join(dirpath, filename)
  111. if limit_to is not None and abspath not in limit_to:
  112. continue # strip unused files
  113. arcpath = ZIPSEP.join(
  114. ['resources',
  115. packageName,
  116. sectionName,
  117. make_zipfile_path(abs_dirname,
  118. os.path.join(dirpath, filename)),
  119. ])
  120. files_to_copy[str(arcpath)] = str(abspath)
  121. del harness_options['packages']
  122. locales_json_data = {"locales": []}
  123. mkzipdir(zf, "locale/")
  124. for language in sorted(harness_options['locale']):
  125. locales_json_data["locales"].append(language)
  126. locale = harness_options['locale'][language]
  127. # Be carefull about strings, we need to always ensure working with UTF-8
  128. jsonStr = json.dumps(locale, indent=1, sort_keys=True, ensure_ascii=False)
  129. info = zipfile.ZipInfo('locale/' + language + '.json')
  130. info.external_attr = 0644 << 16L
  131. zf.writestr(info, jsonStr.encode( "utf-8" ))
  132. del harness_options['locale']
  133. jsonStr = json.dumps(locales_json_data, ensure_ascii=True) +"\n"
  134. info = zipfile.ZipInfo('locales.json')
  135. info.external_attr = 0644 << 16L
  136. zf.writestr(info, jsonStr.encode("utf-8"))
  137. # now figure out which directories we need: all retained files parents
  138. for arcpath in files_to_copy:
  139. bits = arcpath.split("/")
  140. for i in range(1,len(bits)):
  141. parentpath = ZIPSEP.join(bits[0:i])
  142. dirs_to_create.add(parentpath)
  143. # Create zipfile in alphabetical order, with each directory before its
  144. # files
  145. for name in sorted(dirs_to_create.union(set(files_to_copy))):
  146. if name in dirs_to_create:
  147. mkzipdir(zf, name+"/")
  148. if name in files_to_copy:
  149. zf.write(files_to_copy[name], name)
  150. # Add extra harness options
  151. harness_options = harness_options.copy()
  152. for key,value in extra_harness_options.items():
  153. if key in harness_options:
  154. msg = "Can't use --harness-option for existing key '%s'" % key
  155. raise HarnessOptionAlreadyDefinedError(msg)
  156. harness_options[key] = value
  157. # Write harness-options.json
  158. open('.options.json', 'w').write(json.dumps(harness_options, indent=1,
  159. sort_keys=True))
  160. zf.write('.options.json', 'harness-options.json')
  161. os.remove('.options.json')
  162. zf.close()