| Left: | ||
| Right: | 
| OLD | NEW | 
|---|---|
| 1 # coding: utf-8 | 1 # coding: utf-8 | 
| 2 | 2 | 
| 3 # This file is part of the Adblock Plus web scripts, | 3 # This file is part of the Adblock Plus web scripts, | 
| 4 # Copyright (C) 2006-2013 Eyeo GmbH | 4 # Copyright (C) 2006-2013 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 """ | 18 """ | 
| 19 | 19 | 
| 20 Nightly builds generation script | 20 Nightly builds generation script | 
| 21 ================================ | 21 ================================ | 
| 22 | 22 | 
| 23 This script generates nightly builds of extensions, together | 23 This script generates nightly builds of extensions, together | 
| 24 with changelogs and documentation. | 24 with changelogs and documentation. | 
| 25 | 25 | 
| 26 """ | 26 """ | 
| 27 | 27 | 
| 28 import sys, os, os.path, codecs, subprocess, ConfigParser, traceback, json, hash lib | 28 import sys, os, os.path, codecs, subprocess, ConfigParser, traceback, json, hash lib | 
| 29 import tempfile, re, shutil, urlparse, pipes | 29 import tempfile, re, shutil, urlparse, pipes, time, urllib2, struct | 
| 30 from datetime import datetime | 30 from datetime import datetime | 
| 31 from urllib import urlencode | |
| 31 from xml.dom.minidom import parse as parseXml | 32 from xml.dom.minidom import parse as parseXml | 
| 32 from sitescripts.utils import get_config, setupStderr, get_template | 33 from sitescripts.utils import get_config, setupStderr, get_template | 
| 33 from sitescripts.extensions.utils import compareVersions, Configuration | 34 from sitescripts.extensions.utils import compareVersions, Configuration | 
| 34 | 35 | 
| 35 MAX_BUILDS = 50 | 36 MAX_BUILDS = 50 | 
| 36 | 37 | 
| 37 | 38 | 
| 38 class NightlyBuild(object): | 39 class NightlyBuild(object): | 
| 39 """ | 40 """ | 
| 40 Performs the build process for an extension, | 41 Performs the build process for an extension, | 
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 279 | 280 | 
| 280 | 281 | 
| 281 def build(self): | 282 def build(self): | 
| 282 """ | 283 """ | 
| 283 run the build command in the tempdir | 284 run the build command in the tempdir | 
| 284 """ | 285 """ | 
| 285 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) | 286 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) | 
| 286 if not os.path.exists(baseDir): | 287 if not os.path.exists(baseDir): | 
| 287 os.makedirs(baseDir) | 288 os.makedirs(baseDir) | 
| 288 outputFile = "%s-%s%s" % (self.basename, self.version, self.config.packageSu ffix) | 289 outputFile = "%s-%s%s" % (self.basename, self.version, self.config.packageSu ffix) | 
| 289 outputPath = os.path.join(baseDir, outputFile) | 290 self.path = os.path.join(baseDir, outputFile) | 
| 290 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + '/' + outputFile + '?update') | 291 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + '/' + outputFile + '?update') | 
| 291 | 292 | 
| 292 if self.config.type == 'android': | 293 if self.config.type == 'android': | 
| 293 apkFile = open(outputPath, 'wb') | 294 apkFile = open(self.path, 'wb') | 
| 294 | 295 | 
| 295 try: | 296 try: | 
| 296 try: | 297 try: | 
| 297 port = get_config().get('extensions', 'androidBuildPort') | 298 port = get_config().get('extensions', 'androidBuildPort') | 
| 298 except ConfigParser.NoOptionError: | 299 except ConfigParser.NoOptionError: | 
| 299 port = '22' | 300 port = '22' | 
| 300 buildCommand = ['ssh', '-p', port, get_config().get('extensions', 'andro idBuildHost')] | 301 buildCommand = ['ssh', '-p', port, get_config().get('extensions', 'andro idBuildHost')] | 
| 301 buildCommand.extend(map(pipes.quote, ['/home/android/bin/makedebugbuild. py', '--revision', self.revision, '--version', self.version, '--stdout'])) | 302 buildCommand.extend(map(pipes.quote, ['/home/android/bin/makedebugbuild. py', '--revision', self.revision, '--version', self.version, '--stdout'])) | 
| 302 subprocess.check_call(buildCommand, stdout=apkFile, close_fds=True) | 303 subprocess.check_call(buildCommand, stdout=apkFile, close_fds=True) | 
| 303 except: | 304 except: | 
| 304 # clear broken output if any | 305 # clear broken output if any | 
| 305 if os.path.exists(outputPath): | 306 if os.path.exists(self.path): | 
| 306 os.remove(outputPath) | 307 os.remove(self.path) | 
| 307 raise | 308 raise | 
| 308 elif self.config.type == 'chrome' or self.config.type == 'opera': | 309 elif self.config.type == 'chrome' or self.config.type == 'opera': | 
| 309 import buildtools.packagerChrome as packager | 310 import buildtools.packagerChrome as packager | 
| 310 packager.createBuild(self.tempdir, type=self.config.type, outFile=outputPa th, buildNum=self.revision, keyFile=self.config.keyFile, experimentalAPI=self.co nfig.experimental) | 311 packager.createBuild(self.tempdir, type=self.config.type, outFile=self.pat h, buildNum=self.revision, keyFile=self.config.keyFile, experimentalAPI=self.con fig.experimental) | 
| 311 elif self.config.type == 'safari': | 312 elif self.config.type == 'safari': | 
| 312 import buildtools.packagerSafari as packager | 313 import buildtools.packagerSafari as packager | 
| 313 packager.createBuild(self.tempdir, type=self.config.type, outFile=outputPa th, buildNum=self.revision, keyFile=self.config.keyFile) | 314 packager.createBuild(self.tempdir, type=self.config.type, outFile=self.pat h, buildNum=self.revision, keyFile=self.config.keyFile) | 
| 314 else: | 315 else: | 
| 315 import buildtools.packagerGecko as packager | 316 import buildtools.packagerGecko as packager | 
| 316 packager.createBuild(self.tempdir, outFile=outputPath, buildNum=self.revis ion, keyFile=self.config.keyFile) | 317 packager.createBuild(self.tempdir, outFile=self.path, buildNum=self.revisi on, keyFile=self.config.keyFile) | 
| 317 | 318 | 
| 318 if not os.path.exists(outputPath): | 319 if not os.path.exists(self.path): | 
| 319 raise Exception("Build failed, output file hasn't been created") | 320 raise Exception("Build failed, output file hasn't been created") | 
| 320 | 321 | 
| 321 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffix) | 322 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffix) | 
| 322 if hasattr(os, 'symlink'): | 323 if hasattr(os, 'symlink'): | 
| 323 if os.path.exists(linkPath): | 324 if os.path.exists(linkPath): | 
| 324 os.remove(linkPath) | 325 os.remove(linkPath) | 
| 325 os.symlink(os.path.basename(outputPath), linkPath) | 326 os.symlink(os.path.basename(self.path), linkPath) | 
| 326 else: | 327 else: | 
| 327 shutil.copyfile(outputPath, linkPath) | 328 shutil.copyfile(self.path, linkPath) | 
| 328 | 329 | 
| 329 def retireBuilds(self): | 330 def retireBuilds(self): | 
| 330 """ | 331 """ | 
| 331 removes outdated builds, returns the sorted version numbers of remaining | 332 removes outdated builds, returns the sorted version numbers of remaining | 
| 332 builds | 333 builds | 
| 333 """ | 334 """ | 
| 334 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) | 335 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) | 
| 335 versions = [] | 336 versions = [] | 
| 336 prefix = self.basename + '-' | 337 prefix = self.basename + '-' | 
| 337 suffix = self.config.packageSuffix | 338 suffix = self.config.packageSuffix | 
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 command = ['hg', 'archive', '-R', get_config().get('extensions', 'jsdocRepos itory'), '-r', 'default', docsdir] | 386 command = ['hg', 'archive', '-R', get_config().get('extensions', 'jsdocRepos itory'), '-r', 'default', docsdir] | 
| 386 subprocess.check_call(command) | 387 subprocess.check_call(command) | 
| 387 | 388 | 
| 388 try: | 389 try: | 
| 389 import buildtools.build as build | 390 import buildtools.build as build | 
| 390 outputPath = os.path.join(self.config.docsDirectory, self.basename) | 391 outputPath = os.path.join(self.config.docsDirectory, self.basename) | 
| 391 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type) | 392 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type) | 
| 392 finally: | 393 finally: | 
| 393 shutil.rmtree(docsdir, ignore_errors=True) | 394 shutil.rmtree(docsdir, ignore_errors=True) | 
| 394 | 395 | 
| 396 def uploadToChromeWebStore(self): | |
| 397 # use refresh token to obtain a valid access token | |
| 398 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh | |
| 399 | |
| 400 response = json.load(urllib2.urlopen( | |
| 401 'https://accounts.google.com/o/oauth2/token', | |
| 402 | |
| 403 urlencode([ | |
| 404 ('refresh_token', self.config.refreshToken), | |
| 405 ('client_id', self.config.clientID), | |
| 406 ('client_secret', self.config.clientSecret), | |
| 407 ('grant_type', 'refresh_token'), | |
| 408 ]) | |
| 409 )) | |
| 410 | |
| 411 # upload a new version with the Chrome Web Store API | |
| 412 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng | |
| 413 | |
| 414 request = urllib2.Request('https://www.googleapis.com/upload/chromewebstore/ v1.1/items/' + self.config.galleryID) | |
| 415 request.get_method = lambda: 'PUT' | |
| 416 request.add_header('Authorization', '%s %s' % (response['token_type'], respo nse['access_token'])) | |
| 417 request.add_header('x-goog-api-version', '2') | |
| 418 | |
| 419 with open(self.path, 'rb') as file: | |
| 420 # skip CRX magic and version | |
| 421 offset = 8 | |
| 422 file.seek(offset) | |
| 423 | |
| 424 # skip public key and signature | |
| 425 offset += 8 + sum(struct.unpack('<II', file.read(8))) | |
| 
 
Wladimir Palant
2014/04/21 20:22:56
Please don't skip magic number and version - read
 
Sebastian Noack
2014/04/21 20:46:36
Done.
 
 | |
| 426 file.seek(offset) | |
| 
 
Wladimir Palant
2014/04/21 20:22:56
Please use os.SEEK_CUR as second parameter to file
 
Sebastian Noack
2014/04/21 20:46:36
Done.
 
 | |
| 427 | |
| 428 request.add_header('Content-Length', os.fstat(file.fileno()).st_size - off set) | |
| 429 request.add_data(file) | |
| 430 | |
| 431 response = json.load(urllib2.urlopen(request)) | |
| 432 | |
| 433 if response['uploadState'] == 'FAILURE': | |
| 434 raise Exception(response['itemError']) | |
| 435 | |
| 395 def run(self): | 436 def run(self): | 
| 396 """ | 437 """ | 
| 397 Run the nightly build process for one extension | 438 Run the nightly build process for one extension | 
| 398 """ | 439 """ | 
| 399 try: | 440 try: | 
| 400 if self.config.type == 'ie': | 441 if self.config.type == 'ie': | 
| 401 # We cannot build IE builds, simply list the builds already in | 442 # We cannot build IE builds, simply list the builds already in | 
| 402 # the directory. Basename has to be deduced from the repository name. | 443 # the directory. Basename has to be deduced from the repository name. | 
| 403 self.basename = os.path.basename(self.config.repository) | 444 self.basename = os.path.basename(self.config.repository) | 
| 404 else: | 445 else: | 
| (...skipping 26 matching lines...) Expand all Loading... | |
| 431 versions = self.retireBuilds() | 472 versions = self.retireBuilds() | 
| 432 | 473 | 
| 433 if self.config.type == 'ie': | 474 if self.config.type == 'ie': | 
| 434 self.writeIEUpdateManifest(versions) | 475 self.writeIEUpdateManifest(versions) | 
| 435 | 476 | 
| 436 # update index page | 477 # update index page | 
| 437 self.updateIndex(versions) | 478 self.updateIndex(versions) | 
| 438 | 479 | 
| 439 # update nightlies config | 480 # update nightlies config | 
| 440 self.config.latestRevision = self.revision | 481 self.config.latestRevision = self.revision | 
| 482 | |
| 483 if self.config.clientID and self.config.clientSecret and self.config.refre shToken: | |
| 
 
Wladimir Palant
2014/04/21 20:22:56
I think it makes sense to keep the type == 'chrome
 
Sebastian Noack
2014/04/21 20:46:36
Done.
 
 | |
| 484 self.uploadToChromeWebStore() | |
| 441 finally: | 485 finally: | 
| 442 # clean up | 486 # clean up | 
| 443 if self.tempdir: | 487 if self.tempdir: | 
| 444 shutil.rmtree(self.tempdir, ignore_errors=True) | 488 shutil.rmtree(self.tempdir, ignore_errors=True) | 
| 445 | 489 | 
| 446 | 490 | 
| 447 def main(): | 491 def main(): | 
| 448 """ | 492 """ | 
| 449 main function for createNightlies.py | 493 main function for createNightlies.py | 
| 450 """ | 494 """ | 
| (...skipping 16 matching lines...) Expand all Loading... | |
| 467 except Exception, ex: | 511 except Exception, ex: | 
| 468 print >>sys.stderr, "The build for %s failed:" % repo | 512 print >>sys.stderr, "The build for %s failed:" % repo | 
| 469 traceback.print_exc() | 513 traceback.print_exc() | 
| 470 | 514 | 
| 471 file = open(nightlyConfigFile, 'wb') | 515 file = open(nightlyConfigFile, 'wb') | 
| 472 nightlyConfig.write(file) | 516 nightlyConfig.write(file) | 
| 473 | 517 | 
| 474 | 518 | 
| 475 if __name__ == '__main__': | 519 if __name__ == '__main__': | 
| 476 main() | 520 main() | 
| OLD | NEW |