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: Created April 19, 2014, 4:12 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
30 from datetime import datetime 30 from datetime import datetime
31 from urllib import urlencode
32 from urllib2 import urlopen
31 from xml.dom.minidom import parse as parseXml 33 from xml.dom.minidom import parse as parseXml
32 from sitescripts.utils import get_config, setupStderr, get_template 34 from sitescripts.utils import get_config, setupStderr, get_template
33 from sitescripts.extensions.utils import compareVersions, Configuration 35 from sitescripts.extensions.utils import compareVersions, Configuration
34 36
35 MAX_BUILDS = 50 37 MAX_BUILDS = 50
36 38
37 39
38 class NightlyBuild(object): 40 class NightlyBuild(object):
39 """ 41 """
40 Performs the build process for an extension, 42 Performs the build process for an extension,
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after
279 281
280 282
281 def build(self): 283 def build(self):
282 """ 284 """
283 run the build command in the tempdir 285 run the build command in the tempdir
284 """ 286 """
285 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 287 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
286 if not os.path.exists(baseDir): 288 if not os.path.exists(baseDir):
287 os.makedirs(baseDir) 289 os.makedirs(baseDir)
288 outputFile = "%s-%s%s" % (self.basename, self.version, self.config.packageSu ffix) 290 outputFile = "%s-%s%s" % (self.basename, self.version, self.config.packageSu ffix)
289 outputPath = os.path.join(baseDir, outputFile) 291 self.path = os.path.join(baseDir, outputFile)
290 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + '/' + outputFile + '?update') 292 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + '/' + outputFile + '?update')
291 293
292 if self.config.type == 'android': 294 if self.config.type == 'android':
293 apkFile = open(outputPath, 'wb') 295 apkFile = open(self.path, 'wb')
294 296
295 try: 297 try:
296 try: 298 try:
297 port = get_config().get('extensions', 'androidBuildPort') 299 port = get_config().get('extensions', 'androidBuildPort')
298 except ConfigParser.NoOptionError: 300 except ConfigParser.NoOptionError:
299 port = '22' 301 port = '22'
300 buildCommand = ['ssh', '-p', port, get_config().get('extensions', 'andro idBuildHost')] 302 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'])) 303 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) 304 subprocess.check_call(buildCommand, stdout=apkFile, close_fds=True)
303 except: 305 except:
304 # clear broken output if any 306 # clear broken output if any
305 if os.path.exists(outputPath): 307 if os.path.exists(self.path):
306 os.remove(outputPath) 308 os.remove(self.path)
307 raise 309 raise
308 elif self.config.type == 'chrome' or self.config.type == 'opera': 310 elif self.config.type == 'chrome' or self.config.type == 'opera':
309 import buildtools.packagerChrome as packager 311 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) 312 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': 313 elif self.config.type == 'safari':
312 import buildtools.packagerSafari as packager 314 import buildtools.packagerSafari as packager
313 packager.createBuild(self.tempdir, type=self.config.type, outFile=outputPa th, buildNum=self.revision, keyFile=self.config.keyFile) 315 packager.createBuild(self.tempdir, type=self.config.type, outFile=self.pat h, buildNum=self.revision, keyFile=self.config.keyFile)
314 else: 316 else:
315 import buildtools.packagerGecko as packager 317 import buildtools.packagerGecko as packager
316 packager.createBuild(self.tempdir, outFile=outputPath, buildNum=self.revis ion, keyFile=self.config.keyFile) 318 packager.createBuild(self.tempdir, outFile=self.path, buildNum=self.revisi on, keyFile=self.config.keyFile)
317 319
318 if not os.path.exists(outputPath): 320 if not os.path.exists(self.path):
319 raise Exception("Build failed, output file hasn't been created") 321 raise Exception("Build failed, output file hasn't been created")
320 322
321 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffix) 323 linkPath = os.path.join(baseDir, '00latest%s' % self.config.packageSuffix)
322 if hasattr(os, 'symlink'): 324 if hasattr(os, 'symlink'):
323 if os.path.exists(linkPath): 325 if os.path.exists(linkPath):
324 os.remove(linkPath) 326 os.remove(linkPath)
325 os.symlink(os.path.basename(outputPath), linkPath) 327 os.symlink(os.path.basename(self.path), linkPath)
326 else: 328 else:
327 shutil.copyfile(outputPath, linkPath) 329 shutil.copyfile(self.path, linkPath)
328 330
329 def retireBuilds(self): 331 def retireBuilds(self):
330 """ 332 """
331 removes outdated builds, returns the sorted version numbers of remaining 333 removes outdated builds, returns the sorted version numbers of remaining
332 builds 334 builds
333 """ 335 """
334 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 336 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
335 versions = [] 337 versions = []
336 prefix = self.basename + '-' 338 prefix = self.basename + '-'
337 suffix = self.config.packageSuffix 339 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] 387 command = ['hg', 'archive', '-R', get_config().get('extensions', 'jsdocRepos itory'), '-r', 'default', docsdir]
386 subprocess.check_call(command) 388 subprocess.check_call(command)
387 389
388 try: 390 try:
389 import buildtools.build as build 391 import buildtools.build as build
390 outputPath = os.path.join(self.config.docsDirectory, self.basename) 392 outputPath = os.path.join(self.config.docsDirectory, self.basename)
391 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type) 393 build.generateDocs(self.tempdir, None, [("-t", docsdir), ("-q", "")], [out putPath], self.config.type)
392 finally: 394 finally:
393 shutil.rmtree(docsdir, ignore_errors=True) 395 shutil.rmtree(docsdir, ignore_errors=True)
394 396
397 def uploadToChromeWebStore(self):
398 import M2Crypto
399
400 # log in with the Google Developer Service Account
401 # https://developers.google.com/accounts/docs/OAuth2ServiceAccount#creatingj wt
402
403 base64encode = lambda data: base64.urlsafe_b64encode(data).rstrip('=')
404 now = int(time.mktime(time.gmtime()))
405
406 jwt = '%s.%s' % (
407 base64encode(json.dumps({
408 'alg': 'RS256',
409 'typ': 'JWT',
410 })),
411 base64encode(json.dumps({
412 'iss': self.config.serviceAccountEmail,
413 'scope': 'https://www.googleapis.com/auth/chromewebstore',
414 'aud':'https://accounts.google.com/o/oauth2/token',
415 'exp': now + 3600, # fails if less than about an hour
416 'iat': now,
417 })),
418 )
419
420 jwt = '%s.%s' % (jwt, base64encode(
421 M2Crypto.RSA.load_key(self.config.serviceAccountKey).sign(
422 hashlib.sha256(jwt).digest(), 'sha256'
423 )
424 ))
425
426 response = json.load(urlopen(
427 'https://accounts.google.com/o/oauth2/token',
428
429 urlencode([
430 ('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'),
431 ('assertion', jwt),
432 ])
433 ))
Wladimir Palant 2014/04/20 06:47:53 Check for error responses?
Sebastian Noack 2014/04/20 16:03:08 This API uses standard HTTP error codes, which ar
434
435 # upload a new version with the Chrome Web Store API
436 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitng
437
438 subprocess.check_call([
Sebastian Noack 2014/04/19 16:15:34 I use curl instead of urllib here, because urllib
Wladimir Palant 2014/04/20 06:47:53 We do file uploads with urllib in buildtools/local
Sebastian Noack 2014/04/20 16:03:08 It was actually easier to use urllib2 as I thought
439 'curl', 'https://www.googleapis.com/upload/chromewebstore/v1.1/items/' + s elf.config.galleryID
440
441 '--header', 'Authorization: %s %s' % (response['token_type'], response['ac cess_token'])
442 '--header', 'x-goog-api-version: 2',
443 '--request', 'PUT',
444 '--upload-file', self.path,
445 '--silent',
446 ])
Wladimir Palant 2014/04/20 06:47:53 Here as well - please check for error responses, i
Sebastian Noack 2014/04/20 16:03:08 Done.
447
395 def run(self): 448 def run(self):
396 """ 449 """
397 Run the nightly build process for one extension 450 Run the nightly build process for one extension
398 """ 451 """
399 try: 452 try:
400 if self.config.type == 'ie': 453 if self.config.type == 'ie':
401 # We cannot build IE builds, simply list the builds already in 454 # We cannot build IE builds, simply list the builds already in
402 # the directory. Basename has to be deduced from the repository name. 455 # the directory. Basename has to be deduced from the repository name.
403 self.basename = os.path.basename(self.config.repository) 456 self.basename = os.path.basename(self.config.repository)
404 else: 457 else:
(...skipping 26 matching lines...) Expand all
431 versions = self.retireBuilds() 484 versions = self.retireBuilds()
432 485
433 if self.config.type == 'ie': 486 if self.config.type == 'ie':
434 self.writeIEUpdateManifest(versions) 487 self.writeIEUpdateManifest(versions)
435 488
436 # update index page 489 # update index page
437 self.updateIndex(versions) 490 self.updateIndex(versions)
438 491
439 # update nightlies config 492 # update nightlies config
440 self.config.latestRevision = self.revision 493 self.config.latestRevision = self.revision
494
495 if self.config.type == 'chrome':
Wladimir Palant 2014/04/20 06:47:53 This step needs to be optional, only if the necess
Sebastian Noack 2014/04/20 16:03:08 Done.
496 self.uploadToChromeWebStore()
441 finally: 497 finally:
442 # clean up 498 # clean up
443 if self.tempdir: 499 if self.tempdir:
444 shutil.rmtree(self.tempdir, ignore_errors=True) 500 shutil.rmtree(self.tempdir, ignore_errors=True)
445 501
446 502
447 def main(): 503 def main():
448 """ 504 """
449 main function for createNightlies.py 505 main function for createNightlies.py
450 """ 506 """
(...skipping 16 matching lines...) Expand all
467 except Exception, ex: 523 except Exception, ex:
468 print >>sys.stderr, "The build for %s failed:" % repo 524 print >>sys.stderr, "The build for %s failed:" % repo
469 traceback.print_exc() 525 traceback.print_exc()
470 526
471 file = open(nightlyConfigFile, 'wb') 527 file = open(nightlyConfigFile, 'wb')
472 nightlyConfig.write(file) 528 nightlyConfig.write(file)
473 529
474 530
475 if __name__ == '__main__': 531 if __name__ == '__main__':
476 main() 532 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