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

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

Issue 29374637: Issue 4549 - Implement the Windows Store API to upload development builds (Closed)
Left Patch Set: Add .sitescripts.example changes Created Feb. 6, 2017, 9:47 p.m.
Right Patch Set: Rebase Created Feb. 16, 2017, 1:14 p.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 | « ensure_dependencies.py ('k') | sitescripts/extensions/test/sitescripts.ini.template » ('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-2016 Eyeo GmbH 2 # Copyright (C) 2006-2016 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
11 # GNU General Public License for more details. 11 # GNU General Public License for more details.
12 # 12 #
13 # You should have received a copy of the GNU General Public License 13 # You should have received a copy of the GNU General Public License
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
15 15
16 """ 16 """
17 17
18 Nightly builds generation script 18 Nightly builds generation script
19 ================================ 19 ================================
20 20
21 This script generates nightly builds of extensions, together 21 This script generates nightly builds of extensions, together
22 with changelogs and documentation. 22 with changelogs and documentation.
23 23
24 """ 24 """
25 25
26 import ConfigParser 26 import ConfigParser
27 import base64 27 import base64
28 from datetime import datetime
29 import hashlib 28 import hashlib
30 import hmac 29 import hmac
31 import json 30 import json
32 import logging 31 import logging
33 import os 32 import os
34 import pipes 33 import pipes
35 import random 34 import random
36 import shutil 35 import shutil
37 import struct 36 import struct
38 import subprocess 37 import subprocess
39 import sys 38 import sys
40 import tempfile 39 import tempfile
41 import time 40 import time
42 from urllib import urlencode 41 from urllib import urlencode
43 import urllib2 42 import urllib2
44 import urlparse 43 import urlparse
45 import httplib
46 import zipfile 44 import zipfile
45 import contextlib
47 46
48 from xml.dom.minidom import parse as parseXml 47 from xml.dom.minidom import parse as parseXml
49 48
50 from sitescripts.extensions.utils import ( 49 from sitescripts.extensions.utils import (
51 compareVersions, Configuration, 50 compareVersions, Configuration,
52 writeAndroidUpdateManifest 51 writeAndroidUpdateManifest
53 ) 52 )
54 from sitescripts.utils import get_config, get_template 53 from sitescripts.utils import get_config, get_template
55 54
56 MAX_BUILDS = 50 55 MAX_BUILDS = 50
56
57
58 # Google and Microsoft APIs use HTTP error codes with error message in
59 # body. So we add the response body to the HTTPError to get more
60 # meaningful error messages.
61 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler):
62 def http_error_default(self, req, fp, code, msg, hdrs):
63 raise urllib2.HTTPError(req.get_full_url(), code,
64 '{}\n{}'.format(msg, fp.read()), hdrs, fp)
57 65
58 66
59 class NightlyBuild(object): 67 class NightlyBuild(object):
60 """ 68 """
61 Performs the build process for an extension, 69 Performs the build process for an extension,
62 generating changelogs and documentation. 70 generating changelogs and documentation.
63 """ 71 """
64 72
65 def __init__(self, config): 73 def __init__(self, config):
66 """ 74 """
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 metadata = packager.readMetadata(self.tempdir, self.config.type) 236 metadata = packager.readMetadata(self.tempdir, self.config.type)
229 certs = xarfile.read_certificates_and_key(self.config.keyFile)[0] 237 certs = xarfile.read_certificates_and_key(self.config.keyFile)[0]
230 238
231 self.certificateID = packager.get_developer_identifier(certs) 239 self.certificateID = packager.get_developer_identifier(certs)
232 self.version = packager.getBuildVersion(self.tempdir, metadata, False, 240 self.version = packager.getBuildVersion(self.tempdir, metadata, False,
233 self.buildNum) 241 self.buildNum)
234 self.shortVersion = metadata.get('general', 'version') 242 self.shortVersion = metadata.get('general', 'version')
235 self.basename = metadata.get('general', 'basename') 243 self.basename = metadata.get('general', 'basename')
236 self.updatedFromGallery = False 244 self.updatedFromGallery = False
237 245
238 def readEdgeMetadata(self): 246 def read_edge_metadata(self):
239 """ 247 """
240 Read Edge-specific metadata from metadata file. 248 Read Edge-specific metadata from metadata file.
241 """ 249 """
242 import buildtools.packagerEdge as packagerEdge 250 from buildtools import packager
Vasily Kuznetsov 2017/02/07 12:38:15 You don't really need the `import ... as ...` synt
Oleksandr 2017/02/09 00:57:14 Done.
243 # Now read metadata file 251 # Now read metadata file
244 metadata = packagerEdge.packager.readMetadata(self.tempdir, 252 metadata = packager.readMetadata(self.tempdir, self.config.type)
Vasily Kuznetsov 2017/02/07 12:38:15 This `packagerEdge.packager` is actually `buildtoo
Oleksandr 2017/02/09 00:57:15 Done.
245 self.config.type) 253 self.version = packager.getBuildVersion(self.tempdir, metadata, False,
246 self.version = packagerEdge.packager.getBuildVersion(self.tempdir, 254 self.buildNum)
247 metadata, False,
248 self.buildNum)
249 self.basename = metadata.get('general', 'basename') 255 self.basename = metadata.get('general', 'basename')
250 256
251 self.compat = [] 257 self.compat = []
252 258
253 def writeUpdateManifest(self): 259 def writeUpdateManifest(self):
254 """ 260 """
255 Writes update manifest for the current build 261 Writes update manifest for the current build
256 """ 262 """
257 baseDir = os.path.join(self.config.nightliesDirectory, self.basename) 263 baseDir = os.path.join(self.config.nightliesDirectory, self.basename)
258 if self.config.type == 'safari': 264 if self.config.type == 'safari':
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after
469 try: 475 try:
470 urllib2.urlopen(request).close() 476 urllib2.urlopen(request).close()
471 except urllib2.HTTPError as e: 477 except urllib2.HTTPError as e:
472 try: 478 try:
473 logging.error(e.read()) 479 logging.error(e.read())
474 finally: 480 finally:
475 e.close() 481 e.close()
476 raise 482 raise
477 483
478 def uploadToChromeWebStore(self): 484 def uploadToChromeWebStore(self):
479 # Google APIs use HTTP error codes with error message in body. So we add
480 # the response body to the HTTPError to get more meaningful error messag es.
481
482 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler):
483 def http_error_default(self, req, fp, code, msg, hdrs):
484 raise urllib2.HTTPError(req.get_full_url(), code, '%s\n%s' % (ms g, fp.read()), hdrs, fp)
485 485
486 opener = urllib2.build_opener(HTTPErrorBodyHandler) 486 opener = urllib2.build_opener(HTTPErrorBodyHandler)
487 487
488 # use refresh token to obtain a valid access token 488 # use refresh token to obtain a valid access token
489 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh 489 # https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
490 490
491 response = json.load(opener.open( 491 response = json.load(opener.open(
492 'https://accounts.google.com/o/oauth2/token', 492 'https://accounts.google.com/o/oauth2/token',
493 493
494 urlencode([ 494 urlencode([
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
531 request.get_method = lambda: 'POST' 531 request.get_method = lambda: 'POST'
532 request.add_header('Authorization', auth_token) 532 request.add_header('Authorization', auth_token)
533 request.add_header('x-goog-api-version', '2') 533 request.add_header('x-goog-api-version', '2')
534 request.add_header('Content-Length', '0') 534 request.add_header('Content-Length', '0')
535 535
536 response = json.load(opener.open(request)) 536 response = json.load(opener.open(request))
537 537
538 if any(status not in ('OK', 'ITEM_PENDING_REVIEW') for status in respons e['status']): 538 if any(status not in ('OK', 'ITEM_PENDING_REVIEW') for status in respons e['status']):
539 raise Exception({'status': response['status'], 'statusDetail': respo nse['statusDetail']}) 539 raise Exception({'status': response['status'], 'statusDetail': respo nse['statusDetail']})
540 540
541 def uploadToWindowsStore(self): 541 def get_windows_store_access_token(self):
Vasily Kuznetsov 2017/02/07 12:38:16 This method is quite long. It seems like it basica
Sebastian Noack 2017/02/07 13:54:43 Also new methods (and variables) should use unders
Oleksandr 2017/02/09 00:57:15 Done.
542
Vasily Kuznetsov 2017/02/07 12:38:16 Also the ingestionConnection-related code seems to
Oleksandr 2017/02/09 00:57:15 Done.
543 class HTTPErrorBodyHandler(urllib2.HTTPDefaultErrorHandler):
544 def http_error_default(self, req, fp, code, msg, hdrs):
545 raise urllib2.HTTPError(req.get_full_url(), code,
546 '%s\n%s' % (msg, fp.read()), hdrs, fp)
Vasily Kuznetsov 2017/02/07 12:38:16 You're reading the content of `fp` and at the same
Vasily Kuznetsov 2017/02/07 12:38:16 Our style guide now recommends using `'{} bla {}'.
Sebastian Noack 2017/02/07 13:54:42 The thing is HttpError objects are both exceptions
Oleksandr 2017/02/09 00:57:15 I have moved all the networking code to just httpl
547
548 opener = urllib2.build_opener(HTTPErrorBodyHandler)
549
550 # use refresh token to obtain a valid access token 542 # use refresh token to obtain a valid access token
551 # https://docs.microsoft.com/en-us/azure/active-directory/active-directo ry-protocols-oauth-code#refreshing-the-access-tokens 543 # https://docs.microsoft.com/en-us/azure/active-directory/active-directo ry-protocols-oauth-code#refreshing-the-access-tokens
552 544 server = 'https://login.microsoftonline.com'
553 response = json.load(opener.open( 545 token_path = '{}/{}/oauth2/token'.format(server, self.config.tenantID)
Sebastian Noack 2017/02/07 13:54:42 The response object isn't closed here. The canonic
554 'https://login.microsoftonline.com/{0}/oauth2/token'.format( 546
Sebastian Noack 2017/02/07 13:54:43 Thanks for using the format() method here, but the
555 self.config.tenantID), 547 opener = urllib2.build_opener(HTTPErrorBodyHandler)
556 urlencode([ 548 post_data = urlencode([
557 ('refresh_token', self.config.refreshToken), 549 ('refresh_token', self.config.refreshToken),
558 ('client_id', self.config.clientID), 550 ('client_id', self.config.clientID),
559 ('client_secret', self.config.clientSecret), 551 ('client_secret', self.config.clientSecret),
560 ('grant_type', 'refresh_token'), 552 ('grant_type', 'refresh_token'),
561 ('resource', 'https://graph.windows.net') 553 ('resource', 'https://graph.windows.net')
562 ]) 554 ])
563 )) 555 request = urllib2.Request(token_path, post_data)
564 556 with contextlib.closing(opener.open(request)) as response:
565 auth_token = response['token_type'] + ' ' + response['access_token'] 557 data = json.load(response)
Sebastian Noack 2017/02/07 13:54:42 As agreed on in our coding practices, we use the f
Oleksandr 2017/02/09 00:57:16 Done.
566 558 auth_token = '{0[token_type]} {0[access_token]}'.format(data)
567 # Clone the previous submission for the new one. Largely based on code 559
568 # from https://msdn.microsoft.com/en-us/windows/uwp/monetize/python-code -examples-for-the-windows-store-submission-api#create-an-app-submission 560 return auth_token
Vasily Kuznetsov 2017/02/07 12:38:15 This is a very long line. I wonder if we could use
Sebastian Noack 2017/02/07 13:54:42 I'd rather not obfuscate URLs in the source code (
Vasily Kuznetsov 2017/02/07 15:05:26 Yeah, good point. I also don't see an ideal soluti
Oleksandr 2017/02/09 00:57:15 Acknowledged.
569 headers = {'Authorization': auth_token, 561
570 'Content-type': 'application/json', 562 def upload_appx_file_to_windows_store(self, file_upload_url):
571 'User-Agent': 'Python'} 563 # Add .appx file to a .zip file
Sebastian Noack 2017/02/07 13:54:42 Is it even necessary to set a User-Agent here?
Oleksandr 2017/02/09 00:57:15 It is not. Removed.
572 564 zip_path = os.path.splitext(self.path)[0] + '.zip'
573 apiServer = 'manage.devcenter.microsoft.com' 565 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
Vasily Kuznetsov 2017/02/07 12:38:15 PEP8 (and therefore our style guide) recommends lo
Sebastian Noack 2017/02/07 13:54:43 As I said above, I think we should also avoid came
Oleksandr 2017/02/09 00:57:15 Done.
574 ingestionConnection = httplib.HTTPSConnection(apiServer) 566 zf.write(self.path, os.path.basename(self.path))
Sebastian Noack 2017/02/07 13:54:42 "ingestion" seems like a weird term to use here, i
Oleksandr 2017/02/09 00:57:15 Done.
567
568 # Upload that .zip file
569 file_upload_url = file_upload_url.replace('+', '%2B')
570 request = urllib2.Request(file_upload_url)
571 request.get_method = lambda: 'PUT'
572 request.add_header('x-ms-blob-type', 'BlockBlob')
573
574 opener = urllib2.build_opener(HTTPErrorBodyHandler)
575
576 with open(zip_path, 'rb') as file:
577 request.add_header('Content-Length',
578 os.fstat(file.fileno()).st_size - file.tell())
579 request.add_data(file)
580 opener.open(request).close()
581
582 # Clone the previous submission for the new one. Largely based on code
583 # from https://msdn.microsoft.com/en-us/windows/uwp/monetize/python-code-exa mples-for-the-windows-store-submission-api#create-an-app-submission
584 def upload_to_windows_store(self):
585 opener = urllib2.build_opener(HTTPErrorBodyHandler)
586
587 headers = {'Authorization': self.get_windows_store_access_token(),
588 'Content-type': 'application/json'}
575 589
576 # Get application 590 # Get application
577 appPath = '/v1.0/my/applications/%s' % self.config.devbuildGalleryID 591 # https://docs.microsoft.com/en-us/windows/uwp/monetize/get-an-app
578 592 api_path = '{}/v1.0/my/applications/{}'.format(
579 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on 593 'https://manage.devcenter.microsoft.com',
Vasily Kuznetsov 2017/02/07 12:38:15 Is this URL relevant to what we're doing here? The
Oleksandr 2017/02/09 00:57:16 Fixed, and also same for URL below.
580 ingestionConnection.request('GET', appPath, '', headers) 594 self.config.devbuildGalleryID
581 appResponse = ingestionConnection.getresponse() 595 )
596
597 request = urllib2.Request(api_path, None, headers)
598 with contextlib.closing(opener.open(request)) as response:
599 app_obj = json.load(response)
582 600
583 # Delete existing in-progress submission 601 # Delete existing in-progress submission
584 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/get-an-add-on 602 # https://docs.microsoft.com/en-us/windows/uwp/monetize/delete-an-app-su bmission
585 appObj = json.loads(appResponse.read().decode()) 603 submissions_path = api_path + '/submissions'
586 604 if 'pendingApplicationSubmission' in app_obj:
587 submissionsPath = appPath + '/submissions' 605 remove_id = app_obj['pendingApplicationSubmission']['id']
588 if 'pendingApplicationSubmission' in appObj: 606 remove_path = '{}/{}'.format(submissions_path, remove_id)
589 removeId = appObj['pendingApplicationSubmission']['id'] 607 request = urllib2.Request(remove_path, '', headers)
590 ingestionConnection.request('DELETE', 608 request.get_method = lambda: 'DELETE'
591 '%s/%s' % (submissionsPath, removeId), 609 opener.open(request).close()
592 '', headers)
593 deleteSubmissionResponse = ingestionConnection.getresponse()
594 deleteSubmissionResponse.read()
Vasily Kuznetsov 2017/02/07 12:38:15 Should we do some error checking here? Or will we
Sebastian Noack 2017/02/07 13:54:42 Also what about closing the response?
Oleksandr 2017/02/09 00:57:14 If successful, the response would have an empty bo
Oleksandr 2017/02/09 00:57:15 I don't think we have to close the response. We wi
595 610
596 # Create submission 611 # Create submission
597 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/create-an-app-su bmission 612 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/create-an-app-su bmission
598 ingestionConnection.request('POST', submissionsPath, '', headers) 613 request = urllib2.Request(submissions_path, '', headers)
599 createSubmissionResponse = ingestionConnection.getresponse() 614 request.get_method = lambda: 'POST'
600 615 with contextlib.closing(opener.open(request)) as response:
601 submission = json.loads( 616 submission = json.load(response)
602 createSubmissionResponse.read().decode() 617
603 ) 618 submission_id = submission['id']
604 619 file_upload_url = submission['fileUploadUrl']
605 submissionId = submission['id'] 620
606 fileUploadUrl = submission['fileUploadUrl'] 621 new_submission_path = '{}/{}'.format(submissions_path,
607 622 submission_id)
608 # Update submission 623
609 oldSubmission = submission['applicationPackages'][0] 624 request = urllib2.Request(new_submission_path, None, headers)
610 oldSubmission['fileStatus'] = 'PendingDelete' 625 opener.open(request).close()
611 submission['applicationPackages'].append( 626
612 {'fileStatus': 'PendingUpload'}) 627 self.upload_appx_file_to_windows_store(file_upload_url)
613 addedSubmission = submission['applicationPackages'][1]
614 addedSubmission['fileName'] = os.path.basename(self.path)
615 addedSubmission['minimumSystemRam'] = oldSubmission['minimumSystemRam']
616
617 oldDirectXVersion = oldSubmission['minimumDirectXVersion']
618 addedSubmission['minimumDirectXVersion'] = oldDirectXVersion
619
620 newSubmissionPath = '%s/%s' % (submissionsPath, submissionId)
621 ingestionConnection.request('PUT', newSubmissionPath,
622 json.dumps(submission), headers)
623 ingestionConnection.getresponse().read()
624
625 # Add .appx file to a .zip file
626 zipPath = os.path.splitext(self.path)[0] + '.zip'
627 with zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED) as zf:
628 zf.write(self.path, os.path.basename(self.path))
629
630 # Upload that .zip file
631 request = urllib2.Request(fileUploadUrl.replace('+', '%2B'))
632 request.get_method = lambda: 'PUT'
633 request.add_header('x-ms-blob-type', 'BlockBlob')
634
635 with open(zipPath, 'rb') as file:
636 fileSize = os.fstat(file.fileno()).st_size - file.tell()
637 request.add_header('Content-Length', fileSize)
638 request.add_data(file)
639 opener.open(request)
640 628
641 # Commit submission 629 # Commit submission
642 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-su bmission 630 # https://msdn.microsoft.com/en-us/windows/uwp/monetize/commit-an-app-su bmission
643 ingestionConnection.request('POST', 631 commit_path = '{}/commit'.format(new_submission_path)
644 newSubmissionPath + '/commit', 632 request = urllib2.Request(commit_path, '', headers)
645 '', headers) 633 request.get_method = lambda: 'POST'
646 submission = json.loads( 634 with contextlib.closing(opener.open(request)) as response:
Sebastian Noack 2017/02/07 13:54:42 Couldn't you simply use json.load(ingestionConnect
647 ingestionConnection.getresponse().read().decode() 635 submission = json.load(response)
648 )
649 636
650 if submission['status'] != 'CommitStarted': 637 if submission['status'] != 'CommitStarted':
651 raise Exception({'status': submission['status'], 638 raise Exception({'status': submission['status'],
652 'statusDetails': submission['statusDetails']}) 639 'statusDetails': submission['statusDetails']})
Sebastian Noack 2017/02/07 13:54:43 The indentation here seems off. The keys should be
Oleksandr 2017/02/09 00:57:14 Done.
653 ingestionConnection.close()
Sebastian Noack 2017/02/07 13:54:42 It would be better to use the with-statement (or t
Oleksandr 2017/02/09 00:57:15 Done.
654 640
655 def run(self): 641 def run(self):
656 """ 642 """
657 Run the nightly build process for one extension 643 Run the nightly build process for one extension
658 """ 644 """
659 try: 645 try:
660 if self.config.type == 'ie': 646 if self.config.type == 'ie':
661 # We cannot build IE builds, simply list the builds already in 647 # We cannot build IE builds, simply list the builds already in
662 # the directory. Basename has to be deduced from the repository name. 648 # the directory. Basename has to be deduced from the repository name.
663 self.basename = os.path.basename(self.config.repository) 649 self.basename = os.path.basename(self.config.repository)
664 else: 650 else:
665 # copy the repository into a temporary directory 651 # copy the repository into a temporary directory
666 self.copyRepository() 652 self.copyRepository()
667 self.buildNum = self.getCurrentBuild() 653 self.buildNum = self.getCurrentBuild()
668 654
669 # get meta data from the repository 655 # get meta data from the repository
670 if self.config.type == 'android': 656 if self.config.type == 'android':
671 self.readAndroidMetadata() 657 self.readAndroidMetadata()
672 elif self.config.type == 'chrome': 658 elif self.config.type == 'chrome':
673 self.readChromeMetadata() 659 self.readChromeMetadata()
674 elif self.config.type == 'safari': 660 elif self.config.type == 'safari':
675 self.readSafariMetadata() 661 self.readSafariMetadata()
676 elif self.config.type in {'gecko', 'gecko-webext'}: 662 elif self.config.type in {'gecko', 'gecko-webext'}:
677 self.readGeckoMetadata() 663 self.readGeckoMetadata()
678 elif self.config.type == 'edge': 664 elif self.config.type == 'edge':
679 self.readEdgeMetadata() 665 self.read_edge_metadata()
680 else: 666 else:
681 raise Exception('Unknown build type {}' % self.config.type) 667 raise Exception('Unknown build type {}' % self.config.type)
682 668
683 # create development build 669 # create development build
684 self.build() 670 self.build()
685 671
686 # write out changelog 672 # write out changelog
687 self.writeChangelog(self.getChanges()) 673 self.writeChangelog(self.getChanges())
688 674
689 # write update manifest 675 # write update manifest
(...skipping 11 matching lines...) Expand all
701 687
702 # update nightlies config 688 # update nightlies config
703 self.config.latestRevision = self.revision 689 self.config.latestRevision = self.revision
704 690
705 if (self.config.type in {'gecko', 'gecko-webext'} and 691 if (self.config.type in {'gecko', 'gecko-webext'} and
706 self.config.galleryID and 692 self.config.galleryID and
707 get_config().has_option('extensions', 'amo_key')): 693 get_config().has_option('extensions', 'amo_key')):
708 self.uploadToMozillaAddons() 694 self.uploadToMozillaAddons()
709 elif self.config.type == 'chrome' and self.config.clientID and self. config.clientSecret and self.config.refreshToken: 695 elif self.config.type == 'chrome' and self.config.clientID and self. config.clientSecret and self.config.refreshToken:
710 self.uploadToChromeWebStore() 696 self.uploadToChromeWebStore()
711 elif self.config.type == 'edge' and self.config.clientID and self.co nfig.clientSecret and self.config.refreshToken and self.config.tenantID: 697 elif self.config.type == 'edge' and self.config.clientID and self.co nfig.clientSecret and self.config.refreshToken and self.config.tenantID:
Vasily Kuznetsov 2017/02/07 12:38:15 What happens if we don't have `config.clientID` or
Sebastian Noack 2017/02/07 13:54:42 At least this seems consistent with the equivalane
Vasily Kuznetsov 2017/02/07 15:05:26 Acknowledged.
712 self.uploadToWindowsStore() 698 self.upload_to_windows_store()
713 699
714 finally: 700 finally:
715 # clean up 701 # clean up
716 if self.tempdir: 702 if self.tempdir:
717 shutil.rmtree(self.tempdir, ignore_errors=True) 703 shutil.rmtree(self.tempdir, ignore_errors=True)
718 704
719 705
720 def main(): 706 def main():
721 """ 707 """
722 main function for createNightlies.py 708 main function for createNightlies.py
(...skipping 15 matching lines...) Expand all
738 except Exception as ex: 724 except Exception as ex:
739 logging.error('The build for %s failed:', repo) 725 logging.error('The build for %s failed:', repo)
740 logging.exception(ex) 726 logging.exception(ex)
741 727
742 file = open(nightlyConfigFile, 'wb') 728 file = open(nightlyConfigFile, 'wb')
743 nightlyConfig.write(file) 729 nightlyConfig.write(file)
744 730
745 731
746 if __name__ == '__main__': 732 if __name__ == '__main__':
747 main() 733 main()
LEFTRIGHT

Powered by Google App Engine
This is Rietveld