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: Skip CRX header when uploading to CWS Created April 21, 2014, 6:10 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, 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
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 # 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
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
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()
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