blob: 0216fc0fe7bd08243b5dfb6fd524988c91dc265c [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
19from __future__ import print_function
20
21import argparse
Ulya Trafimovich3c902e72021-03-04 18:06:27 +000022import json
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010023import os
Ulya Trafimovich0aba2522021-03-03 16:38:37 +000024import re
25import subprocess
Colin Cross72119102019-05-20 13:14:18 -070026import sys
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010027from collections import OrderedDict
Colin Cross72119102019-05-20 13:14:18 -070028from xml.dom import minidom
29
Colin Cross72119102019-05-20 13:14:18 -070030from manifest import android_ns
31from manifest import get_children_with_tag
32from manifest import parse_manifest
33from manifest import write_xml
34
35
36class ManifestMismatchError(Exception):
Spandan Dasf8807422021-08-25 20:01:17 +000037 pass
Colin Cross72119102019-05-20 13:14:18 -070038
39
40def parse_args():
Spandan Dasf8807422021-08-25 20:01:17 +000041 """Parse commandline arguments."""
Colin Cross72119102019-05-20 13:14:18 -070042
Spandan Dasf8807422021-08-25 20:01:17 +000043 parser = argparse.ArgumentParser()
44 parser.add_argument(
45 '--uses-library',
46 dest='uses_libraries',
47 action='append',
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010048 default=[],
Spandan Dasf8807422021-08-25 20:01:17 +000049 help='specify uses-library entries known to the build system')
50 parser.add_argument(
51 '--optional-uses-library',
52 dest='optional_uses_libraries',
53 action='append',
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010054 default=[],
Spandan Dasf8807422021-08-25 20:01:17 +000055 help='specify uses-library entries known to the build system with '
56 'required:false'
57 )
58 parser.add_argument(
59 '--enforce-uses-libraries',
60 dest='enforce_uses_libraries',
61 action='store_true',
62 help='check the uses-library entries known to the build system against '
63 'the manifest'
64 )
65 parser.add_argument(
66 '--enforce-uses-libraries-relax',
67 dest='enforce_uses_libraries_relax',
68 action='store_true',
69 help='do not fail immediately, just save the error message to file')
70 parser.add_argument(
71 '--enforce-uses-libraries-status',
72 dest='enforce_uses_libraries_status',
73 help='output file to store check status (error message)')
74 parser.add_argument(
75 '--extract-target-sdk-version',
76 dest='extract_target_sdk_version',
77 action='store_true',
78 help='print the targetSdkVersion from the manifest')
79 parser.add_argument(
80 '--dexpreopt-config',
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010081 dest='dexpreopt_config',
82 help='a path to dexpreopt.config file for this library/app')
83 parser.add_argument(
84 '--dexpreopt-dep-config',
85 dest='dexpreopt_dep_configs',
Spandan Dasf8807422021-08-25 20:01:17 +000086 action='append',
Ulya Trafimovich24abbe82022-04-28 12:50:47 +010087 default=[],
88 help='a path to dexpreopt.config file for a dependency library')
Spandan Dasf8807422021-08-25 20:01:17 +000089 parser.add_argument('--aapt', dest='aapt', help='path to aapt executable')
90 parser.add_argument(
91 '--output', '-o', dest='output', help='output AndroidManifest.xml file')
92 parser.add_argument('input', help='input AndroidManifest.xml file')
93 return parser.parse_args()
Colin Cross72119102019-05-20 13:14:18 -070094
95
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +000096C_RED = "\033[1;31m"
97C_GREEN = "\033[1;32m"
98C_BLUE = "\033[1;34m"
99C_OFF = "\033[0m"
100C_BOLD = "\033[1m"
101
102
Ulya Trafimovichbb7513d2021-03-30 17:15:16 +0100103def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path):
Spandan Dasf8807422021-08-25 20:01:17 +0000104 """Verify that the <uses-library> tags in the manifest match those provided
105
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000106 by the build system.
Colin Cross72119102019-05-20 13:14:18 -0700107
108 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000109 manifest: manifest (either parsed XML or aapt dump of APK)
110 required: required libs known to the build system
111 optional: optional libs known to the build system
112 relax: if true, suppress error on mismatch and just write it to file
113 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +0000114 """
115 if is_apk:
116 manifest_required, manifest_optional, tags = extract_uses_libs_apk(
117 manifest)
118 else:
119 manifest_required, manifest_optional, tags = extract_uses_libs_xml(
120 manifest)
Colin Cross72119102019-05-20 13:14:18 -0700121
Spandan Dasf8807422021-08-25 20:01:17 +0000122 # Trim namespace component. Normally Soong does that automatically when it
123 # handles module names specified in Android.bp properties. However not all
124 # <uses-library> entries in the manifest correspond to real modules: some of
125 # the optional libraries may be missing at build time. Therefor this script
126 # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the
127 # optional namespace part manually.
128 required = trim_namespace_parts(required)
129 optional = trim_namespace_parts(optional)
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100130
Spandan Dasf8807422021-08-25 20:01:17 +0000131 if manifest_required == required and manifest_optional == optional:
132 return None
Colin Cross72119102019-05-20 13:14:18 -0700133
Spandan Dasf8807422021-08-25 20:01:17 +0000134 #pylint: disable=line-too-long
135 errmsg = ''.join([
136 'mismatch in the <uses-library> tags between the build system and the '
137 'manifest:\n',
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000138 '\t- required libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(required), C_OFF),
139 '\t vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_required), C_OFF),
140 '\t- optional libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(optional), C_OFF),
141 '\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
Spandan Dasf8807422021-08-25 20:01:17 +0000199def extract_uses_libs_xml(xml): #pylint: disable=inconsistent-return-statements
200 """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)
203 elems = get_children_with_tag(manifest, 'application')
204 application = elems[0] if len(elems) == 1 else None
205 if len(elems) > 1: #pylint: disable=no-else-raise
206 raise RuntimeError('found multiple <application> tags')
207 elif not elems:
208 if uses_libraries or optional_uses_libraries: #pylint: disable=undefined-variable
209 raise ManifestMismatchError('no <application> tag found')
210 return
Colin Cross72119102019-05-20 13:14:18 -0700211
Spandan Dasf8807422021-08-25 20:01:17 +0000212 libs = get_children_with_tag(application, 'uses-library')
Colin Cross72119102019-05-20 13:14:18 -0700213
Spandan Dasf8807422021-08-25 20:01:17 +0000214 required = [uses_library_name(x) for x in libs if uses_library_required(x)]
215 optional = [
216 uses_library_name(x) for x in libs if not uses_library_required(x)
217 ]
Colin Cross72119102019-05-20 13:14:18 -0700218
Spandan Dasf8807422021-08-25 20:01:17 +0000219 # render <uses-library> tags as XML for a pretty error message
220 tags = []
221 for lib in libs:
222 tags.append(lib.toprettyxml())
Ulya Trafimovichbb7513d2021-03-30 17:15:16 +0100223
Spandan Dasf8807422021-08-25 20:01:17 +0000224 required = first_unique_elements(required)
225 optional = first_unique_elements(optional)
226 tags = first_unique_elements(tags)
227 return required, optional, tags
Colin Cross72119102019-05-20 13:14:18 -0700228
229
230def first_unique_elements(l):
Spandan Dasf8807422021-08-25 20:01:17 +0000231 result = []
232 for x in l:
233 if x not in result:
234 result.append(x)
235 return result
Colin Cross72119102019-05-20 13:14:18 -0700236
237
238def uses_library_name(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000239 """Extract the name attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700240
241 Args:
242 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000243 """
244 name = lib.getAttributeNodeNS(android_ns, 'name')
245 return name.value if name is not None else ''
Colin Cross72119102019-05-20 13:14:18 -0700246
247
248def uses_library_required(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000249 """Extract the required attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700250
251 Args:
252 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000253 """
254 required = lib.getAttributeNodeNS(android_ns, 'required')
255 return (required.value == 'true') if required is not None else True
Colin Cross72119102019-05-20 13:14:18 -0700256
257
Spandan Dasf8807422021-08-25 20:01:17 +0000258def extract_target_sdk_version(manifest, is_apk=False):
259 """Returns the targetSdkVersion from the manifest.
Colin Cross72119102019-05-20 13:14:18 -0700260
261 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000262 manifest: manifest (either parsed XML or aapt dump of APK)
263 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +0000264 """
265 if is_apk: #pylint: disable=no-else-return
266 return extract_target_sdk_version_apk(manifest)
267 else:
268 return extract_target_sdk_version_xml(manifest)
Colin Cross72119102019-05-20 13:14:18 -0700269
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000270
271def extract_target_sdk_version_apk(badging):
Spandan Dasf8807422021-08-25 20:01:17 +0000272 """Extract targetSdkVersion tags from the manifest of an APK."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000273
Spandan Dasf8807422021-08-25 20:01:17 +0000274 pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000275
Spandan Dasf8807422021-08-25 20:01:17 +0000276 for match in re.finditer(pattern, badging):
277 return match.group(1)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000278
Spandan Dasf8807422021-08-25 20:01:17 +0000279 raise RuntimeError('cannot find targetSdkVersion in the manifest')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000280
281
282def extract_target_sdk_version_xml(xml):
Spandan Dasf8807422021-08-25 20:01:17 +0000283 """Extract targetSdkVersion tags from the manifest."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000284
Spandan Dasf8807422021-08-25 20:01:17 +0000285 manifest = parse_manifest(xml)
Colin Cross72119102019-05-20 13:14:18 -0700286
Spandan Dasf8807422021-08-25 20:01:17 +0000287 # Get or insert the uses-sdk element
288 uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
289 if len(uses_sdk) > 1: #pylint: disable=no-else-raise
290 raise RuntimeError('found multiple uses-sdk elements')
291 elif len(uses_sdk) == 0:
292 raise RuntimeError('missing uses-sdk element')
Colin Cross72119102019-05-20 13:14:18 -0700293
Spandan Dasf8807422021-08-25 20:01:17 +0000294 uses_sdk = uses_sdk[0]
Colin Cross72119102019-05-20 13:14:18 -0700295
Spandan Dasf8807422021-08-25 20:01:17 +0000296 min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
297 if min_attr is None:
298 raise RuntimeError('minSdkVersion is not specified')
Colin Cross72119102019-05-20 13:14:18 -0700299
Spandan Dasf8807422021-08-25 20:01:17 +0000300 target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
301 if target_attr is None:
302 target_attr = min_attr
Colin Cross72119102019-05-20 13:14:18 -0700303
Spandan Dasf8807422021-08-25 20:01:17 +0000304 return target_attr.value
Colin Cross72119102019-05-20 13:14:18 -0700305
306
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100307def remove_duplicates(l):
308 return list(OrderedDict.fromkeys(l))
309
310
311def load_dexpreopt_configs(args):
Spandan Dasf8807422021-08-25 20:01:17 +0000312 """Load dexpreopt.config files and map module names to library names."""
313 module_to_libname = {}
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000314
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100315 # Go over dexpreopt.config files for uses-library dependencies and create
316 # a mapping from module name to real library name (they may differ).
317 for config in args.dexpreopt_dep_configs:
318 # Empty dexpreopt.config files are expected for some dependencies.
319 if os.stat(config).st_size != 0:
320 with open(config, 'r') as f:
321 contents = json.load(f)
322 module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000323
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100324 required = translate_libnames(args.uses_libraries, module_to_libname)
325 optional = translate_libnames(args.optional_uses_libraries, module_to_libname)
326
327 # Add extra uses-libraries from the library/app's own dexpreopt.config.
328 # Extra libraries may be propagated via dependencies' dexpreopt.config files
329 # (not only uses-library ones, but also transitively via static libraries).
330 if args.dexpreopt_config:
331 with open(args.dexpreopt_config, 'r') as f:
Spandan Dasf8807422021-08-25 20:01:17 +0000332 contents = json.load(f)
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100333 for clc in contents['ClassLoaderContexts']['any']:
334 ulib = clc['Name']
335 if clc['Optional']:
336 optional.append(ulib)
337 else:
338 required.append(ulib)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000339
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100340 required = remove_duplicates(required)
341 optional = remove_duplicates(optional)
342
343 # If the same library is both in optional and required, prefer required.
344 # This may happen for compatibility libraries, e.g. org.apache.http.legacy.
345 for lib in required:
346 if lib in optional:
347 optional.remove(lib)
348
349 return required, optional
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000350
351
352def translate_libnames(modules, module_to_libname):
Spandan Dasf8807422021-08-25 20:01:17 +0000353 """Translate module names into library names using the mapping."""
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000354
Spandan Dasf8807422021-08-25 20:01:17 +0000355 libnames = []
356 for name in modules:
357 if name in module_to_libname:
358 name = module_to_libname[name]
359 libnames.append(name)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000360
Spandan Dasf8807422021-08-25 20:01:17 +0000361 return libnames
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000362
363
Colin Cross72119102019-05-20 13:14:18 -0700364def main():
Spandan Dasf8807422021-08-25 20:01:17 +0000365 """Program entry point."""
366 try:
367 args = parse_args()
Colin Cross72119102019-05-20 13:14:18 -0700368
Spandan Dasf8807422021-08-25 20:01:17 +0000369 # The input can be either an XML manifest or an APK, they are parsed and
370 # processed in different ways.
371 is_apk = args.input.endswith('.apk')
372 if is_apk:
373 aapt = args.aapt if args.aapt is not None else 'aapt'
374 manifest = subprocess.check_output(
Cole Faustc41dd722021-11-09 15:08:26 -0800375 [aapt, 'dump', 'badging', args.input]).decode('utf-8')
Spandan Dasf8807422021-08-25 20:01:17 +0000376 else:
377 manifest = minidom.parse(args.input)
Colin Cross72119102019-05-20 13:14:18 -0700378
Spandan Dasf8807422021-08-25 20:01:17 +0000379 if args.enforce_uses_libraries:
380 # Load dexpreopt.config files and build a mapping from module
381 # names to library names. This is necessary because build system
382 # addresses libraries by their module name (`uses_libs`,
383 # `optional_uses_libs`, `LOCAL_USES_LIBRARIES`,
384 # `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain module names), while
385 # the manifest addresses libraries by their name.
Ulya Trafimovich24abbe82022-04-28 12:50:47 +0100386 required, optional = load_dexpreopt_configs(args)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000387
Spandan Dasf8807422021-08-25 20:01:17 +0000388 # Check if the <uses-library> lists in the build system agree with
389 # those in the manifest. Raise an exception on mismatch, unless the
390 # script was passed a special parameter to suppress exceptions.
391 errmsg = enforce_uses_libraries(manifest, required, optional,
392 args.enforce_uses_libraries_relax,
393 is_apk, args.input)
Ulya Trafimovich8c35fcf2021-02-17 16:23:28 +0000394
Spandan Dasf8807422021-08-25 20:01:17 +0000395 # Create a status file that is empty on success, or contains an
396 # error message on failure. When exceptions are suppressed,
397 # dexpreopt command command will check file size to determine if
398 # the check has failed.
399 if args.enforce_uses_libraries_status:
400 with open(args.enforce_uses_libraries_status, 'w') as f:
Spandan Das3d5cd4d2021-09-20 18:24:56 +0000401 if errmsg is not None:
Spandan Dasf8807422021-08-25 20:01:17 +0000402 f.write('%s\n' % errmsg)
Colin Cross72119102019-05-20 13:14:18 -0700403
Spandan Dasf8807422021-08-25 20:01:17 +0000404 if args.extract_target_sdk_version:
405 try:
406 print(extract_target_sdk_version(manifest, is_apk))
407 except: #pylint: disable=bare-except
408 # Failed; don't crash, return "any" SDK version. This will
409 # result in dexpreopt not adding any compatibility libraries.
410 print(10000)
Colin Cross72119102019-05-20 13:14:18 -0700411
Spandan Dasf8807422021-08-25 20:01:17 +0000412 if args.output:
413 # XML output is supposed to be written only when this script is
414 # invoked with XML input manifest, not with an APK.
415 if is_apk:
416 raise RuntimeError('cannot save APK manifest as XML')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000417
Cole Faustc41dd722021-11-09 15:08:26 -0800418 with open(args.output, 'w') as f:
Spandan Dasf8807422021-08-25 20:01:17 +0000419 write_xml(f, manifest)
Colin Cross72119102019-05-20 13:14:18 -0700420
Spandan Dasf8807422021-08-25 20:01:17 +0000421 # pylint: disable=broad-except
422 except Exception as err:
Ulya Trafimovichb4c19f82021-11-01 12:57:59 +0000423 print('%serror:%s ' % (C_RED, C_OFF) + str(err), file=sys.stderr)
Spandan Dasf8807422021-08-25 20:01:17 +0000424 sys.exit(-1)
425
Colin Cross72119102019-05-20 13:14:18 -0700426
427if __name__ == '__main__':
Spandan Dasf8807422021-08-25 20:01:17 +0000428 main()