Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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:])) |
| OLD | NEW |