Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: sitescripts/extensions/bin/createNightlies.py

Issue 5092904657747968: Issue 356 - Update devbuild in the Chrome Web Store (Closed)
Patch Set: Addressed comments Created April 20, 2014, 3:58 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « .sitescripts.example ('k') | sitescripts/extensions/utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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, base64, urllib2
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
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
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 import M2Crypto
398
399 # log in with the Google Developer Service Account
400 # https://developers.google.com/accounts/docs/OAuth2ServiceAccount#creatingj wt
401
402 base64encode = lambda data: base64.urlsafe_b64encode(data).rstrip('=')
403 now = int(time.mktime(time.gmtime()))
404
405 jwt = '%s.%s' % (
406 base64encode(json.dumps({
407 'alg': 'RS256',
408 'typ': 'JWT',
409 })),
410 base64encode(json.dumps({
411 'iss': self.config.serviceAccountEmail,
412 'scope': 'https://www.googleapis.com/auth/chromewebstore',
413 'aud': 'https://accounts.google.com/o/oauth2/token',
414 'exp': now + 3600, # fails if less than about an hour
415 'iat': now,
416 })),
417 )
418
419 jwt = '%s.%s' % (jwt, base64encode(
420 M2Crypto.RSA.load_key(self.config.serviceAccountKey).sign(
421 hashlib.sha256(jwt).digest(), 'sha256'
422 )
423 ))
424
425 response = json.load(urllib2.urlopen(
426 'https://accounts.google.com/o/oauth2/token',
427
428 urlencode([
429 ('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'),
430 ('assertion', jwt),
431 ])
432 ))
433
434 # upload a new version with the Chrome Web Store API
435 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng
436
437 request = urllib2.Request('https://www.googleapis.com/upload/chromewebstore/ v1.1/items/' + self.config.galleryID)
438 request.get_method = lambda: 'PUT'
439 request.add_header('Authorization', '%s %s' % (response['token_type'], respo nse['access_token']))
440 request.add_header('x-goog-api-version', '2')
441
442 with open(self.path, 'rb') as file:
443 request.add_header('Content-Length', '%d' % os.fstat(file.fileno()).st_siz e)
444 request.add_data(file)
445
446 response = json.load(urllib2.urlopen(request))
447
448 if response['uploadState'] == 'FAILURE':
449 raise Exception(response['itemError'])
450
395 def run(self): 451 def run(self):
396 """ 452 """
397 Run the nightly build process for one extension 453 Run the nightly build process for one extension
398 """ 454 """
399 try: 455 try:
400 if self.config.type == 'ie': 456 if self.config.type == 'ie':
401 # We cannot build IE builds, simply list the builds already in 457 # We cannot build IE builds, simply list the builds already in
402 # the directory. Basename has to be deduced from the repository name. 458 # the directory. Basename has to be deduced from the repository name.
403 self.basename = os.path.basename(self.config.repository) 459 self.basename = os.path.basename(self.config.repository)
404 else: 460 else:
(...skipping 26 matching lines...) Expand all
431 versions = self.retireBuilds() 487 versions = self.retireBuilds()
432 488
433 if self.config.type == 'ie': 489 if self.config.type == 'ie':
434 self.writeIEUpdateManifest(versions) 490 self.writeIEUpdateManifest(versions)
435 491
436 # update index page 492 # update index page
437 self.updateIndex(versions) 493 self.updateIndex(versions)
438 494
439 # update nightlies config 495 # update nightlies config
440 self.config.latestRevision = self.revision 496 self.config.latestRevision = self.revision
497
498 if self.config.serviceAccountEmail and self.config.serviceAccountKey:
499 self.uploadToChromeWebStore()
441 finally: 500 finally:
442 # clean up 501 # clean up
443 if self.tempdir: 502 if self.tempdir:
444 shutil.rmtree(self.tempdir, ignore_errors=True) 503 shutil.rmtree(self.tempdir, ignore_errors=True)
445 504
446 505
447 def main(): 506 def main():
448 """ 507 """
449 main function for createNightlies.py 508 main function for createNightlies.py
450 """ 509 """
(...skipping 16 matching lines...) Expand all
467 except Exception, ex: 526 except Exception, ex:
468 print >>sys.stderr, "The build for %s failed:" % repo 527 print >>sys.stderr, "The build for %s failed:" % repo
469 traceback.print_exc() 528 traceback.print_exc()
470 529
471 file = open(nightlyConfigFile, 'wb') 530 file = open(nightlyConfigFile, 'wb')
472 nightlyConfig.write(file) 531 nightlyConfig.write(file)
473 532
474 533
475 if __name__ == '__main__': 534 if __name__ == '__main__':
476 main() 535 main()
OLDNEW
« no previous file with comments | « .sitescripts.example ('k') | sitescripts/extensions/utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld