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