blob: 175451e747aab23cc89974a40b5d26a892a1e141 [file] [log] [blame]
Colin Cross72119102019-05-20 13:14:18 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""A tool for checking that a manifest agrees with the build system."""
18
Colin Cross72119102019-05-20 13:14:18 -070019import argparse
Ulya Trafimovich3c902e72021-03-04 18:06:27 +000020import json
Ulya Trafimovich0aba2522021-03-03 16:38:37 +000021import re
22import subprocess
Colin Cross72119102019-05-20 13:14:18 -070023import sys
24from xml.dom import minidom
25
Colin Crosse1ab8492024-09-11 13:28:16 -070026from manifest import *
Colin Cross72119102019-05-20 13:14:18 -070027
28
29class ManifestMismatchError(Exception):
Spandan Dasf8807422021-08-25 20:01:17 +000030 pass
Colin Cross72119102019-05-20 13:14:18 -070031
32
33def parse_args():
Spandan Dasf8807422021-08-25 20:01:17 +000034 """Parse commandline arguments."""
Colin Cross72119102019-05-20 13:14:18 -070035
Spandan Dasf8807422021-08-25 20:01:17 +000036 parser = argparse.ArgumentParser()
37 parser.add_argument(
38 '--uses-library',
39 dest='uses_libraries',
40 action='append',
41 help='specify uses-library entries known to the build system')
42 parser.add_argument(
43 '--optional-uses-library',
44 dest='optional_uses_libraries',
45 action='append',
46 help='specify uses-library entries known to the build system with '
47 'required:false'
48 )
49 parser.add_argument(
Jiakai Zhangf98da192024-04-15 11:15:41 +000050 '--missing-optional-uses-library',
51 dest='missing_optional_uses_libraries',
52 action='append',
53 help='specify uses-library entries missing from the build system with '
54 'required:false',
55 default=[]
56 )
57 parser.add_argument(
Spandan Dasf8807422021-08-25 20:01:17 +000058 '--enforce-uses-libraries',
59 dest='enforce_uses_libraries',
60 action='store_true',
61 help='check the uses-library entries known to the build system against '
62 'the manifest'
63 )
64 parser.add_argument(
65 '--enforce-uses-libraries-relax',
66 dest='enforce_uses_libraries_relax',
67 action='store_true',
68 help='do not fail immediately, just save the error message to file')
69 parser.add_argument(
70 '--enforce-uses-libraries-status',
71 dest='enforce_uses_libraries_status',
72 help='output file to store check status (error message)')
73 parser.add_argument(
74 '--extract-target-sdk-version',
75 dest='extract_target_sdk_version',
76 action='store_true',
77 help='print the targetSdkVersion from the manifest')
78 parser.add_argument(
79 '--dexpreopt-config',
Ulya Trofimovichc68b2892022-06-13 09:04:49 +000080 dest='dexpreopt_configs',
Spandan Dasf8807422021-08-25 20:01:17 +000081 action='append',
Ulya Trofimovichc68b2892022-06-13 09:04:49 +000082 help='a paths to a dexpreopt.config of some library')
Spandan Dasf8807422021-08-25 20:01:17 +000083 parser.add_argument('--aapt', dest='aapt', help='path to aapt executable')
84 parser.add_argument(
85 '--output', '-o', dest='output', help='output AndroidManifest.xml file')
86 parser.add_argument('input', help='input AndroidManifest.xml file')
87 return parser.parse_args()
Colin Cross72119102019-05-20 13:14:18 -070088
89
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +000090C_RED = "\033[1;31m"
91C_GREEN = "\033[1;32m"
92C_BLUE = "\033[1;34m"
93C_OFF = "\033[0m"
94C_BOLD = "\033[1m"
95
96
Jiakai Zhangf98da192024-04-15 11:15:41 +000097def enforce_uses_libraries(manifest, required, optional, missing_optional, relax, is_apk, path):
Spandan Dasf8807422021-08-25 20:01:17 +000098 """Verify that the <uses-library> tags in the manifest match those provided
99
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000100 by the build system.
Colin Cross72119102019-05-20 13:14:18 -0700101
102 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000103 manifest: manifest (either parsed XML or aapt dump of APK)
104 required: required libs known to the build system
105 optional: optional libs known to the build system
106 relax: if true, suppress error on mismatch and just write it to file
107 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +0000108 """
109 if is_apk:
110 manifest_required, manifest_optional, tags = extract_uses_libs_apk(
111 manifest)
112 else:
113 manifest_required, manifest_optional, tags = extract_uses_libs_xml(
114 manifest)
Colin Cross72119102019-05-20 13:14:18 -0700115
Spandan Dasf8807422021-08-25 20:01:17 +0000116 # Trim namespace component. Normally Soong does that automatically when it
117 # handles module names specified in Android.bp properties. However not all
118 # <uses-library> entries in the manifest correspond to real modules: some of
119 # the optional libraries may be missing at build time. Therefor this script
Colin Cross6cb462b2024-09-11 11:34:35 -0700120 # accepts raw module names as spelled in Android.bp/Android.mk and trims the
Spandan Dasf8807422021-08-25 20:01:17 +0000121 # optional namespace part manually.
122 required = trim_namespace_parts(required)
123 optional = trim_namespace_parts(optional)
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100124
Jiakai Zhangf98da192024-04-15 11:15:41 +0000125 existing_manifest_optional = [
126 lib for lib in manifest_optional if lib not in missing_optional]
127
128 # The order of the existing libraries matter, while the order of the missing
129 # ones doesn't.
130 if manifest_required == required and existing_manifest_optional == optional:
Spandan Dasf8807422021-08-25 20:01:17 +0000131 return None
Colin Cross72119102019-05-20 13:14:18 -0700132
Spandan Dasf8807422021-08-25 20:01:17 +0000133 #pylint: disable=line-too-long
134 errmsg = ''.join([
135 'mismatch in the <uses-library> tags between the build system and the '
136 'manifest:\n',
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000137 '\t- required libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(required), C_OFF),
138 '\t vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_required), C_OFF),
139 '\t- optional libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(optional), C_OFF),
Jiakai Zhangf98da192024-04-15 11:15:41 +0000140 '\t and missing ones in build system: %s[%s]%s\n' % (C_RED, ', '.join(missing_optional), C_OFF),
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000141 '\t vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_optional), C_OFF),
Spandan Dasf8807422021-08-25 20:01:17 +0000142 '\t- tags in the manifest (%s):\n' % path,
143 '\t\t%s\n' % '\t\t'.join(tags),
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000144 '%snote:%s the following options are available:\n' % (C_BLUE, C_OFF),
Spandan Dasf8807422021-08-25 20:01:17 +0000145 '\t- to temporarily disable the check on command line, rebuild with ',
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000146 '%sRELAX_USES_LIBRARY_CHECK=true%s' % (C_BOLD, C_OFF),
147 ' (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)\n',
Spandan Dasf8807422021-08-25 20:01:17 +0000148 '\t- to temporarily disable the check for the whole product, set ',
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000149 '%sPRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true%s in the product makefiles\n' % (C_BOLD, C_OFF),
150 '\t- to fix the check, make build system properties coherent with the manifest\n',
151 '\t- for details, see %sbuild/make/Changes.md%s' % (C_GREEN, C_OFF),
152 ' and %shttps://source.android.com/devices/tech/dalvik/art-class-loader-context%s\n' % (C_GREEN, C_OFF)
Spandan Dasf8807422021-08-25 20:01:17 +0000153 ])
154 #pylint: enable=line-too-long
Colin Cross72119102019-05-20 13:14:18 -0700155
Spandan Dasf8807422021-08-25 20:01:17 +0000156 if not relax:
157 raise ManifestMismatchError(errmsg)
Colin Cross72119102019-05-20 13:14:18 -0700158
Spandan Dasf8807422021-08-25 20:01:17 +0000159 return errmsg
Colin Cross72119102019-05-20 13:14:18 -0700160
Colin Cross72119102019-05-20 13:14:18 -0700161
Spandan Dasf8807422021-08-25 20:01:17 +0000162MODULE_NAMESPACE = re.compile('^//[^:]+:')
163
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100164
165def trim_namespace_parts(modules):
Spandan Dasf8807422021-08-25 20:01:17 +0000166 """Trim the namespace part of each module, if present.
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100167
Spandan Dasf8807422021-08-25 20:01:17 +0000168 Leave only the name.
169 """
170
171 trimmed = []
172 for module in modules:
173 trimmed.append(MODULE_NAMESPACE.sub('', module))
174 return trimmed
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100175
176
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000177def extract_uses_libs_apk(badging):
Spandan Dasf8807422021-08-25 20:01:17 +0000178 """Extract <uses-library> tags from the manifest of an APK."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000179
Spandan Dasf8807422021-08-25 20:01:17 +0000180 pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000181
Spandan Dasf8807422021-08-25 20:01:17 +0000182 required = []
183 optional = []
184 lines = []
185 for match in re.finditer(pattern, badging):
186 lines.append(match.group(0))
187 libname = match.group(2)
188 if match.group(1) is None:
189 required.append(libname)
190 else:
191 optional.append(libname)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000192
Spandan Dasf8807422021-08-25 20:01:17 +0000193 required = first_unique_elements(required)
194 optional = first_unique_elements(optional)
195 tags = first_unique_elements(lines)
196 return required, optional, tags
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000197
198
Colin Crossc00fa152023-10-06 13:10:52 -0700199def extract_uses_libs_xml(xml):
Spandan Dasf8807422021-08-25 20:01:17 +0000200 """Extract <uses-library> tags from the manifest."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000201
Spandan Dasf8807422021-08-25 20:01:17 +0000202 manifest = parse_manifest(xml)
Colin Crosse1ab8492024-09-11 13:28:16 -0700203 libs = [child
204 for application in get_or_create_applications(xml, manifest)
205 for child in get_children_with_tag(application, 'uses-library')]
Colin Cross72119102019-05-20 13:14:18 -0700206
Spandan Dasf8807422021-08-25 20:01:17 +0000207 required = [uses_library_name(x) for x in libs if uses_library_required(x)]
208 optional = [
209 uses_library_name(x) for x in libs if not uses_library_required(x)
210 ]
Colin Cross72119102019-05-20 13:14:18 -0700211
Spandan Dasf8807422021-08-25 20:01:17 +0000212 # render <uses-library> tags as XML for a pretty error message
213 tags = []
214 for lib in libs:
215 tags.append(lib.toprettyxml())
Ulya Trafimovichbb7513d2021-03-30 17:15:16 +0100216
Spandan Dasf8807422021-08-25 20:01:17 +0000217 required = first_unique_elements(required)
218 optional = first_unique_elements(optional)
219 tags = first_unique_elements(tags)
220 return required, optional, tags
Colin Cross72119102019-05-20 13:14:18 -0700221
222
223def first_unique_elements(l):
Spandan Dasf8807422021-08-25 20:01:17 +0000224 result = []
225 for x in l:
226 if x not in result:
227 result.append(x)
228 return result
Colin Cross72119102019-05-20 13:14:18 -0700229
230
231def uses_library_name(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000232 """Extract the name attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700233
234 Args:
235 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000236 """
237 name = lib.getAttributeNodeNS(android_ns, 'name')
238 return name.value if name is not None else ''
Colin Cross72119102019-05-20 13:14:18 -0700239
240
241def uses_library_required(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000242 """Extract the required attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700243
244 Args:
245 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000246 """
247 required = lib.getAttributeNodeNS(android_ns, 'required')
248 return (required.value == 'true') if required is not None else True
Colin Cross72119102019-05-20 13:14:18 -0700249
250
Spandan Dasf8807422021-08-25 20:01:17 +0000251def extract_target_sdk_version(manifest, is_apk=False):
252 """Returns the targetSdkVersion from the manifest.
Colin Cross72119102019-05-20 13:14:18 -0700253
254 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000255 manifest: manifest (either parsed XML or aapt dump of APK)
256 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +0000257 """
Colin Cross6cb462b2024-09-11 11:34:35 -0700258 if is_apk: #pylint: disable=no-else-return
Spandan Dasf8807422021-08-25 20:01:17 +0000259 return extract_target_sdk_version_apk(manifest)
260 else:
261 return extract_target_sdk_version_xml(manifest)
Colin Cross72119102019-05-20 13:14:18 -0700262
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000263
264def extract_target_sdk_version_apk(badging):
Spandan Dasf8807422021-08-25 20:01:17 +0000265 """Extract targetSdkVersion tags from the manifest of an APK."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000266
Spandan Dasf8807422021-08-25 20:01:17 +0000267 pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000268
Spandan Dasf8807422021-08-25 20:01:17 +0000269 for match in re.finditer(pattern, badging):
270 return match.group(1)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000271
Spandan Dasf8807422021-08-25 20:01:17 +0000272 raise RuntimeError('cannot find targetSdkVersion in the manifest')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000273
274
275def extract_target_sdk_version_xml(xml):
Spandan Dasf8807422021-08-25 20:01:17 +0000276 """Extract targetSdkVersion tags from the manifest."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000277
Spandan Dasf8807422021-08-25 20:01:17 +0000278 manifest = parse_manifest(xml)
Colin Cross72119102019-05-20 13:14:18 -0700279
Spandan Dasf8807422021-08-25 20:01:17 +0000280 # Get or insert the uses-sdk element
281 uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
282 if len(uses_sdk) > 1: #pylint: disable=no-else-raise
283 raise RuntimeError('found multiple uses-sdk elements')
284 elif len(uses_sdk) == 0:
285 raise RuntimeError('missing uses-sdk element')
Colin Cross72119102019-05-20 13:14:18 -0700286
Spandan Dasf8807422021-08-25 20:01:17 +0000287 uses_sdk = uses_sdk[0]
Colin Cross72119102019-05-20 13:14:18 -0700288
Spandan Dasf8807422021-08-25 20:01:17 +0000289 min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
290 if min_attr is None:
291 raise RuntimeError('minSdkVersion is not specified')
Colin Cross72119102019-05-20 13:14:18 -0700292
Spandan Dasf8807422021-08-25 20:01:17 +0000293 target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
294 if target_attr is None:
295 target_attr = min_attr
Colin Cross72119102019-05-20 13:14:18 -0700296
Spandan Dasf8807422021-08-25 20:01:17 +0000297 return target_attr.value
Colin Cross72119102019-05-20 13:14:18 -0700298
299
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000300def load_dexpreopt_configs(configs):
Spandan Dasf8807422021-08-25 20:01:17 +0000301 """Load dexpreopt.config files and map module names to library names."""
302 module_to_libname = {}
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000303
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000304 if configs is None:
305 configs = []
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000306
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000307 for config in configs:
308 with open(config, 'r') as f:
Spandan Dasf8807422021-08-25 20:01:17 +0000309 contents = json.load(f)
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000310 module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000311
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000312 return module_to_libname
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000313
314
315def translate_libnames(modules, module_to_libname):
Spandan Dasf8807422021-08-25 20:01:17 +0000316 """Translate module names into library names using the mapping."""
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000317 if modules is None:
318 modules = []
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000319
Spandan Dasf8807422021-08-25 20:01:17 +0000320 libnames = []
321 for name in modules:
322 if name in module_to_libname:
323 name = module_to_libname[name]
324 libnames.append(name)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000325
Spandan Dasf8807422021-08-25 20:01:17 +0000326 return libnames
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000327
328
Colin Cross72119102019-05-20 13:14:18 -0700329def main():
Spandan Dasf8807422021-08-25 20:01:17 +0000330 """Program entry point."""
331 try:
332 args = parse_args()
Colin Cross72119102019-05-20 13:14:18 -0700333
Spandan Dasf8807422021-08-25 20:01:17 +0000334 # The input can be either an XML manifest or an APK, they are parsed and
335 # processed in different ways.
336 is_apk = args.input.endswith('.apk')
337 if is_apk:
338 aapt = args.aapt if args.aapt is not None else 'aapt'
339 manifest = subprocess.check_output(
Cole Faustc41dd722021-11-09 15:08:26 -0800340 [aapt, 'dump', 'badging', args.input]).decode('utf-8')
Spandan Dasf8807422021-08-25 20:01:17 +0000341 else:
342 manifest = minidom.parse(args.input)
Colin Cross72119102019-05-20 13:14:18 -0700343
Spandan Dasf8807422021-08-25 20:01:17 +0000344 if args.enforce_uses_libraries:
345 # Load dexpreopt.config files and build a mapping from module
Jiakai Zhangf98da192024-04-15 11:15:41 +0000346 # names to library names. This is for Make only and it's necessary
347 # because Make passes module names from `LOCAL_USES_LIBRARIES`,
348 # `LOCAL_OPTIONAL_LIBRARY_NAMES`, while the manifest addresses
349 # libraries by their name. Soong doesn't use it and doesn't need it
350 # because it converts the module names to the library names and
351 # passes the library names. There is no need to translate missing
352 # optional libs because they are missing and therefore there is no
353 # mapping for them.
Ulya Trofimovichc68b2892022-06-13 09:04:49 +0000354 mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
355 required = translate_libnames(args.uses_libraries, mod_to_lib)
356 optional = translate_libnames(args.optional_uses_libraries,
357 mod_to_lib)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000358
Spandan Dasf8807422021-08-25 20:01:17 +0000359 # Check if the <uses-library> lists in the build system agree with
360 # those in the manifest. Raise an exception on mismatch, unless the
361 # script was passed a special parameter to suppress exceptions.
362 errmsg = enforce_uses_libraries(manifest, required, optional,
Jiakai Zhangf98da192024-04-15 11:15:41 +0000363 args.missing_optional_uses_libraries,
364 args.enforce_uses_libraries_relax, is_apk, args.input)
Ulya Trafimovich8c35fcf2021-02-17 16:23:28 +0000365
Spandan Dasf8807422021-08-25 20:01:17 +0000366 # Create a status file that is empty on success, or contains an
367 # error message on failure. When exceptions are suppressed,
Colin Cross6cb462b2024-09-11 11:34:35 -0700368 # dexpreopt command will check file size to determine if
Spandan Dasf8807422021-08-25 20:01:17 +0000369 # the check has failed.
370 if args.enforce_uses_libraries_status:
371 with open(args.enforce_uses_libraries_status, 'w') as f:
Spandan Das3d5cd4d2021-09-20 18:24:56 +0000372 if errmsg is not None:
Spandan Dasf8807422021-08-25 20:01:17 +0000373 f.write('%s\n' % errmsg)
Colin Cross72119102019-05-20 13:14:18 -0700374
Spandan Dasf8807422021-08-25 20:01:17 +0000375 if args.extract_target_sdk_version:
376 try:
377 print(extract_target_sdk_version(manifest, is_apk))
Colin Cross6cb462b2024-09-11 11:34:35 -0700378 except: #pylint: disable=bare-except
Spandan Dasf8807422021-08-25 20:01:17 +0000379 # Failed; don't crash, return "any" SDK version. This will
380 # result in dexpreopt not adding any compatibility libraries.
381 print(10000)
Colin Cross72119102019-05-20 13:14:18 -0700382
Spandan Dasf8807422021-08-25 20:01:17 +0000383 if args.output:
384 # XML output is supposed to be written only when this script is
385 # invoked with XML input manifest, not with an APK.
386 if is_apk:
387 raise RuntimeError('cannot save APK manifest as XML')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000388
Cole Faustc41dd722021-11-09 15:08:26 -0800389 with open(args.output, 'w') as f:
Spandan Dasf8807422021-08-25 20:01:17 +0000390 write_xml(f, manifest)
Colin Cross72119102019-05-20 13:14:18 -0700391
Spandan Dasf8807422021-08-25 20:01:17 +0000392 # pylint: disable=broad-except
393 except Exception as err:
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000394 print('%serror:%s ' % (C_RED, C_OFF) + str(err), file=sys.stderr)
Spandan Dasf8807422021-08-25 20:01:17 +0000395 sys.exit(-1)
396
Colin Cross72119102019-05-20 13:14:18 -0700397
398if __name__ == '__main__':
Spandan Dasf8807422021-08-25 20:01:17 +0000399 main()