OLD | NEW |
1 # coding: utf-8 | 1 # coding: utf-8 |
2 | 2 |
3 # This file is part of the Adblock Plus build tools, | 3 # This file is part of the Adblock Plus build tools, |
4 # Copyright (C) 2006-2012 Eyeo GmbH | 4 # Copyright (C) 2006-2012 Eyeo GmbH |
5 # | 5 # |
6 # Adblock Plus is free software: you can redistribute it and/or modify | 6 # Adblock Plus is free software: you can redistribute it and/or modify |
7 # it under the terms of the GNU General Public License version 3 as | 7 # it under the terms of the GNU General Public License version 3 as |
8 # published by the Free Software Foundation. | 8 # published by the Free Software Foundation. |
9 # | 9 # |
10 # Adblock Plus is distributed in the hope that it will be useful, | 10 # Adblock Plus is distributed in the hope that it will be useful, |
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 # GNU General Public License for more details. | 13 # GNU General Public License for more details. |
14 # | 14 # |
15 # You should have received a copy of the GNU General Public License | 15 # You should have received a copy of the GNU General Public License |
16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 |
18 import os, sys, re, hashlib, base64, urllib, json | 18 import os, sys, re, hashlib, base64, urllib, json |
19 from ConfigParser import SafeConfigParser | 19 from ConfigParser import SafeConfigParser |
20 from StringIO import StringIO | 20 from StringIO import StringIO |
21 from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED | |
22 import xml.dom.minidom as minidom | 21 import xml.dom.minidom as minidom |
23 import buildtools.localeTools as localeTools | 22 import buildtools.localeTools as localeTools |
24 | 23 |
25 from packager import getDefaultFileName, readMetadata, getBuildVersion, getTempl
ate | 24 from packager import getDefaultFileName, readMetadata, getBuildVersion, getTempl
ate, Files |
26 | 25 |
27 KNOWN_APPS = { | 26 KNOWN_APPS = { |
28 'conkeror': '{a79fe89b-6662-4ff4-8e88-09950ad4dfde}', | 27 'conkeror': '{a79fe89b-6662-4ff4-8e88-09950ad4dfde}', |
29 'emusic': 'dlm@emusic.com', | 28 'emusic': 'dlm@emusic.com', |
30 'fennec': '{a23983c0-fd0e-11dc-95ff-0800200c9a66}', | 29 'fennec': '{a23983c0-fd0e-11dc-95ff-0800200c9a66}', |
31 'fennec2': '{aa3c5121-dab2-40e2-81ca-7ea25febc110}', | 30 'fennec2': '{aa3c5121-dab2-40e2-81ca-7ea25febc110}', |
32 'firefox': '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}', | 31 'firefox': '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}', |
33 'midbrowser': '{aa5ca914-c309-495d-91cf-3141bbb04115}', | 32 'midbrowser': '{aa5ca914-c309-495d-91cf-3141bbb04115}', |
34 'prism': 'prism@developer.mozilla.org', | 33 'prism': 'prism@developer.mozilla.org', |
35 'seamonkey': '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}', | 34 'seamonkey': '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}', |
(...skipping 12 matching lines...) Expand all Loading... |
48 | 47 |
49 def getChromeSubdirs(baseDir, locales): | 48 def getChromeSubdirs(baseDir, locales): |
50 result = {} | 49 result = {} |
51 chromeDir = getChromeDir(baseDir) | 50 chromeDir = getChromeDir(baseDir) |
52 for subdir in ('content', 'skin'): | 51 for subdir in ('content', 'skin'): |
53 result[subdir] = os.path.join(chromeDir, subdir) | 52 result[subdir] = os.path.join(chromeDir, subdir) |
54 for locale in locales: | 53 for locale in locales: |
55 result['locale/%s' % locale] = os.path.join(chromeDir, 'locale', locale) | 54 result['locale/%s' % locale] = os.path.join(chromeDir, 'locale', locale) |
56 return result | 55 return result |
57 | 56 |
58 def getXPIFiles(baseDir): | 57 def getPackageFiles(params): |
59 for file in ('components', 'modules', 'lib', 'resources', 'defaults', 'chrome.
manifest', 'icon.png', 'icon64.png'): | 58 result = set(('chrome', 'components', 'modules', 'lib', 'resources', 'defaults
', 'chrome.manifest', 'icon.png', 'icon64.png',)) |
60 yield os.path.join(baseDir, file) | 59 |
| 60 baseDir = params['baseDir'] |
61 for file in os.listdir(baseDir): | 61 for file in os.listdir(baseDir): |
62 if file.endswith('.js') or file.endswith('.xml'): | 62 if file.endswith('.js') or file.endswith('.xml'): |
63 yield os.path.join(baseDir, file) | 63 result.add(file) |
| 64 return result |
64 | 65 |
65 def getIgnoredFiles(params): | 66 def getIgnoredFiles(params): |
66 result = ['.incomplete', 'meta.properties'] | 67 result = set(('.incomplete', 'meta.properties',)) |
67 if params['releaseBuild']: | 68 if params['releaseBuild']: |
68 result.append('timeline.js') | 69 result.add('timeline.js') |
69 return result | 70 return result |
70 | 71 |
71 def isValidLocale(localesDir, dir, includeIncomplete=False): | 72 def isValidLocale(localesDir, dir, includeIncomplete=False): |
72 if re.search(r'[^\w\-]', dir): | 73 if re.search(r'[^\w\-]', dir): |
73 return False | 74 return False |
74 if not os.path.isdir(os.path.join(localesDir, dir)): | 75 if not os.path.isdir(os.path.join(localesDir, dir)): |
75 return False | 76 return False |
76 if not includeIncomplete and os.path.exists(os.path.join(localesDir, dir, '.in
complete')): | 77 if not includeIncomplete and os.path.exists(os.path.join(localesDir, dir, '.in
complete')): |
77 return False | 78 return False |
78 return True | 79 return True |
79 | 80 |
80 def getLocales(baseDir, includeIncomplete=False): | 81 def getLocales(baseDir, includeIncomplete=False): |
81 global defaultLocale | 82 global defaultLocale |
82 localesDir = getLocalesDir(baseDir) | 83 localesDir = getLocalesDir(baseDir) |
83 locales = filter(lambda dir: isValidLocale(localesDir, dir, includeIncomplete
), os.listdir(localesDir)) | 84 locales = filter(lambda dir: isValidLocale(localesDir, dir, includeIncomplete
), os.listdir(localesDir)) |
84 locales.sort(key=lambda x: '!' if x == defaultLocale else x) | 85 locales.sort(key=lambda x: '!' if x == defaultLocale else x) |
85 return locales | 86 return locales |
86 | 87 |
87 def processFile(path, data, params): | 88 def processFile(path, data, params): |
88 if not re.search(r'\.(manifest|xul|jsm?|xml|xhtml|rdf|dtd|properties|css)$', p
ath): | |
89 return data | |
90 | |
91 data = re.sub(r'\r', '', data) | |
92 data = data.replace('{{VERSION}}', params['version']) | |
93 | |
94 whitespaceRegExp = re.compile(r'^( )+', re.M) | |
95 data = re.sub(whitespaceRegExp, lambda match: '\t' * (len(match.group(0)) / 2)
, data) | |
96 | |
97 if path.endswith('.manifest') and data.find('{{LOCALE}}') >= 0: | 89 if path.endswith('.manifest') and data.find('{{LOCALE}}') >= 0: |
98 localesRegExp = re.compile(r'^(.*?){{LOCALE}}(.*?){{LOCALE}}(.*)$', re.M) | 90 localesRegExp = re.compile(r'^(.*?){{LOCALE}}(.*?){{LOCALE}}(.*)$', re.M) |
99 replacement = '\n'.join(map(lambda locale: r'\1%s\2%s\3' % (locale, locale),
params['locales'])) | 91 replacement = '\n'.join(map(lambda locale: r'\1%s\2%s\3' % (locale, locale),
params['locales'])) |
100 data = re.sub(localesRegExp, replacement, data) | 92 data = re.sub(localesRegExp, replacement, data) |
101 | 93 |
102 if params['releaseBuild'] and path.endswith('.js'): | 94 if params['releaseBuild'] and path.endswith('.js'): |
103 # Remove timeline calls from release builds | 95 # Remove timeline calls from release builds |
104 timelineRegExp1 = re.compile(r'^.*\b[tT]imeLine\.(\w+)\(.*', re.M) | 96 timelineRegExp1 = re.compile(r'^.*\b[tT]imeLine\.(\w+)\(.*', re.M) |
105 timelineRegExp2 = re.compile(r'^.*\brequire\(\"timeline\"\).*', re.M) | 97 timelineRegExp2 = re.compile(r'^.*\brequire\(\"timeline\"\).*', re.M) |
106 data = re.sub(timelineRegExp1, '', data) | 98 data = re.sub(timelineRegExp1, '', data) |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 main.append(value) | 141 main.append(value) |
150 return main + sorted(additional, key=unicode.lower) | 142 return main + sorted(additional, key=unicode.lower) |
151 | 143 |
152 def initTranslators(localeMetadata): | 144 def initTranslators(localeMetadata): |
153 for locale in localeMetadata.itervalues(): | 145 for locale in localeMetadata.itervalues(): |
154 if 'translator' in locale: | 146 if 'translator' in locale: |
155 locale['translators'] = sorted(map(lambda t: t.strip(), locale['translator
'].split(',')), key=unicode.lower) | 147 locale['translators'] = sorted(map(lambda t: t.strip(), locale['translator
'].split(',')), key=unicode.lower) |
156 else: | 148 else: |
157 locale['translators'] = [] | 149 locale['translators'] = [] |
158 | 150 |
159 def createManifest(baseDir, params): | 151 def createManifest(params): |
160 global KNOWN_APPS, defaultLocale | 152 global KNOWN_APPS, defaultLocale |
161 template = getTemplate('install.rdf.tmpl', autoEscape=True) | 153 template = getTemplate('install.rdf.tmpl', autoEscape=True) |
162 templateData = dict(params) | 154 templateData = dict(params) |
163 templateData['localeMetadata'] = readLocaleMetadata(baseDir, params['locales']
) | 155 templateData['localeMetadata'] = readLocaleMetadata(params['baseDir'], params[
'locales']) |
164 initTranslators(templateData['localeMetadata']) | 156 initTranslators(templateData['localeMetadata']) |
165 templateData['KNOWN_APPS'] = KNOWN_APPS | 157 templateData['KNOWN_APPS'] = KNOWN_APPS |
166 templateData['defaultLocale'] = defaultLocale | 158 templateData['defaultLocale'] = defaultLocale |
167 return template.render(templateData).encode('utf-8') | 159 return template.render(templateData).encode('utf-8') |
168 | 160 |
169 def readFile(files, params, path, name): | 161 def fixupLocales(params, files): |
170 ignoredFiles = getIgnoredFiles(params) | |
171 if os.path.isdir(path): | |
172 for file in os.listdir(path): | |
173 if file in ignoredFiles: | |
174 continue | |
175 readFile(files, params, os.path.join(path, file), '%s/%s' % (name, file)) | |
176 else: | |
177 file = open(path, 'rb') | |
178 data = processFile(path, file.read(), params) | |
179 file.close() | |
180 files[name] = data | |
181 | |
182 def fixupLocales(baseDir, files, params): | |
183 global defaultLocale | 162 global defaultLocale |
184 | 163 |
185 # Read in default locale data, it might not be included in files | 164 # Read in default locale data, it might not be included in files |
186 defaultLocaleDir = os.path.join(getLocalesDir(baseDir), defaultLocale) | 165 defaultLocaleDir = os.path.join(getLocalesDir(params['baseDir']), defaultLocal
e) |
187 reference = {} | 166 reference = {} |
188 ignoredFiles = getIgnoredFiles(params) | 167 ignoredFiles = getIgnoredFiles(params) |
189 for file in os.listdir(defaultLocaleDir): | 168 for file in os.listdir(defaultLocaleDir): |
190 path = os.path.join(defaultLocaleDir, file) | 169 path = os.path.join(defaultLocaleDir, file) |
191 if file in ignoredFiles or not os.path.isfile(path): | 170 if file in ignoredFiles or not os.path.isfile(path): |
192 continue | 171 continue |
193 data = localeTools.readFile(path) | 172 data = localeTools.readFile(path) |
194 if data: | 173 if data: |
195 reference[file] = data | 174 reference[file] = data |
196 | 175 |
197 for locale in params['locales']: | 176 for locale in params['locales']: |
198 for file in reference.iterkeys(): | 177 for file in reference.iterkeys(): |
199 path = 'chrome/locale/%s/%s' % (locale, file) | 178 path = 'chrome/locale/%s/%s' % (locale, file) |
200 if path in files: | 179 if path in files: |
201 data = localeTools.parseString(files[path].decode('utf-8'), path) | 180 data = localeTools.parseString(files[path].decode('utf-8'), path) |
202 for key, value in reference[file].iteritems(): | 181 for key, value in reference[file].iteritems(): |
203 if not key in data: | 182 if not key in data: |
204 files[path] += localeTools.generateStringEntry(key, value, path).enc
ode('utf-8') | 183 files[path] += localeTools.generateStringEntry(key, value, path).enc
ode('utf-8') |
205 else: | 184 else: |
206 files[path] = reference[file]['_origData'].encode('utf-8') | 185 files[path] = reference[file]['_origData'].encode('utf-8') |
207 | 186 |
208 def readXPIFiles(baseDir, params, files): | 187 def addMissingFiles(params, files): |
209 for path in getXPIFiles(baseDir): | |
210 if os.path.exists(path): | |
211 readFile(files, params, path, os.path.basename(path)) | |
212 | |
213 def addMissingFiles(baseDir, params, files): | |
214 templateData = { | 188 templateData = { |
215 'hasChrome': False, | 189 'hasChrome': False, |
216 'hasChromeRequires': False, | 190 'hasChromeRequires': False, |
217 'hasShutdownHandlers': False, | 191 'hasShutdownHandlers': False, |
218 'hasVersionPref': False, | 192 'hasVersionPref': False, |
219 'chromeWindows': [], | 193 'chromeWindows': [], |
220 'requires': {}, | 194 'requires': {}, |
221 'metadata': params['metadata'], | 195 'metadata': params['metadata'], |
222 'multicompartment': params['multicompartment'], | 196 'multicompartment': params['multicompartment'], |
223 'applications': dict((v, k) for k, v in KNOWN_APPS.iteritems()), | 197 'applications': dict((v, k) for k, v in KNOWN_APPS.iteritems()), |
(...skipping 27 matching lines...) Expand all Loading... |
251 for module in templateData['requires']: | 225 for module in templateData['requires']: |
252 moduleFile = 'lib/' + module + '.js' | 226 moduleFile = 'lib/' + module + '.js' |
253 if not moduleFile in files: | 227 if not moduleFile in files: |
254 import buildtools | 228 import buildtools |
255 path = os.path.join(buildtools.__path__[0], moduleFile) | 229 path = os.path.join(buildtools.__path__[0], moduleFile) |
256 if os.path.exists(path): | 230 if os.path.exists(path): |
257 missing.append((path, moduleFile)) | 231 missing.append((path, moduleFile)) |
258 if not len(missing): | 232 if not len(missing): |
259 break | 233 break |
260 for path, moduleFile in missing: | 234 for path, moduleFile in missing: |
261 readFile(files, params, path, moduleFile) | 235 files.read(path, moduleFile) |
262 checkScript(moduleFile) | 236 checkScript(moduleFile) |
263 | 237 |
264 template = getTemplate('bootstrap.js.tmpl') | 238 template = getTemplate('bootstrap.js.tmpl') |
265 files['bootstrap.js'] = processFile('bootstrap.js', template.render(templateDa
ta).encode('utf-8'), params) | 239 files['bootstrap.js'] = template.render(templateData).encode('utf-8') |
266 | 240 |
267 def signFiles(files, keyFile): | 241 def signFiles(files, keyFile): |
268 import M2Crypto | 242 import M2Crypto |
269 manifest = [] | 243 manifest = [] |
270 signature = [] | 244 signature = [] |
271 | 245 |
272 def getDigest(data): | 246 def getDigest(data): |
273 md5 = hashlib.md5() | 247 md5 = hashlib.md5() |
274 md5.update(data) | 248 md5.update(data) |
275 sha1 = hashlib.sha1() | 249 sha1 = hashlib.sha1() |
(...skipping 30 matching lines...) Expand all Loading... |
306 | 280 |
307 mime = M2Crypto.SMIME.SMIME() | 281 mime = M2Crypto.SMIME.SMIME() |
308 mime.load_key(keyFile) | 282 mime.load_key(keyFile) |
309 mime.set_x509_stack(stack) | 283 mime.set_x509_stack(stack) |
310 signature = mime.sign(M2Crypto.BIO.MemoryBuffer(files['META-INF/zigbert.sf'].e
ncode('utf-8')), M2Crypto.SMIME.PKCS7_DETACHED | M2Crypto.SMIME.PKCS7_BINARY) | 284 signature = mime.sign(M2Crypto.BIO.MemoryBuffer(files['META-INF/zigbert.sf'].e
ncode('utf-8')), M2Crypto.SMIME.PKCS7_DETACHED | M2Crypto.SMIME.PKCS7_BINARY) |
311 | 285 |
312 buffer = M2Crypto.BIO.MemoryBuffer() | 286 buffer = M2Crypto.BIO.MemoryBuffer() |
313 signature.write_der(buffer) | 287 signature.write_der(buffer) |
314 files['META-INF/zigbert.rsa'] = buffer.read() | 288 files['META-INF/zigbert.rsa'] = buffer.read() |
315 | 289 |
316 def writeXPI(files, outFile): | |
317 zip = ZipFile(outFile, 'w', ZIP_DEFLATED) | |
318 names = files.keys() | |
319 names.sort(key=lambda x: '!' if x == 'META-INF/zigbert.rsa' else x) | |
320 for name in names: | |
321 zip.writestr(name, files[name]) | |
322 zip.close() | |
323 | |
324 def createBuild(baseDir, outFile=None, locales=None, buildNum=None, releaseBuild
=False, keyFile=None, multicompartment=False): | 290 def createBuild(baseDir, outFile=None, locales=None, buildNum=None, releaseBuild
=False, keyFile=None, multicompartment=False): |
325 if locales == None: | 291 if locales == None: |
326 locales = getLocales(baseDir) | 292 locales = getLocales(baseDir) |
327 elif locales == 'all': | 293 elif locales == 'all': |
328 locales = getLocales(baseDir, True) | 294 locales = getLocales(baseDir, True) |
329 | 295 |
330 metadata = readMetadata(baseDir) | 296 metadata = readMetadata(baseDir) |
331 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | 297 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) |
332 | 298 |
333 if outFile == None: | 299 if outFile == None: |
334 outFile = getDefaultFileName(baseDir, metadata, version, 'xpi') | 300 outFile = getDefaultFileName(baseDir, metadata, version, 'xpi') |
335 | 301 |
336 contributors = getContributors(baseDir, metadata) | 302 contributors = getContributors(baseDir, metadata) |
337 | 303 |
338 params = { | 304 params = { |
| 305 'baseDir': baseDir, |
339 'locales': locales, | 306 'locales': locales, |
340 'releaseBuild': releaseBuild, | 307 'releaseBuild': releaseBuild, |
341 'version': version.encode('utf-8'), | 308 'version': version.encode('utf-8'), |
342 'metadata': metadata, | 309 'metadata': metadata, |
343 'contributors': contributors, | 310 'contributors': contributors, |
344 'multicompartment': multicompartment, | 311 'multicompartment': multicompartment, |
345 } | 312 } |
346 files = {} | 313 |
347 files['install.rdf'] = createManifest(baseDir, params) | 314 files = Files(getPackageFiles(params), getIgnoredFiles(params), |
| 315 process=lambda path, data: processFile(path, data, params)) |
| 316 files['install.rdf'] = createManifest(params) |
| 317 files.read(baseDir, skip=('chrome')) |
348 for name, path in getChromeSubdirs(baseDir, params['locales']).iteritems(): | 318 for name, path in getChromeSubdirs(baseDir, params['locales']).iteritems(): |
349 if os.path.isdir(path): | 319 if os.path.isdir(path): |
350 readFile(files, params, path, 'chrome/%s' % name) | 320 files.read(path, 'chrome/%s' % name) |
351 fixupLocales(baseDir, files, params) | 321 fixupLocales(params, files) |
352 readXPIFiles(baseDir, params, files) | |
353 if not 'bootstrap.js' in files: | 322 if not 'bootstrap.js' in files: |
354 addMissingFiles(baseDir, params, files) | 323 addMissingFiles(params, files) |
355 if keyFile: | 324 if keyFile: |
356 signFiles(files, keyFile) | 325 signFiles(files, keyFile) |
357 writeXPI(files, outFile) | 326 files.zip(outFile, sortKey=lambda x: '!' if x == 'META-INF/zigbert.rsa' else x
) |
358 | 327 |
359 def autoInstall(baseDir, host, port, multicompartment=False): | 328 def autoInstall(baseDir, host, port, multicompartment=False): |
360 fileBuffer = StringIO() | 329 fileBuffer = StringIO() |
361 createBuild(baseDir, outFile=fileBuffer, multicompartment=multicompartment) | 330 createBuild(baseDir, outFile=fileBuffer, multicompartment=multicompartment) |
362 urllib.urlopen('http://%s:%s/' % (host, port), data=fileBuffer.getvalue()) | 331 urllib.urlopen('http://%s:%s/' % (host, port), data=fileBuffer.getvalue()) |
OLD | NEW |