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

Delta Between Two Patch Sets: sitescripts/extensions/bin/createNightlies.py

Issue 29751598: Issue 6291 - Use client certificate for Windows Store uploads (Closed) Base URL: https://hg.adblockplus.org/abpssembly/file/a67d8f0e66b2
Left Patch Set: Addressing non-functional nits Created April 18, 2018, 12:57 p.m.
Right Patch Set: NO CHANGE rebase against https://codereview.adblockplus.org/29756646/ Created April 20, 2018, 7:23 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | sitescripts/extensions/utils.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This file is part of the Adblock Plus web scripts, 1 # This file is part of the Adblock Plus web scripts,
2 # Copyright (C) 2006-present eyeo GmbH 2 # Copyright (C) 2006-present eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
46 import zipfile 46 import zipfile
47 import contextlib 47 import contextlib
48 from xml.dom.minidom import parse as parseXml 48 from xml.dom.minidom import parse as parseXml
49 49
50 from Crypto.PublicKey import RSA 50 from Crypto.PublicKey import RSA
51 from Crypto.Signature import PKCS1_v1_5 51 from Crypto.Signature import PKCS1_v1_5
52 import Crypto.Hash.SHA256 52 import Crypto.Hash.SHA256
53 53
54 from sitescripts.extensions.utils import ( 54 from sitescripts.extensions.utils import (
55 compareVersions, Configuration, 55 compareVersions, Configuration,
56 writeAndroidUpdateManifest 56 writeAndroidUpdateManifest,
57 ) 57 )
58 from sitescripts.utils import get_config, get_template 58 from sitescripts.utils import get_config, get_template
59 59
60 MAX_BUILDS = 50 60 MAX_BUILDS = 50
61 61
62 62
63 # Google and Microsoft APIs use HTTP error codes with error message in 63 # Google and Microsoft APIs use HTTP error codes with error message in
64 # body. So we add the response body to the HTTPError to get more 64 # body. So we add the response body to the HTTPError to get more
65 # meaningful error messages. 65 # meaningful error messages.
66 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler): 66 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler):
(...skipping 28 matching lines...) Expand all
95 95
96 def hasChanges(self): 96 def hasChanges(self):
97 return self.revision != self.previousRevision 97 return self.revision != self.previousRevision
98 98
99 def getCurrentRevision(self): 99 def getCurrentRevision(self):
100 """ 100 """
101 retrieves the current revision ID from the repository 101 retrieves the current revision ID from the repository
102 """ 102 """
103 command = [ 103 command = [
104 'hg', 'id', '-i', '-r', self.config.revision, '--config', 104 'hg', 'id', '-i', '-r', self.config.revision, '--config',
105 'defaults.id=', self.config.repository 105 'defaults.id=', self.config.repository,
106 ] 106 ]
107 return subprocess.check_output(command).strip() 107 return subprocess.check_output(command).strip()
108 108
109 def getCurrentBuild(self): 109 def getCurrentBuild(self):
110 """ 110 """
111 calculates the (typically numerical) build ID for the current build 111 calculates the (typically numerical) build ID for the current build
112 """ 112 """
113 command = ['hg', 'id', '-n', '--config', 'defaults.id=', self.tempdir] 113 command = ['hg', 'id', '-n', '--config', 'defaults.id=', self.tempdir]
114 build = subprocess.check_output(command).strip() 114 build = subprocess.check_output(command).strip()
115 return build 115 return build
116 116
117 def getChanges(self): 117 def getChanges(self):
118 """ 118 """
119 retrieve changes between the current and previous ("first") revision 119 retrieve changes between the current and previous ("first") revision
120 """ 120 """
121 command = [ 121 command = [
122 'hg', 'log', '-R', self.tempdir, '-r', 122 'hg', 'log', '-R', self.tempdir, '-r',
123 'reverse(ancestors({}))'.format(self.config.revision), '-l', '50', 123 'reverse(ancestors({}))'.format(self.config.revision), '-l', '50',
124 '--encoding', 'utf-8', '--template', 124 '--encoding', 'utf-8', '--template',
125 '{date|isodate}\\0{author|person}\\0{rev}\\0{desc}\\0\\0', 125 '{date|isodate}\\0{author|person}\\0{rev}\\0{desc}\\0\\0',
126 '--config', 'defaults.log=' 126 '--config', 'defaults.log=',
127 ] 127 ]
128 result = subprocess.check_output(command).decode('utf-8') 128 result = subprocess.check_output(command).decode('utf-8')
129 129
130 for change in result.split('\x00\x00'): 130 for change in result.split('\x00\x00'):
131 if change: 131 if change:
132 date, author, revision, description = change.split('\x00') 132 date, author, revision, description = change.split('\x00')
133 yield {'date': date, 'author': author, 'revision': revision, 'de scription': description} 133 yield {'date': date, 'author': author, 'revision': revision, 'de scription': description}
134 134
135 def copyRepository(self): 135 def copyRepository(self):
136 """ 136 """
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 os.makedirs(baseDir) 278 os.makedirs(baseDir)
279 279
280 # ABP for Android used to have its own update manifest format. We need t o 280 # ABP for Android used to have its own update manifest format. We need t o
281 # generate both that and the new one in the libadblockplus format as lon g 281 # generate both that and the new one in the libadblockplus format as lon g
282 # as a significant amount of users is on an old version. 282 # as a significant amount of users is on an old version.
283 if self.config.type == 'android': 283 if self.config.type == 'android':
284 newManifestPath = os.path.join(baseDir, 'update.json') 284 newManifestPath = os.path.join(baseDir, 'update.json')
285 writeAndroidUpdateManifest(newManifestPath, [{ 285 writeAndroidUpdateManifest(newManifestPath, [{
286 'basename': self.basename, 286 'basename': self.basename,
287 'version': self.version, 287 'version': self.version,
288 'updateURL': self.updateURL 288 'updateURL': self.updateURL,
289 }]) 289 }])
290 290
291 template = get_template(get_config().get('extensions', templateName), 291 template = get_template(get_config().get('extensions', templateName),
292 autoescape=autoescape) 292 autoescape=autoescape)
293 template.stream({'extensions': [self]}).dump(manifestPath) 293 template.stream({'extensions': [self]}).dump(manifestPath)
294 294
295 def writeIEUpdateManifest(self, versions): 295 def writeIEUpdateManifest(self, versions):
296 """ 296 """
297 Writes update.json file for the latest IE build 297 Writes update.json file for the latest IE build
298 """ 298 """
299 if len(versions) == 0: 299 if len(versions) == 0:
300 return 300 return
301 301
302 version = versions[0] 302 version = versions[0]
303 packageName = self.basename + '-' + version + self.config.packageSuffix 303 packageName = self.basename + '-' + version + self.config.packageSuffix
304 updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + ' /' + packageName + '?update') 304 updateURL = urlparse.urljoin(self.config.nightliesURL, self.basename + ' /' + packageName + '?update')
305 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 305 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
306 manifestPath = os.path.join(baseDir, 'update.json') 306 manifestPath = os.path.join(baseDir, 'update.json')
307 307
308 from sitescripts.extensions.utils import writeIEUpdateManifest as doWrit e 308 from sitescripts.extensions.utils import writeIEUpdateManifest as doWrit e
309 doWrite(manifestPath, [{ 309 doWrite(manifestPath, [{
310 'basename': self.basename, 310 'basename': self.basename,
311 'version': version, 311 'version': version,
312 'updateURL': updateURL 312 'updateURL': updateURL,
313 }]) 313 }])
314 314
315 for suffix in ['-x86.msi', '-x64.msi', '-gpo-x86.msi', '-gpo-x64.msi']: 315 for suffix in ['-x86.msi', '-x64.msi', '-gpo-x86.msi', '-gpo-x64.msi']:
316 linkPath = os.path.join(baseDir, '00latest%s' % suffix) 316 linkPath = os.path.join(baseDir, '00latest%s' % suffix)
317 outputPath = os.path.join(baseDir, self.basename + '-' + version + s uffix) 317 outputPath = os.path.join(baseDir, self.basename + '-' + version + s uffix)
318 self.symlink_or_copy(outputPath, linkPath) 318 self.symlink_or_copy(outputPath, linkPath)
319 319
320 def build(self): 320 def build(self):
321 """ 321 """
322 run the build command in the tempdir 322 run the build command in the tempdir
323 """ 323 """
324 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 324 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
325 if not os.path.exists(baseDir): 325 if not os.path.exists(baseDir):
326 os.makedirs(baseDir) 326 os.makedirs(baseDir)
327 outputFile = '%s-%s%s' % (self.basename, self.version, self.config.packa geSuffix) 327 outputFile = '%s-%s%s' % (self.basename, self.version, self.config.packa geSuffix)
328 self.path = os.path.join(baseDir, outputFile) 328 self.path = os.path.join(baseDir, outputFile)
329 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basenam e + '/' + outputFile + '?update') 329 self.updateURL = urlparse.urljoin(self.config.nightliesURL, self.basenam e + '/' + outputFile + '?update')
330 330
331 if self.config.type == 'android': 331 if self.config.type == 'android':
332 apkFile = open(self.path, 'wb') 332 apkFile = open(self.path, 'wb')
333 333
334 try: 334 try:
335 try: 335 try:
336 port = get_config().get('extensions', 'androidBuildPort') 336 port = get_config().get('extensions', 'androidBuildPort')
337 except ConfigParser.NoOptionError: 337 except ConfigParser.NoOptionError:
338 port = '22' 338 port = '22'
339 command = ['ssh', '-p', port, get_config().get('extensions', 'an droidBuildHost')] 339 command = ['ssh', '-p', port, get_config().get('extensions', 'an droidBuildHost')]
340 command.extend(map(pipes.quote, [ 340 command.extend(map(pipes.quote, [
341 '/home/android/bin/makedebugbuild.py', '--revision', 341 '/home/android/bin/makedebugbuild.py', '--revision',
342 self.buildNum, '--version', self.version, '--stdout' 342 self.buildNum, '--version', self.version, '--stdout',
343 ])) 343 ]))
344 subprocess.check_call(command, stdout=apkFile, close_fds=True) 344 subprocess.check_call(command, stdout=apkFile, close_fds=True)
345 except: 345 except:
346 # clear broken output if any 346 # clear broken output if any
347 if os.path.exists(self.path): 347 if os.path.exists(self.path):
348 os.remove(self.path) 348 os.remove(self.path)
349 raise 349 raise
350 else: 350 else:
351 env = os.environ 351 env = os.environ
352 spiderMonkeyBinary = self.config.spiderMonkeyBinary 352 spiderMonkeyBinary = self.config.spiderMonkeyBinary
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 packageFile = self.basename + '-' + version + self.config.packageSuf fix 406 packageFile = self.basename + '-' + version + self.config.packageSuf fix
407 changelogFile = self.basename + '-' + version + '.changelog.xhtml' 407 changelogFile = self.basename + '-' + version + '.changelog.xhtml'
408 if not os.path.exists(os.path.join(baseDir, packageFile)): 408 if not os.path.exists(os.path.join(baseDir, packageFile)):
409 # Oops 409 # Oops
410 continue 410 continue
411 411
412 link = { 412 link = {
413 'version': version, 413 'version': version,
414 'download': packageFile, 414 'download': packageFile,
415 'mtime': os.path.getmtime(os.path.join(baseDir, packageFile)), 415 'mtime': os.path.getmtime(os.path.join(baseDir, packageFile)),
416 'size': os.path.getsize(os.path.join(baseDir, packageFile)) 416 'size': os.path.getsize(os.path.join(baseDir, packageFile)),
417 } 417 }
418 if os.path.exists(os.path.join(baseDir, changelogFile)): 418 if os.path.exists(os.path.join(baseDir, changelogFile)):
419 link['changelog'] = changelogFile 419 link['changelog'] = changelogFile
420 links.append(link) 420 links.append(link)
421 template = get_template(get_config().get('extensions', 'nightlyIndexPage ')) 421 template = get_template(get_config().get('extensions', 'nightlyIndexPage '))
422 template.stream({'config': self.config, 'links': links}).dump(outputPath ) 422 template.stream({'config': self.config, 'links': links}).dump(outputPath )
423 423
424 def read_downloads_lockfile(self): 424 def read_downloads_lockfile(self):
425 path = get_config().get('extensions', 'downloadLockFile') 425 path = get_config().get('extensions', 'downloadLockFile')
426 try: 426 try:
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
516 config = get_config() 516 config = get_config()
517 517
518 upload_url = ('https://addons.mozilla.org/api/v3/addons/{}/' 518 upload_url = ('https://addons.mozilla.org/api/v3/addons/{}/'
519 'versions/{}/').format(self.extensionID, self.version) 519 'versions/{}/').format(self.extensionID, self.version)
520 520
521 with open(self.path, 'rb') as file: 521 with open(self.path, 'rb') as file:
522 data, content_type = urllib3.filepost.encode_multipart_formdata({ 522 data, content_type = urllib3.filepost.encode_multipart_formdata({
523 'upload': ( 523 'upload': (
524 os.path.basename(self.path), 524 os.path.basename(self.path),
525 file.read(), 525 file.read(),
526 'application/x-xpinstall' 526 'application/x-xpinstall',
527 ) 527 ),
528 }) 528 })
529 529
530 request = self.generate_mozilla_jwt_request( 530 request = self.generate_mozilla_jwt_request(
531 config.get('extensions', 'amo_key'), 531 config.get('extensions', 'amo_key'),
532 config.get('extensions', 'amo_secret'), 532 config.get('extensions', 'amo_secret'),
533 upload_url, 533 upload_url,
534 'PUT', 534 'PUT',
535 data, 535 data,
536 [('Content-Type', content_type)], 536 [('Content-Type', content_type)],
537 ) 537 )
538 538
539 try: 539 try:
540 urllib2.urlopen(request).close() 540 urllib2.urlopen(request).close()
541 except urllib2.HTTPError as e: 541 except urllib2.HTTPError as e:
542 try: 542 try:
543 logging.error(e.read()) 543 logging.error(e.read())
544 finally: 544 finally:
545 e.close() 545 e.close()
546 raise 546 raise
547 547
548 self.add_to_downloads_lockfile( 548 self.add_to_downloads_lockfile(
549 self.config.type, 549 self.config.type,
550 { 550 {
551 'buildtype': 'devbuild', 551 'buildtype': 'devbuild',
552 'app_id': self.extensionID, 552 'app_id': self.extensionID,
553 'version': self.version, 553 'version': self.version,
554 } 554 },
555 ) 555 )
556 os.remove(self.path) 556 os.remove(self.path)
557 557
558 def download_from_mozilla_addons(self, buildtype, version, app_id): 558 def download_from_mozilla_addons(self, buildtype, version, app_id):
559 config = get_config() 559 config = get_config()
560 iss = config.get('extensions', 'amo_key') 560 iss = config.get('extensions', 'amo_key')
561 secret = config.get('extensions', 'amo_secret') 561 secret = config.get('extensions', 'amo_secret')
562 562
563 url = ('https://addons.mozilla.org/api/v3/addons/{}/' 563 url = ('https://addons.mozilla.org/api/v3/addons/{}/'
564 'versions/{}/').format(app_id, version) 564 'versions/{}/').format(app_id, version)
565 565
566 request = self.generate_mozilla_jwt_request( 566 request = self.generate_mozilla_jwt_request(
567 iss, secret, url, 'GET', 567 iss, secret, url, 'GET',
568 ) 568 )
569 response = json.load(urllib2.urlopen(request)) 569 response = json.load(urllib2.urlopen(request))
570 570
571 filename = '{}-{}.xpi'.format(self.basename, version) 571 filename = '{}-{}.xpi'.format(self.basename, version)
572 self.path = os.path.join( 572 self.path = os.path.join(
573 config.get('extensions', 'nightliesDirectory'), 573 config.get('extensions', 'nightliesDirectory'),
574 self.basename, 574 self.basename,
575 filename 575 filename,
576 ) 576 )
577 577
578 necessary = ['passed_review', 'reviewed', 'processed', 'valid'] 578 necessary = ['passed_review', 'reviewed', 'processed', 'valid']
579 if all(response[x] for x in necessary): 579 if all(response[x] for x in necessary):
580 download_url = response['files'][0]['download_url'] 580 download_url = response['files'][0]['download_url']
581 checksum = response['files'][0]['hash'] 581 checksum = response['files'][0]['hash']
582 582
583 request = self.generate_mozilla_jwt_request( 583 request = self.generate_mozilla_jwt_request(
584 iss, secret, download_url, 'GET', 584 iss, secret, download_url, 'GET',
585 ) 585 )
(...skipping 10 matching lines...) Expand all
596 if returned_checksum != checksum: 596 if returned_checksum != checksum:
597 logging.error('Checksum could not be verified: {} vs {}' 597 logging.error('Checksum could not be verified: {} vs {}'
598 ''.format(checksum, returned_checksum)) 598 ''.format(checksum, returned_checksum))
599 599
600 with open(self.path, 'w') as fp: 600 with open(self.path, 'w') as fp:
601 fp.write(file_content) 601 fp.write(file_content)
602 602
603 self.update_link = os.path.join( 603 self.update_link = os.path.join(
604 config.get('extensions', 'nightliesURL'), 604 config.get('extensions', 'nightliesURL'),
605 self.basename, 605 self.basename,
606 filename 606 filename,
607 ) 607 )
608 608
609 self.remove_from_downloads_lockfile(self.config.type, 609 self.remove_from_downloads_lockfile(self.config.type,
610 'version', 610 'version',
611 version) 611 version)
612 elif not response['passed_review'] or not response['valid']: 612 elif not response['passed_review'] or not response['valid']:
613 # When the review failed for any reason, we want to know about it 613 # When the review failed for any reason, we want to know about it
614 logging.error(json.dumps(response, indent=4)) 614 logging.error(json.dumps(response, indent=4))
615 self.remove_from_downloads_lockfile(self.config.type, 615 self.remove_from_downloads_lockfile(self.config.type,
616 'version', 616 'version',
617 version) 617 version)
618 618
619 def uploadToChromeWebStore(self): 619 def uploadToChromeWebStore(self):
620 620
621 opener = urllib2.build_opener(HTTPErrorBodyHandler) 621 opener = urllib2.build_opener(HTTPErrorBodyHandler)
622 622
623 # use refresh token to obtain a valid access token 623 # use refresh token to obtain a valid access token
624 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh 624 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
625 625
626 response = json.load(opener.open( 626 response = json.load(opener.open(
627 'https://accounts.google.com/o/oauth2/token', 627 'https://accounts.google.com/o/oauth2/token',
628 628
629 urlencode([ 629 urlencode([
630 ('refresh_token', self.config.refreshToken), 630 ('refresh_token', self.config.refreshToken),
631 ('client_id', self.config.clientID), 631 ('client_id', self.config.clientID),
632 ('client_secret', self.config.clientSecret), 632 ('client_secret', self.config.clientSecret),
633 ('grant_type', 'refresh_token'), 633 ('grant_type', 'refresh_token'),
634 ]) 634 ]),
635 )) 635 ))
636 636
637 auth_token = '%s %s' % (response['token_type'], response['access_token'] ) 637 auth_token = '%s %s' % (response['token_type'], response['access_token'] )
638 638
639 # upload a new version with the Chrome Web Store API 639 # upload a new version with the Chrome Web Store API
640 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitn g 640 # https://developer.chrome.com/webstore/using_webstore_api#uploadexisitn g
641 641
642 request = urllib2.Request('https://www.googleapis.com/upload/chromewebst ore/v1.1/items/' + self.config.devbuildGalleryID) 642 request = urllib2.Request('https://www.googleapis.com/upload/chromewebst ore/v1.1/items/' + self.config.devbuildGalleryID)
643 request.get_method = lambda: 'PUT' 643 request.get_method = lambda: 'PUT'
644 request.add_header('Authorization', auth_token) 644 request.add_header('Authorization', auth_token)
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
742 def upload_to_windows_store(self): 742 def upload_to_windows_store(self):
743 opener = urllib2.build_opener(HTTPErrorBodyHandler) 743 opener = urllib2.build_opener(HTTPErrorBodyHandler)
744 744
745 headers = {'Authorization': self.get_windows_store_access_token(), 745 headers = {'Authorization': self.get_windows_store_access_token(),
746 'Content-type': 'application/json'} 746 'Content-type': 'application/json'}
747 747
748 # Get application 748 # Get application
749 # https://docs.microsoft.com/en-us/windows/uwp/monetize/get-an-app 749 # https://docs.microsoft.com/en-us/windows/uwp/monetize/get-an-app
750 api_path = '{}/v1.0/my/applications/{}'.format( 750 api_path = '{}/v1.0/my/applications/{}'.format(
751 'https://manage.devcenter.microsoft.com', 751 'https://manage.devcenter.microsoft.com',
752 self.config.devbuildGalleryID 752 self.config.devbuildGalleryID,
753 ) 753 )
754 754
755 request = urllib2.Request(api_path, None, headers) 755 request = urllib2.Request(api_path, None, headers)
756 with contextlib.closing(opener.open(request)) as response: 756 with contextlib.closing(opener.open(request)) as response:
757 app_obj = json.load(response) 757 app_obj = json.load(response)
758 758
759 # Delete existing in-progress submission 759 # Delete existing in-progress submission
760 # https://docs.microsoft.com/en-us/windows/uwp/monetize/delete-an-app-su bmission 760 # https://docs.microsoft.com/en-us/windows/uwp/monetize/delete-an-app-su bmission
761 submissions_path = api_path + '/submissions' 761 submissions_path = api_path + '/submissions'
762 if 'pendingApplicationSubmission' in app_obj: 762 if 'pendingApplicationSubmission' in app_obj:
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
889 # write update manifest 889 # write update manifest
890 self.writeUpdateManifest() 890 self.writeUpdateManifest()
891 891
892 # retire old builds 892 # retire old builds
893 versions = self.retireBuilds() 893 versions = self.retireBuilds()
894 # update index page 894 # update index page
895 self.updateIndex(versions) 895 self.updateIndex(versions)
896 896
897 # Update soft link to latest build 897 # Update soft link to latest build
898 baseDir = os.path.join( 898 baseDir = os.path.join(
899 self.config.nightliesDirectory, self.basename 899 self.config.nightliesDirectory, self.basename,
900 ) 900 )
901 linkPath = os.path.join( 901 linkPath = os.path.join(
902 baseDir, '00latest' + self.config.packageSuffix 902 baseDir, '00latest' + self.config.packageSuffix,
903 ) 903 )
904 904
905 self.symlink_or_copy(self.path, linkPath) 905 self.symlink_or_copy(self.path, linkPath)
906 finally: 906 finally:
907 # clean up 907 # clean up
908 if self.tempdir: 908 if self.tempdir:
909 shutil.rmtree(self.tempdir, ignore_errors=True) 909 shutil.rmtree(self.tempdir, ignore_errors=True)
910 910
911 911
912 def main(download=False): 912 def main(download=False):
(...skipping 23 matching lines...) Expand all
936 936
937 file = open(nightlyConfigFile, 'wb') 937 file = open(nightlyConfigFile, 'wb')
938 nightlyConfig.write(file) 938 nightlyConfig.write(file)
939 939
940 940
941 if __name__ == '__main__': 941 if __name__ == '__main__':
942 parser = argparse.ArgumentParser() 942 parser = argparse.ArgumentParser()
943 parser.add_argument('--download', action='store_true', default=False) 943 parser.add_argument('--download', action='store_true', default=False)
944 args = parser.parse_args() 944 args = parser.parse_args()
945 main(args.download) 945 main(args.download)
LEFTRIGHT

Powered by Google App Engine
This is Rietveld