| 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 |