Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(115)

Side by Side Diff: build/android/play_services/update.py

Issue 1418833005: [gms updater] Fixes and tests to prepare activation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address comments Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/android/PRESUBMIT.py ('k') | build/android/play_services/update_test.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 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 ''' 6 '''
7 Script to help uploading and downloading the Google Play services client 7 Script to help uploading and downloading the Google Play services client
8 library to and from a Google Cloud storage. 8 library to and from a Google Cloud storage.
9 ''' 9 '''
10 10
(...skipping 19 matching lines...) Expand all
30 import download_from_google_storage 30 import download_from_google_storage
31 import upload_to_google_storage 31 import upload_to_google_storage
32 32
33 33
34 # Directory where the SHA1 files for the zip and the license are stored 34 # Directory where the SHA1 files for the zip and the license are stored
35 # It should be managed by git to provided information about new versions. 35 # It should be managed by git to provided information about new versions.
36 SHA1_DIRECTORY = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', 36 SHA1_DIRECTORY = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android',
37 'play_services') 37 'play_services')
38 38
39 # Default bucket used for storing the files. 39 # Default bucket used for storing the files.
40 GMS_CLOUD_STORAGE = 'chrome-sdk-extras' 40 GMS_CLOUD_STORAGE = 'chromium-android-tools/play-services'
41 41
42 # Path to the default configuration file. It exposes the currently installed 42 # Path to the default configuration file. It exposes the currently installed
43 # version of the library in a human readable way. 43 # version of the library in a human readable way.
44 CONFIG_DEFAULT_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 44 CONFIG_DEFAULT_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'build',
45 'android', 'play_services', 'config.json') 45 'android', 'play_services', 'config.json')
46 46
47 LICENSE_FILE_NAME = 'LICENSE' 47 LICENSE_FILE_NAME = 'LICENSE'
48 LIBRARY_FILE_NAME = 'google_play_services_library.zip' 48 LIBRARY_FILE_NAME = 'google_play_services_library.zip'
49 GMS_PACKAGE_ID = 'extra-google-google_play_services' # used by sdk manager 49 GMS_PACKAGE_ID = 'extra-google-google_play_services' # used by sdk manager
50 50
51 LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P<text>.*)$', re.MULTILINE) 51 LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P<text>.*)$', re.MULTILINE)
52 52
53 53
54 def Main(): 54 def main(raw_args):
55 parser = argparse.ArgumentParser( 55 parser = argparse.ArgumentParser(
56 description=__doc__ + 'Please see the subcommand help for more details.', 56 description=__doc__ + 'Please see the subcommand help for more details.',
57 formatter_class=utils.DefaultsRawHelpFormatter) 57 formatter_class=utils.DefaultsRawHelpFormatter)
58 subparsers = parser.add_subparsers(title='commands') 58 subparsers = parser.add_subparsers(title='commands')
59 59
60 # Download arguments 60 # Download arguments
61 parser_download = subparsers.add_parser( 61 parser_download = subparsers.add_parser(
62 'download', 62 'download',
63 help='download the library from the cloud storage', 63 help='download the library from the cloud storage',
64 description=Download.__doc__, 64 description=Download.__doc__,
65 formatter_class=utils.DefaultsRawHelpFormatter) 65 formatter_class=utils.DefaultsRawHelpFormatter)
66 parser_download.add_argument('-f', '--force',
67 action='store_true',
68 help=('run even if the local version is '
69 'already up to date'))
70 parser_download.set_defaults(func=Download) 66 parser_download.set_defaults(func=Download)
71 AddCommonArguments(parser_download) 67 AddBasicArguments(parser_download)
68 AddBucketArguments(parser_download)
72 69
73 # SDK Update arguments 70 # SDK Update arguments
74 parser_sdk = subparsers.add_parser( 71 parser_sdk = subparsers.add_parser(
75 'sdk', 72 'sdk',
76 help='update the local sdk using the Android SDK Manager', 73 help='update the local sdk using the Android SDK Manager',
77 description=UpdateSdk.__doc__, 74 description=UpdateSdk.__doc__,
78 formatter_class=utils.DefaultsRawHelpFormatter) 75 formatter_class=utils.DefaultsRawHelpFormatter)
79 parser_sdk.add_argument('--sdk-root',
80 help=('base path to the Android SDK tools to use to '
81 'update the library'),
82 default=constants.ANDROID_SDK_ROOT)
83 parser_sdk.add_argument('-v', '--verbose',
84 action='store_true',
85 help='print debug information')
86 parser_sdk.set_defaults(func=UpdateSdk) 76 parser_sdk.set_defaults(func=UpdateSdk)
77 AddBasicArguments(parser_sdk)
87 78
88 # Upload arguments 79 # Upload arguments
89 parser_upload = subparsers.add_parser( 80 parser_upload = subparsers.add_parser(
90 'upload', 81 'upload',
91 help='upload the library to the cloud storage', 82 help='upload the library to the cloud storage',
92 description=Upload.__doc__, 83 description=Upload.__doc__,
93 formatter_class=utils.DefaultsRawHelpFormatter) 84 formatter_class=utils.DefaultsRawHelpFormatter)
94 parser_upload.add_argument('-f', '--force', 85
95 action='store_true',
96 help=('run even if the checked in version is '
97 'already up to date'))
98 parser_upload.add_argument('--sdk-root',
99 help=('base path to the Android SDK tools to use '
100 'to update the library'),
101 default=constants.ANDROID_SDK_ROOT)
102 parser_upload.add_argument('--skip-git', 86 parser_upload.add_argument('--skip-git',
103 action='store_true', 87 action='store_true',
104 help="don't commit the changes at the end") 88 help="don't commit the changes at the end")
105 parser_upload.set_defaults(func=Upload) 89 parser_upload.set_defaults(func=Upload)
106 AddCommonArguments(parser_upload) 90 AddBasicArguments(parser_upload)
91 AddBucketArguments(parser_upload)
107 92
108 args = parser.parse_args() 93 args = parser.parse_args(raw_args)
jbudorick 2015/11/04 18:46:07 parse_args() will pick up sys.argv[1:] and parse t
dgn 2015/11/05 12:15:10 It's the case in update_test.py, where I call main
jbudorick 2015/11/05 16:03:02 oooooh I missed that. This is fine, then.
109 if args.verbose: 94 if args.verbose:
110 logging.basicConfig(level=logging.DEBUG) 95 logging.basicConfig(level=logging.DEBUG)
111 logging_utils.ColorStreamHandler.MakeDefault() 96 logging_utils.ColorStreamHandler.MakeDefault(not _IsBotEnvironment())
112 return args.func(args) 97 return args.func(args)
113 98
114 99
115 def AddCommonArguments(parser): 100 def AddBasicArguments(parser):
116 ''' 101 '''
117 Defines the common arguments on subparser rather than the main one. This 102 Defines the common arguments on subparser rather than the main one. This
118 allows to put arguments after the command: `foo.py upload --debug --force` 103 allows to put arguments after the command: `foo.py upload --debug --force`
119 instead of `foo.py --debug upload --force` 104 instead of `foo.py --debug upload --force`
120 ''' 105 '''
121 106
122 parser.add_argument('--bucket', 107 parser.add_argument('--sdk-root',
123 help='name of the bucket where the files are stored', 108 help=('base path to the Android SDK tools to use when'
124 default=GMS_CLOUD_STORAGE) 109 'updating the library'),
125 parser.add_argument('--config', 110 default=constants.ANDROID_SDK_ROOT)
126 help='JSON Configuration file', 111
127 default=CONFIG_DEFAULT_PATH)
128 parser.add_argument('--dry-run',
129 action='store_true',
130 help=('run the script in dry run mode. Files will be '
131 'copied to a local directory instead of the cloud '
132 'storage. The bucket name will be as path to that '
133 'directory relative to the repository root.'))
134 parser.add_argument('-v', '--verbose', 112 parser.add_argument('-v', '--verbose',
135 action='store_true', 113 action='store_true',
136 help='print debug information') 114 help='print debug information')
137 115
138 116
117 def AddBucketArguments(parser):
118 parser.add_argument('--bucket',
119 help='name of the bucket where the files are stored',
120 default=GMS_CLOUD_STORAGE)
121
122 parser.add_argument('--config',
123 help='JSON Configuration file',
124 default=CONFIG_DEFAULT_PATH)
125
126 parser.add_argument('--dry-run',
127 action='store_true',
128 help=('run the script in dry run mode. Files will be '
129 'copied to a local directory instead of the '
130 'cloud storage. The bucket name will be as path '
131 'to that directory relative to the repository '
132 'root.'))
133
134 parser.add_argument('-f', '--force',
135 action='store_true',
136 help=('run even if the library is already '
137 ' up to date'))
138
139
139 def Download(args): 140 def Download(args):
140 ''' 141 '''
141 Downloads the Google Play services client library from a Google Cloud Storage 142 Downloads the Google Play services client library from a Google Cloud Storage
142 bucket and installs it to 143 bucket and installs it to
143 //third_party/android_tools/sdk/extras/google/google_play_services. 144 //third_party/android_tools/sdk/extras/google/google_play_services.
144 145
145 A license check will be made, and the user might have to accept the license 146 A license check will be made, and the user might have to accept the license
146 if that has not been done before. 147 if that has not been done before.
147 ''' 148 '''
148 149
149 paths = _InitPaths(constants.ANDROID_SDK_ROOT) 150 if not os.path.isdir(args.sdk_root):
151 logging.debug('Did not find the android sdk directory at "%s".',
152 args.sdk_root)
153 if not args.force:
154 logging.info('Skipping, not on an android checkout.')
155 return 0
156
157 paths = _InitPaths(args.sdk_root)
158
159 if os.path.isdir(paths.package) and not os.access(paths.package, os.W_OK):
160 logging.error('Failed updating the Google Play Services library. '
161 'The location is not writable. Please remove the '
162 'directory (%s) and try again.', paths.package)
163 return -2
150 164
151 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, LIBRARY_FILE_NAME + '.sha1') 165 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, LIBRARY_FILE_NAME + '.sha1')
152 old_lib_zip_sha1 = os.path.join(paths.package, LIBRARY_FILE_NAME + '.sha1') 166 old_lib_zip_sha1 = os.path.join(paths.package, LIBRARY_FILE_NAME + '.sha1')
153 167
154 logging.debug('Comparing library hashes: %s and %s', new_lib_zip_sha1, 168 logging.debug('Comparing library hashes: %s and %s', new_lib_zip_sha1,
155 old_lib_zip_sha1) 169 old_lib_zip_sha1)
156 if utils.FileEquals(new_lib_zip_sha1, old_lib_zip_sha1) and not args.force: 170 if utils.FileEquals(new_lib_zip_sha1, old_lib_zip_sha1) and not args.force:
157 logging.debug('The Google Play services library is up to date.') 171 logging.info('Skipping, the Google Play services library is up to date.')
158 return 0 172 return 0
159 173
160 config = utils.ConfigParser(args.config) 174 config = utils.ConfigParser(args.config)
161 bucket_path = _VerifyBucketPathFormat(args.bucket, 175 bucket_path = _VerifyBucketPathFormat(args.bucket,
162 config.version_number, 176 config.version_number,
163 args.dry_run) 177 args.dry_run)
164 178
165 tmp_root = tempfile.mkdtemp() 179 tmp_root = tempfile.mkdtemp()
166 try: 180 try:
167 if not os.environ.get('CHROME_HEADLESS'): 181 # setup the destination directory
168 if not os.path.isdir(paths.package): 182 if not os.path.isdir(paths.package):
169 os.makedirs(paths.package) 183 os.makedirs(paths.package)
170 184
171 # download license file from bucket/{version_number}/license.sha1 185 # download license file from bucket/{version_number}/license.sha1
172 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) 186 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME)
173 old_license = os.path.join(paths.package, LICENSE_FILE_NAME) 187 old_license = os.path.join(paths.package, LICENSE_FILE_NAME)
174 188
175 license_sha1 = os.path.join(SHA1_DIRECTORY, LICENSE_FILE_NAME + '.sha1') 189 license_sha1 = os.path.join(SHA1_DIRECTORY, LICENSE_FILE_NAME + '.sha1')
176 _DownloadFromBucket(bucket_path, license_sha1, new_license, 190 _DownloadFromBucket(bucket_path, license_sha1, new_license,
177 args.verbose, args.dry_run) 191 args.verbose, args.dry_run)
178 if not _CheckLicenseAgreement(new_license, old_license): 192
193 if (not _IsBotEnvironment() and
194 not _CheckLicenseAgreement(new_license, old_license,
195 config.version_number)):
179 logging.warning('Your version of the Google Play services library is ' 196 logging.warning('Your version of the Google Play services library is '
180 'not up to date. You might run into issues building ' 197 'not up to date. You might run into issues building '
181 'or running the app. Please run `%s download` to ' 198 'or running the app. Please run `%s download` to '
182 'retry downloading it.', __file__) 199 'retry downloading it.', __file__)
183 return 0 200 return 0
184 201
185 new_lib_zip = os.path.join(tmp_root, LIBRARY_FILE_NAME) 202 new_lib_zip = os.path.join(tmp_root, LIBRARY_FILE_NAME)
186 _DownloadFromBucket(bucket_path, new_lib_zip_sha1, new_lib_zip, 203 _DownloadFromBucket(bucket_path, new_lib_zip_sha1, new_lib_zip,
187 args.verbose, args.dry_run) 204 args.verbose, args.dry_run)
188 205
189 # We remove only the library itself. Users having a SDK manager installed 206 # We remove the library itself. Users having a SDK manager installed
190 # library before will keep the documentation and samples from it. 207 # library before will keep the documentation and samples from it.
191 shutil.rmtree(paths.lib, ignore_errors=True) 208 shutil.rmtree(paths.lib, ignore_errors=True)
192 os.makedirs(paths.lib) 209 os.makedirs(paths.lib)
jbudorick 2015/11/04 18:48:10 In light of the root ownership issue, we might wan
dgn 2015/11/05 12:15:10 Done. I also wrapped the calls below, as the licen
193 210
211 # We also remove source.properties so that the SDK manager doesn't fail
212 # recognizing that it didn't install the local version.
213 source_prop = os.path.join(paths.package, 'source.properties')
214 if os.path.isfile(source_prop):
215 os.remove(source_prop)
216
194 logging.debug('Extracting the library to %s', paths.lib) 217 logging.debug('Extracting the library to %s', paths.lib)
195 with zipfile.ZipFile(new_lib_zip, "r") as new_lib_zip_file: 218 with zipfile.ZipFile(new_lib_zip, "r") as new_lib_zip_file:
196 new_lib_zip_file.extractall(paths.lib) 219 new_lib_zip_file.extractall(paths.lib)
197 220
198 logging.debug('Copying %s to %s', new_license, old_license) 221 logging.debug('Copying %s to %s', new_license, old_license)
199 shutil.copy(new_license, old_license) 222 shutil.copy(new_license, old_license)
200 223
201 logging.debug('Copying %s to %s', new_lib_zip_sha1, old_lib_zip_sha1) 224 logging.debug('Copying %s to %s', new_lib_zip_sha1, old_lib_zip_sha1)
202 shutil.copy(new_lib_zip_sha1, old_lib_zip_sha1) 225 shutil.copy(new_lib_zip_sha1, old_lib_zip_sha1)
203 226
227 logging.info('Update complete.')
204 finally: 228 finally:
205 shutil.rmtree(tmp_root) 229 shutil.rmtree(tmp_root)
206 230
207 return 0 231 return 0
208 232
209 233
210 def UpdateSdk(args): 234 def UpdateSdk(args):
211 ''' 235 '''
212 Uses the Android SDK Manager to update or download the local Google Play 236 Uses the Android SDK Manager to update or download the local Google Play
213 services library. Its usual installation path is 237 services library. Its usual installation path is
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 398
375 match = LICENSE_PATTERN.search(prop_file_content) 399 match = LICENSE_PATTERN.search(prop_file_content)
376 if not match: 400 if not match:
377 raise AttributeError('The license was not found in ' + 401 raise AttributeError('The license was not found in ' +
378 os.path.abspath(prop_file_path)) 402 os.path.abspath(prop_file_path))
379 403
380 with open(license_path, 'w') as license_file: 404 with open(license_path, 'w') as license_file:
381 license_file.write(match.group('text')) 405 license_file.write(match.group('text'))
382 406
383 407
384 def _CheckLicenseAgreement(expected_license_path, actual_license_path): 408 def _CheckLicenseAgreement(expected_license_path, actual_license_path,
409 version_number):
385 ''' 410 '''
386 Checks that the new license is the one already accepted by the user. If it 411 Checks that the new license is the one already accepted by the user. If it
387 isn't, it prompts the user to accept it. Returns whether the expected license 412 isn't, it prompts the user to accept it. Returns whether the expected license
388 has been accepted. 413 has been accepted.
389 ''' 414 '''
390 415
391 if utils.FileEquals(expected_license_path, actual_license_path): 416 if utils.FileEquals(expected_license_path, actual_license_path):
392 return True 417 return True
393 418
394 with open(expected_license_path) as license_file: 419 with open(expected_license_path) as license_file:
420 # Uses plain print rather than logging to make sure this is not formatted
421 # by the logger.
422 print ('Updating the Google Play services SDK to '
423 'version %d.' % version_number)
424
395 # The output is buffered when running as part of gclient hooks. We split 425 # The output is buffered when running as part of gclient hooks. We split
396 # the text here and flush is explicitly to avoid having part of it dropped 426 # the text here and flush is explicitly to avoid having part of it dropped
397 # out. 427 # out.
398 # Note: text contains *escaped* new lines, so we split by '\\n', not '\n'. 428 # Note: text contains *escaped* new lines, so we split by '\\n', not '\n'.
399 for license_part in license_file.read().split('\\n'): 429 for license_part in license_file.read().split('\\n'):
400 # Uses plain print rather than logging to make sure this is not formatted
401 # by the logger.
402 print license_part 430 print license_part
403 sys.stdout.flush() 431 sys.stdout.flush()
404 432
405 # Need to put the prompt on a separate line otherwise the gclient hook buffer 433 # Need to put the prompt on a separate line otherwise the gclient hook buffer
406 # only prints it after we received an input. 434 # only prints it after we received an input.
407 print 'Do you accept the license? [y/n]: ' 435 print 'Do you accept the license? [y/n]: '
408 sys.stdout.flush() 436 sys.stdout.flush()
409 return raw_input('> ') in ('Y', 'y') 437 return raw_input('> ') in ('Y', 'y')
410 438
411 439
440 def _IsBotEnvironment():
441 return bool(os.environ.get('CHROME_HEADLESS'))
442
443
412 def _VerifyBucketPathFormat(bucket_name, version_number, is_dry_run): 444 def _VerifyBucketPathFormat(bucket_name, version_number, is_dry_run):
413 ''' 445 '''
414 Formats and checks the download/upload path depending on whether we are 446 Formats and checks the download/upload path depending on whether we are
415 running in dry run mode or not. Returns a supposedly safe path to use with 447 running in dry run mode or not. Returns a supposedly safe path to use with
416 Gsutil. 448 Gsutil.
417 ''' 449 '''
418 450
419 if is_dry_run: 451 if is_dry_run:
420 bucket_path = os.path.abspath(os.path.join(bucket_name, 452 bucket_path = os.path.abspath(os.path.join(bucket_name,
421 str(version_number))) 453 str(version_number)))
(...skipping 25 matching lines...) Expand all
447 def call(self, *args): 479 def call(self, *args):
448 logging.debug('Calling command "%s"', str(args)) 480 logging.debug('Calling command "%s"', str(args))
449 return cmd_helper.GetCmdStatusOutputAndError(args) 481 return cmd_helper.GetCmdStatusOutputAndError(args)
450 482
451 def check_call(self, *args): 483 def check_call(self, *args):
452 logging.debug('Calling command "%s"', str(args)) 484 logging.debug('Calling command "%s"', str(args))
453 return cmd_helper.GetCmdStatusOutputAndError(args) 485 return cmd_helper.GetCmdStatusOutputAndError(args)
454 486
455 487
456 if __name__ == '__main__': 488 if __name__ == '__main__':
457 sys.exit(Main()) 489 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « build/android/PRESUBMIT.py ('k') | build/android/play_services/update_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698