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 |