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 SDK to and |
jbudorick
2015/11/05 16:03:02
nit: Why the doc change from "library" to "SDK"? L
dgn
2015/11/05 16:15:05
The developer website says "Google Play services S
dgn
2015/11/05 16:30:21
Done.
| |
8 library to and from a Google Cloud storage. | 8 from a Google Cloud storage. |
9 ''' | 9 ''' |
10 | 10 |
11 import argparse | 11 import argparse |
12 import collections | |
13 import logging | 12 import logging |
14 import os | 13 import os |
15 import re | 14 import re |
16 import shutil | 15 import shutil |
17 import sys | 16 import sys |
18 import tempfile | 17 import tempfile |
19 import zipfile | 18 import zipfile |
20 | 19 |
21 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) | 20 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) |
22 from devil.utils import cmd_helper | 21 from devil.utils import cmd_helper |
23 from play_services import utils | 22 from play_services import utils |
24 from pylib import constants | 23 from pylib import constants |
25 from pylib.utils import logging_utils | 24 from pylib.utils import logging_utils |
26 | 25 |
27 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'build')) | 26 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'build')) |
28 import find_depot_tools # pylint: disable=import-error,unused-import | 27 import find_depot_tools # pylint: disable=import-error,unused-import |
29 import breakpad | 28 import breakpad |
30 import download_from_google_storage | 29 import download_from_google_storage |
31 import upload_to_google_storage | 30 import upload_to_google_storage |
32 | 31 |
33 | 32 |
34 # Directory where the SHA1 files for the zip and the license are stored | 33 # 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. | 34 # It should be managed by git to provided information about new versions. |
36 SHA1_DIRECTORY = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', | 35 SHA1_DIRECTORY = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', |
37 'play_services') | 36 'play_services') |
38 | 37 |
39 # Default bucket used for storing the files. | 38 # Default bucket used for storing the files. |
40 GMS_CLOUD_STORAGE = 'chrome-sdk-extras' | 39 GMS_CLOUD_STORAGE = 'chromium-android-tools/play-services' |
41 | 40 |
42 # Path to the default configuration file. It exposes the currently installed | 41 # Path to the default configuration file. It exposes the currently installed |
43 # version of the library in a human readable way. | 42 # version of the SDK in a human readable way. |
44 CONFIG_DEFAULT_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'build', | 43 CONFIG_DEFAULT_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'build', |
45 'android', 'play_services', 'config.json') | 44 'android', 'play_services', 'config.json') |
46 | 45 |
47 LICENSE_FILE_NAME = 'LICENSE' | 46 LICENSE_FILE_NAME = 'LICENSE' |
48 LIBRARY_FILE_NAME = 'google_play_services_library.zip' | 47 ZIP_FILE_NAME = 'google_play_services_library.zip' |
49 GMS_PACKAGE_ID = 'extra-google-google_play_services' # used by sdk manager | 48 GMS_PACKAGE_ID = 'extra-google-google_play_services' # used by sdk manager |
50 | 49 |
51 LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P<text>.*)$', re.MULTILINE) | 50 LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P<text>.*)$', re.MULTILINE) |
52 | 51 |
53 | 52 |
54 def Main(): | 53 def main(raw_args): |
55 parser = argparse.ArgumentParser( | 54 parser = argparse.ArgumentParser( |
56 description=__doc__ + 'Please see the subcommand help for more details.', | 55 description=__doc__ + 'Please see the subcommand help for more details.', |
57 formatter_class=utils.DefaultsRawHelpFormatter) | 56 formatter_class=utils.DefaultsRawHelpFormatter) |
58 subparsers = parser.add_subparsers(title='commands') | 57 subparsers = parser.add_subparsers(title='commands') |
59 | 58 |
60 # Download arguments | 59 # Download arguments |
61 parser_download = subparsers.add_parser( | 60 parser_download = subparsers.add_parser( |
62 'download', | 61 'download', |
63 help='download the library from the cloud storage', | 62 help='download the SDK from the cloud storage', |
64 description=Download.__doc__, | 63 description=Download.__doc__, |
65 formatter_class=utils.DefaultsRawHelpFormatter) | 64 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) | 65 parser_download.set_defaults(func=Download) |
71 AddCommonArguments(parser_download) | 66 AddBasicArguments(parser_download) |
67 AddBucketArguments(parser_download) | |
72 | 68 |
73 # SDK Update arguments | 69 # SDK Update arguments |
74 parser_sdk = subparsers.add_parser( | 70 parser_sdk = subparsers.add_parser( |
75 'sdk', | 71 'sdk', |
76 help='update the local sdk using the Android SDK Manager', | 72 help='update the SDK using the Android SDK Manager', |
77 description=UpdateSdk.__doc__, | 73 description=UpdateSdk.__doc__, |
78 formatter_class=utils.DefaultsRawHelpFormatter) | 74 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) | 75 parser_sdk.set_defaults(func=UpdateSdk) |
76 AddBasicArguments(parser_sdk) | |
87 | 77 |
88 # Upload arguments | 78 # Upload arguments |
89 parser_upload = subparsers.add_parser( | 79 parser_upload = subparsers.add_parser( |
90 'upload', | 80 'upload', |
91 help='upload the library to the cloud storage', | 81 help='upload the SDK to the cloud storage', |
92 description=Upload.__doc__, | 82 description=Upload.__doc__, |
93 formatter_class=utils.DefaultsRawHelpFormatter) | 83 formatter_class=utils.DefaultsRawHelpFormatter) |
94 parser_upload.add_argument('-f', '--force', | 84 |
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', | 85 parser_upload.add_argument('--skip-git', |
103 action='store_true', | 86 action='store_true', |
104 help="don't commit the changes at the end") | 87 help="don't commit the changes at the end") |
105 parser_upload.set_defaults(func=Upload) | 88 parser_upload.set_defaults(func=Upload) |
106 AddCommonArguments(parser_upload) | 89 AddBasicArguments(parser_upload) |
90 AddBucketArguments(parser_upload) | |
107 | 91 |
108 args = parser.parse_args() | 92 args = parser.parse_args(raw_args) |
109 if args.verbose: | 93 if args.verbose: |
110 logging.basicConfig(level=logging.DEBUG) | 94 logging.basicConfig(level=logging.DEBUG) |
111 logging_utils.ColorStreamHandler.MakeDefault() | 95 logging_utils.ColorStreamHandler.MakeDefault(not _IsBotEnvironment()) |
112 return args.func(args) | 96 return args.func(args) |
113 | 97 |
114 | 98 |
115 def AddCommonArguments(parser): | 99 def AddBasicArguments(parser): |
116 ''' | 100 ''' |
117 Defines the common arguments on subparser rather than the main one. This | 101 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` | 102 allows to put arguments after the command: `foo.py upload --debug --force` |
119 instead of `foo.py --debug upload --force` | 103 instead of `foo.py --debug upload --force` |
120 ''' | 104 ''' |
121 | 105 |
122 parser.add_argument('--bucket', | 106 parser.add_argument('--sdk-root', |
123 help='name of the bucket where the files are stored', | 107 help='base path to the Android SDK tools root', |
124 default=GMS_CLOUD_STORAGE) | 108 default=constants.ANDROID_SDK_ROOT) |
125 parser.add_argument('--config', | 109 |
126 help='JSON Configuration file', | |
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', | 110 parser.add_argument('-v', '--verbose', |
135 action='store_true', | 111 action='store_true', |
136 help='print debug information') | 112 help='print debug information') |
137 | 113 |
138 | 114 |
115 def AddBucketArguments(parser): | |
116 parser.add_argument('--bucket', | |
117 help='name of the bucket where the files are stored', | |
118 default=GMS_CLOUD_STORAGE) | |
119 | |
120 parser.add_argument('--config', | |
121 help='JSON Configuration file', | |
122 default=CONFIG_DEFAULT_PATH) | |
123 | |
124 parser.add_argument('--dry-run', | |
125 action='store_true', | |
126 help=('run the script in dry run mode. Files will be ' | |
127 'copied to a local directory instead of the ' | |
128 'cloud storage. The bucket name will be as path ' | |
129 'to that directory relative to the repository ' | |
130 'root.')) | |
131 | |
132 parser.add_argument('-f', '--force', | |
133 action='store_true', | |
134 help='run even if the SDK is already up to date') | |
135 | |
136 | |
139 def Download(args): | 137 def Download(args): |
140 ''' | 138 ''' |
141 Downloads the Google Play services client library from a Google Cloud Storage | 139 Downloads the Google Play services SDK from a Google Cloud Storage bucket and |
142 bucket and installs it to | 140 installs it to |
143 //third_party/android_tools/sdk/extras/google/google_play_services. | 141 //third_party/android_tools/sdk/extras/google/google_play_services. |
144 | 142 |
145 A license check will be made, and the user might have to accept the license | 143 A license check will be made, and the user might have to accept the license |
146 if that has not been done before. | 144 if that has not been done before. |
147 ''' | 145 ''' |
148 | 146 |
149 paths = _InitPaths(constants.ANDROID_SDK_ROOT) | 147 if not os.path.isdir(args.sdk_root): |
148 logging.debug('Did not find the Android SDK root directory at "%s".', | |
149 args.sdk_root) | |
150 if not args.force: | |
151 logging.info('Skipping, not on an android checkout.') | |
152 return 0 | |
150 | 153 |
151 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, LIBRARY_FILE_NAME + '.sha1') | 154 paths = PlayServicesPaths(args.sdk_root) |
152 old_lib_zip_sha1 = os.path.join(paths.package, LIBRARY_FILE_NAME + '.sha1') | |
153 | 155 |
154 logging.debug('Comparing library hashes: %s and %s', new_lib_zip_sha1, | 156 if os.path.isdir(paths.package) and not os.access(paths.package, os.W_OK): |
155 old_lib_zip_sha1) | 157 logging.error('Failed updating the Google Play Services SDK. ' |
156 if utils.FileEquals(new_lib_zip_sha1, old_lib_zip_sha1) and not args.force: | 158 'The location is not writable. Please remove the ' |
157 logging.debug('The Google Play services library is up to date.') | 159 'directory (%s) and try again.', paths.package) |
160 return -2 | |
161 | |
162 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, ZIP_FILE_NAME + '.sha1') | |
163 | |
164 logging.debug('Comparing zip hashes: %s and %s', new_lib_zip_sha1, | |
165 paths.lib_zip_sha1) | |
166 if utils.FileEquals(new_lib_zip_sha1, paths.lib_zip_sha1) and not args.force: | |
167 logging.info('Skipping, the Google Play services SDK is up to date.') | |
158 return 0 | 168 return 0 |
159 | 169 |
160 config = utils.ConfigParser(args.config) | 170 config = utils.ConfigParser(args.config) |
161 bucket_path = _VerifyBucketPathFormat(args.bucket, | 171 bucket_path = _VerifyBucketPathFormat(args.bucket, |
162 config.version_number, | 172 config.version_number, |
163 args.dry_run) | 173 args.dry_run) |
164 | 174 |
165 tmp_root = tempfile.mkdtemp() | 175 tmp_root = tempfile.mkdtemp() |
166 try: | 176 try: |
167 if not os.environ.get('CHROME_HEADLESS'): | 177 # setup the destination directory |
168 if not os.path.isdir(paths.package): | 178 if not os.path.isdir(paths.package): |
169 os.makedirs(paths.package) | 179 os.makedirs(paths.package) |
170 | 180 |
171 # download license file from bucket/{version_number}/license.sha1 | 181 # download license file from bucket/{version_number}/license.sha1 |
172 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) | 182 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) |
173 old_license = os.path.join(paths.package, LICENSE_FILE_NAME) | |
174 | 183 |
175 license_sha1 = os.path.join(SHA1_DIRECTORY, LICENSE_FILE_NAME + '.sha1') | 184 license_sha1 = os.path.join(SHA1_DIRECTORY, LICENSE_FILE_NAME + '.sha1') |
176 _DownloadFromBucket(bucket_path, license_sha1, new_license, | 185 _DownloadFromBucket(bucket_path, license_sha1, new_license, |
177 args.verbose, args.dry_run) | 186 args.verbose, args.dry_run) |
178 if not _CheckLicenseAgreement(new_license, old_license): | 187 |
179 logging.warning('Your version of the Google Play services library is ' | 188 if (not _IsBotEnvironment() and |
189 not _CheckLicenseAgreement(new_license, paths.license, | |
190 config.version_number)): | |
191 logging.warning('Your version of the Google Play services SDK is ' | |
180 'not up to date. You might run into issues building ' | 192 'not up to date. You might run into issues building ' |
181 'or running the app. Please run `%s download` to ' | 193 'or running the app. Please run `%s download` to ' |
182 'retry downloading it.', __file__) | 194 'retry downloading it.', __file__) |
183 return 0 | 195 return 0 |
184 | 196 |
185 new_lib_zip = os.path.join(tmp_root, LIBRARY_FILE_NAME) | 197 new_lib_zip = os.path.join(tmp_root, ZIP_FILE_NAME) |
186 _DownloadFromBucket(bucket_path, new_lib_zip_sha1, new_lib_zip, | 198 _DownloadFromBucket(bucket_path, new_lib_zip_sha1, new_lib_zip, |
187 args.verbose, args.dry_run) | 199 args.verbose, args.dry_run) |
188 | 200 |
189 # We remove only the library itself. Users having a SDK manager installed | 201 try: |
190 # library before will keep the documentation and samples from it. | 202 # We remove the current version of the Google Play services SDK. Users |
191 shutil.rmtree(paths.lib, ignore_errors=True) | 203 # having installed it with the Android SDK manager before will keep the |
192 os.makedirs(paths.lib) | 204 # documentation and samples from it. |
205 if os.path.exists(paths.lib): | |
206 shutil.rmtree(paths.lib) | |
207 os.makedirs(paths.lib) | |
193 | 208 |
194 logging.debug('Extracting the library to %s', paths.lib) | 209 # We also remove source.properties so that the SDK manager doesn't fail |
195 with zipfile.ZipFile(new_lib_zip, "r") as new_lib_zip_file: | 210 # recognizing that it didn't install the local version. |
196 new_lib_zip_file.extractall(paths.lib) | 211 if os.path.isfile(paths.source_prop): |
212 os.remove(paths.source_prop) | |
197 | 213 |
198 logging.debug('Copying %s to %s', new_license, old_license) | 214 logging.debug('Extracting the SDK to %s', paths.lib) |
199 shutil.copy(new_license, old_license) | 215 with zipfile.ZipFile(new_lib_zip, "r") as new_lib_zip_file: |
216 new_lib_zip_file.extractall(paths.lib) | |
200 | 217 |
201 logging.debug('Copying %s to %s', new_lib_zip_sha1, old_lib_zip_sha1) | 218 logging.debug('Copying %s to %s', new_license, paths.license) |
202 shutil.copy(new_lib_zip_sha1, old_lib_zip_sha1) | 219 shutil.copy(new_license, paths.license) |
203 | 220 |
221 logging.debug('Copying %s to %s', new_lib_zip_sha1, paths.lib_zip_sha1) | |
222 shutil.copy(new_lib_zip_sha1, paths.lib_zip_sha1) | |
223 | |
224 logging.info('Update complete.') | |
225 | |
226 except Exception as e: # pylint: disable=broad-except | |
227 logging.error('Failed updating the Google Play Services SDK. ' | |
228 'An error occurred while installing the new version in ' | |
229 'the SDK directory: %s ', e) | |
230 return -3 | |
204 finally: | 231 finally: |
205 shutil.rmtree(tmp_root) | 232 shutil.rmtree(tmp_root) |
206 | 233 |
207 return 0 | 234 return 0 |
208 | 235 |
209 | 236 |
210 def UpdateSdk(args): | 237 def UpdateSdk(args): |
211 ''' | 238 ''' |
212 Uses the Android SDK Manager to update or download the local Google Play | 239 Uses the Android SDK Manager to update or download the local Google Play |
213 services library. Its usual installation path is | 240 services SDK. Its usual installation path is |
214 //third_party/android_tools/sdk/extras/google/google_play_services | 241 //third_party/android_tools/sdk/extras/google/google_play_services |
215 ''' | 242 ''' |
216 | 243 |
217 # This should function should not run on bots and could fail for many user | 244 # This should function should not run on bots and could fail for many user |
218 # and setup related reasons. Also, exceptions here are not caught, so we | 245 # and setup related reasons. Also, exceptions here are not caught, so we |
219 # disable breakpad to avoid spamming the logs. | 246 # disable breakpad to avoid spamming the logs. |
220 breakpad.IS_ENABLED = False | 247 breakpad.IS_ENABLED = False |
221 | 248 |
222 sdk_manager = os.path.join(args.sdk_root, 'tools', 'android') | 249 sdk_manager = os.path.join(args.sdk_root, 'tools', 'android') |
223 cmd = [sdk_manager, 'update', 'sdk', '--no-ui', '--filter', GMS_PACKAGE_ID] | 250 cmd = [sdk_manager, 'update', 'sdk', '--no-ui', '--filter', GMS_PACKAGE_ID] |
224 cmd_helper.Call(cmd) | 251 cmd_helper.Call(cmd) |
225 # If no update is needed, it still returns successfully so we just do nothing | 252 # If no update is needed, it still returns successfully so we just do nothing |
226 | 253 |
227 return 0 | 254 return 0 |
228 | 255 |
229 | 256 |
230 def Upload(args): | 257 def Upload(args): |
231 ''' | 258 ''' |
232 Uploads the local Google Play services client library to a Google Cloud | 259 Uploads the library from the local Google Play services SDK to a Google Cloud |
233 storage bucket. | 260 storage bucket. |
234 | 261 |
235 By default, a local commit will be made at the end of the operation. | 262 By default, a local commit will be made at the end of the operation. |
236 ''' | 263 ''' |
237 | 264 |
238 # This should function should not run on bots and could fail for many user | 265 # This should function should not run on bots and could fail for many user |
239 # and setup related reasons. Also, exceptions here are not caught, so we | 266 # and setup related reasons. Also, exceptions here are not caught, so we |
240 # disable breakpad to avoid spamming the logs. | 267 # disable breakpad to avoid spamming the logs. |
241 breakpad.IS_ENABLED = False | 268 breakpad.IS_ENABLED = False |
242 | 269 |
243 paths = _InitPaths(args.sdk_root) | 270 paths = PlayServicesPaths(args.sdk_root) |
244 | 271 |
245 if not args.skip_git and utils.IsRepoDirty(constants.DIR_SOURCE_ROOT): | 272 if not args.skip_git and utils.IsRepoDirty(constants.DIR_SOURCE_ROOT): |
246 logging.error('The repo is dirty. Please commit or stash your changes.') | 273 logging.error('The repo is dirty. Please commit or stash your changes.') |
247 return -1 | 274 return -1 |
248 | 275 |
249 config = utils.ConfigParser(args.config) | 276 config = utils.ConfigParser(args.config) |
250 | 277 |
251 version_xml = os.path.join(paths.lib, 'res', 'values', 'version.xml') | 278 new_version_number = utils.GetVersionNumberFromLibraryResources( |
252 new_version_number = utils.GetVersionNumberFromLibraryResources(version_xml) | 279 paths.version_xml) |
253 logging.debug('comparing versions: new=%d, old=%s', | 280 logging.debug('comparing versions: new=%d, old=%s', |
254 new_version_number, config.version_number) | 281 new_version_number, config.version_number) |
255 if new_version_number <= config.version_number and not args.force: | 282 if new_version_number <= config.version_number and not args.force: |
256 logging.info('The checked in version of the library is already the latest ' | 283 logging.info('The checked in version of the SDK is already the latest ' |
257 'one. No update needed. Please rerun with --force to skip ' | 284 'one. No update needed. Please rerun with --force to skip ' |
258 'this check.') | 285 'this check.') |
259 return 0 | 286 return 0 |
260 | 287 |
261 tmp_root = tempfile.mkdtemp() | 288 tmp_root = tempfile.mkdtemp() |
262 try: | 289 try: |
263 new_lib_zip = os.path.join(tmp_root, LIBRARY_FILE_NAME) | 290 new_lib_zip = os.path.join(tmp_root, ZIP_FILE_NAME) |
264 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) | 291 new_license = os.path.join(tmp_root, LICENSE_FILE_NAME) |
265 | 292 |
266 # need to strip '.zip' from the file name here | 293 # need to strip '.zip' from the file name here |
267 shutil.make_archive(new_lib_zip[:-4], 'zip', paths.lib) | 294 shutil.make_archive(new_lib_zip[:-4], 'zip', paths.lib) |
268 _ExtractLicenseFile(new_license, paths.package) | 295 _ExtractLicenseFile(new_license, paths.source_prop) |
269 | 296 |
270 bucket_path = _VerifyBucketPathFormat(args.bucket, new_version_number, | 297 bucket_path = _VerifyBucketPathFormat(args.bucket, new_version_number, |
271 args.dry_run) | 298 args.dry_run) |
272 files_to_upload = [new_lib_zip, new_license] | 299 files_to_upload = [new_lib_zip, new_license] |
273 logging.debug('Uploading %s to %s', files_to_upload, bucket_path) | 300 logging.debug('Uploading %s to %s', files_to_upload, bucket_path) |
274 _UploadToBucket(bucket_path, files_to_upload, args.dry_run) | 301 _UploadToBucket(bucket_path, files_to_upload, args.dry_run) |
275 | 302 |
276 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, | 303 new_lib_zip_sha1 = os.path.join(SHA1_DIRECTORY, |
277 LIBRARY_FILE_NAME + '.sha1') | 304 ZIP_FILE_NAME + '.sha1') |
278 new_license_sha1 = os.path.join(SHA1_DIRECTORY, | 305 new_license_sha1 = os.path.join(SHA1_DIRECTORY, |
279 LICENSE_FILE_NAME + '.sha1') | 306 LICENSE_FILE_NAME + '.sha1') |
280 shutil.copy(new_lib_zip + '.sha1', new_lib_zip_sha1) | 307 shutil.copy(new_lib_zip + '.sha1', new_lib_zip_sha1) |
281 shutil.copy(new_license + '.sha1', new_license_sha1) | 308 shutil.copy(new_license + '.sha1', new_license_sha1) |
282 finally: | 309 finally: |
283 shutil.rmtree(tmp_root) | 310 shutil.rmtree(tmp_root) |
284 | 311 |
285 config.UpdateVersionNumber(new_version_number) | 312 config.UpdateVersionNumber(new_version_number) |
286 | 313 |
287 if not args.skip_git: | 314 if not args.skip_git: |
288 commit_message = ('Update the Google Play services dependency to %s\n' | 315 commit_message = ('Update the Google Play services dependency to %s\n' |
289 '\n') % new_version_number | 316 '\n') % new_version_number |
290 utils.MakeLocalCommit(constants.DIR_SOURCE_ROOT, | 317 utils.MakeLocalCommit(constants.DIR_SOURCE_ROOT, |
291 [new_lib_zip_sha1, new_license_sha1, config.path], | 318 [new_lib_zip_sha1, new_license_sha1, config.path], |
292 commit_message) | 319 commit_message) |
293 | 320 |
294 return 0 | 321 return 0 |
295 | 322 |
296 | 323 |
297 def _InitPaths(sdk_root): | |
298 ''' | |
299 Initializes the different paths to be used in the update process. | |
300 ''' | |
301 | |
302 PlayServicesPaths = collections.namedtuple('PlayServicesPaths', [ | |
303 # Android SDK root path | |
304 'sdk_root', | |
305 | |
306 # Path to the Google Play services package in the SDK manager sense, | |
307 # where it installs the source.properties file | |
308 'package', | |
309 | |
310 # Path to the Google Play services library itself (jar and res) | |
311 'lib', | |
312 ]) | |
313 | |
314 sdk_play_services_package_dir = os.path.join('extras', 'google', | |
315 'google_play_services') | |
316 sdk_play_services_lib_dir = os.path.join(sdk_play_services_package_dir, | |
317 'libproject', | |
318 'google-play-services_lib') | |
319 | |
320 return PlayServicesPaths( | |
321 sdk_root=sdk_root, | |
322 package=os.path.join(sdk_root, sdk_play_services_package_dir), | |
323 lib=os.path.join(sdk_root, sdk_play_services_lib_dir)) | |
324 | |
325 | |
326 def _DownloadFromBucket(bucket_path, sha1_file, destination, verbose, | 324 def _DownloadFromBucket(bucket_path, sha1_file, destination, verbose, |
327 is_dry_run): | 325 is_dry_run): |
328 '''Downloads the file designated by the provided sha1 from a cloud bucket.''' | 326 '''Downloads the file designated by the provided sha1 from a cloud bucket.''' |
329 | 327 |
330 download_from_google_storage.download_from_google_storage( | 328 download_from_google_storage.download_from_google_storage( |
331 input_filename=sha1_file, | 329 input_filename=sha1_file, |
332 base_url=bucket_path, | 330 base_url=bucket_path, |
333 gsutil=_InitGsutil(is_dry_run), | 331 gsutil=_InitGsutil(is_dry_run), |
334 num_threads=1, | 332 num_threads=1, |
335 directory=None, | 333 directory=None, |
(...skipping 24 matching lines...) Expand all Loading... | |
360 def _InitGsutil(is_dry_run): | 358 def _InitGsutil(is_dry_run): |
361 '''Initialize the Gsutil object as regular or dummy version for dry runs. ''' | 359 '''Initialize the Gsutil object as regular or dummy version for dry runs. ''' |
362 | 360 |
363 if is_dry_run: | 361 if is_dry_run: |
364 return DummyGsutil() | 362 return DummyGsutil() |
365 else: | 363 else: |
366 return download_from_google_storage.Gsutil( | 364 return download_from_google_storage.Gsutil( |
367 download_from_google_storage.GSUTIL_DEFAULT_PATH) | 365 download_from_google_storage.GSUTIL_DEFAULT_PATH) |
368 | 366 |
369 | 367 |
370 def _ExtractLicenseFile(license_path, play_services_package_dir): | 368 def _ExtractLicenseFile(license_path, prop_file_path): |
371 prop_file_path = os.path.join(play_services_package_dir, 'source.properties') | |
372 with open(prop_file_path, 'r') as prop_file: | 369 with open(prop_file_path, 'r') as prop_file: |
373 prop_file_content = prop_file.read() | 370 prop_file_content = prop_file.read() |
374 | 371 |
375 match = LICENSE_PATTERN.search(prop_file_content) | 372 match = LICENSE_PATTERN.search(prop_file_content) |
376 if not match: | 373 if not match: |
377 raise AttributeError('The license was not found in ' + | 374 raise AttributeError('The license was not found in ' + |
378 os.path.abspath(prop_file_path)) | 375 os.path.abspath(prop_file_path)) |
379 | 376 |
380 with open(license_path, 'w') as license_file: | 377 with open(license_path, 'w') as license_file: |
381 license_file.write(match.group('text')) | 378 license_file.write(match.group('text')) |
382 | 379 |
383 | 380 |
384 def _CheckLicenseAgreement(expected_license_path, actual_license_path): | 381 def _CheckLicenseAgreement(expected_license_path, actual_license_path, |
382 version_number): | |
385 ''' | 383 ''' |
386 Checks that the new license is the one already accepted by the user. If it | 384 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 | 385 isn't, it prompts the user to accept it. Returns whether the expected license |
388 has been accepted. | 386 has been accepted. |
389 ''' | 387 ''' |
390 | 388 |
391 if utils.FileEquals(expected_license_path, actual_license_path): | 389 if utils.FileEquals(expected_license_path, actual_license_path): |
392 return True | 390 return True |
393 | 391 |
394 with open(expected_license_path) as license_file: | 392 with open(expected_license_path) as license_file: |
393 # Uses plain print rather than logging to make sure this is not formatted | |
394 # by the logger. | |
395 print ('Updating the Google Play services SDK to ' | |
396 'version %d.' % version_number) | |
397 | |
395 # The output is buffered when running as part of gclient hooks. We split | 398 # 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 | 399 # the text here and flush is explicitly to avoid having part of it dropped |
397 # out. | 400 # out. |
398 # Note: text contains *escaped* new lines, so we split by '\\n', not '\n'. | 401 # Note: text contains *escaped* new lines, so we split by '\\n', not '\n'. |
399 for license_part in license_file.read().split('\\n'): | 402 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 | 403 print license_part |
403 sys.stdout.flush() | 404 sys.stdout.flush() |
404 | 405 |
405 # Need to put the prompt on a separate line otherwise the gclient hook buffer | 406 # Need to put the prompt on a separate line otherwise the gclient hook buffer |
406 # only prints it after we received an input. | 407 # only prints it after we received an input. |
407 print 'Do you accept the license? [y/n]: ' | 408 print 'Do you accept the license? [y/n]: ' |
408 sys.stdout.flush() | 409 sys.stdout.flush() |
409 return raw_input('> ') in ('Y', 'y') | 410 return raw_input('> ') in ('Y', 'y') |
410 | 411 |
411 | 412 |
413 def _IsBotEnvironment(): | |
414 return bool(os.environ.get('CHROME_HEADLESS')) | |
415 | |
416 | |
412 def _VerifyBucketPathFormat(bucket_name, version_number, is_dry_run): | 417 def _VerifyBucketPathFormat(bucket_name, version_number, is_dry_run): |
413 ''' | 418 ''' |
414 Formats and checks the download/upload path depending on whether we are | 419 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 | 420 running in dry run mode or not. Returns a supposedly safe path to use with |
416 Gsutil. | 421 Gsutil. |
417 ''' | 422 ''' |
418 | 423 |
419 if is_dry_run: | 424 if is_dry_run: |
420 bucket_path = os.path.abspath(os.path.join(bucket_name, | 425 bucket_path = os.path.abspath(os.path.join(bucket_name, |
421 str(version_number))) | 426 str(version_number))) |
422 if not os.path.isdir(bucket_path): | 427 if not os.path.isdir(bucket_path): |
423 os.makedirs(bucket_path) | 428 os.makedirs(bucket_path) |
424 else: | 429 else: |
425 if bucket_name.startswith('gs://'): | 430 if bucket_name.startswith('gs://'): |
426 # We enforce the syntax without gs:// for consistency with the standalone | 431 # We enforce the syntax without gs:// for consistency with the standalone |
427 # download/upload scripts and to make dry run transition easier. | 432 # download/upload scripts and to make dry run transition easier. |
428 raise AttributeError('Please provide the bucket name without the gs:// ' | 433 raise AttributeError('Please provide the bucket name without the gs:// ' |
429 'prefix (e.g. %s)' % GMS_CLOUD_STORAGE) | 434 'prefix (e.g. %s)' % GMS_CLOUD_STORAGE) |
430 bucket_path = 'gs://%s/%d' % (bucket_name, version_number) | 435 bucket_path = 'gs://%s/%d' % (bucket_name, version_number) |
431 | 436 |
432 return bucket_path | 437 return bucket_path |
433 | 438 |
434 | 439 |
440 class PlayServicesPaths(object): | |
441 ''' | |
442 Describes the different paths to be used in the update process. | |
443 | |
444 Filesystem hierarchy | Exposed property / notes | |
445 ---------------------------------------------------|------------------------- | |
446 [sdk_root] | sdk_root / (1) | |
447 +- extras | | |
448 +- google | | |
449 +- google_play_services | package / (2) | |
450 +- source.properties | source_prop / (3) | |
451 +- LICENSE | license / (4) | |
452 +- google_play_services_library.zip.sha1 | lib_zip_sha1 / (5) | |
453 +- libproject | | |
454 +- google-play-services_lib | lib / (6) | |
455 +- res | | |
456 +- values | | |
457 +- version.xml | version_xml (7) | |
458 | |
459 Notes: | |
460 | |
461 1. sdk_root: Path provided as a parameter to the script (--sdk_root) | |
462 2. package: This directory contains the Google Play services SDK itself. | |
463 When downloaded via the Android SDK manager, it will contain, | |
464 documentation, samples and other files in addition to the library. | |
465 3. source_prop: File created by the Android SDK manager that contains | |
466 the package information, such as the version info and the license. When | |
467 the update script downloads the SDK from our cloud storage, it is | |
468 removed. | |
469 4. license: File created by the update script. Contains the license accepted | |
470 by the user. | |
471 5. lib_zip_sha1: sha1 of the SDK zip that has been installed by the updated | |
472 script. It is compared with the one required by the config file to check | |
473 if an update is necessary. | |
474 6. lib: Contains the library itself: jar and resources. This is what is | |
475 downloaded from the cloud storage. | |
476 7. version_xml: File that contains the exact Google Play services library | |
477 version, the one that we track. The version looks like 811500, is used in | |
478 the code and the on-device APK, as opposed to the SDK package version | |
479 which looks like 27.0.0 and is used only by the Android SDK manager. | |
480 | |
481 ''' | |
482 | |
483 def __init__(self, sdk_root): | |
484 relative_package = os.path.join('extras', 'google', 'google_play_services') | |
485 relative_lib = os.path.join(relative_package, 'libproject', | |
486 'google-play-services_lib') | |
487 self.sdk_root = sdk_root | |
488 | |
489 self.package = os.path.join(sdk_root, relative_package) | |
490 self.lib_zip_sha1 = os.path.join(self.package, ZIP_FILE_NAME + '.sha1') | |
491 self.license = os.path.join(self.package, LICENSE_FILE_NAME) | |
492 self.source_prop = os.path.join(self.package, 'source.properties') | |
493 | |
494 self.lib = os.path.join(sdk_root, relative_lib) | |
495 self.version_xml = os.path.join(self.lib, 'res', 'values', 'version.xml') | |
496 | |
497 | |
435 class DummyGsutil(download_from_google_storage.Gsutil): | 498 class DummyGsutil(download_from_google_storage.Gsutil): |
436 ''' | 499 ''' |
437 Class that replaces Gsutil to use a local directory instead of an online | 500 Class that replaces Gsutil to use a local directory instead of an online |
438 bucket. It relies on the fact that Gsutil commands are very similar to shell | 501 bucket. It relies on the fact that Gsutil commands are very similar to shell |
439 ones, so for the ones used here (ls, cp), it works to just use them with a | 502 ones, so for the ones used here (ls, cp), it works to just use them with a |
440 local directory. | 503 local directory. |
441 ''' | 504 ''' |
442 | 505 |
443 def __init__(self): | 506 def __init__(self): |
444 super(DummyGsutil, self).__init__( | 507 super(DummyGsutil, self).__init__( |
445 download_from_google_storage.GSUTIL_DEFAULT_PATH) | 508 download_from_google_storage.GSUTIL_DEFAULT_PATH) |
446 | 509 |
447 def call(self, *args): | 510 def call(self, *args): |
448 logging.debug('Calling command "%s"', str(args)) | 511 logging.debug('Calling command "%s"', str(args)) |
449 return cmd_helper.GetCmdStatusOutputAndError(args) | 512 return cmd_helper.GetCmdStatusOutputAndError(args) |
450 | 513 |
451 def check_call(self, *args): | 514 def check_call(self, *args): |
452 logging.debug('Calling command "%s"', str(args)) | 515 logging.debug('Calling command "%s"', str(args)) |
453 return cmd_helper.GetCmdStatusOutputAndError(args) | 516 return cmd_helper.GetCmdStatusOutputAndError(args) |
454 | 517 |
455 | 518 |
456 if __name__ == '__main__': | 519 if __name__ == '__main__': |
457 sys.exit(Main()) | 520 sys.exit(main(sys.argv[1:])) |
OLD | NEW |