blob: bab2fcdac052546f6d6af3a83a46feedc9ef4a8c [file] [log] [blame]
Darryl Green7c2dd582018-03-01 14:53:49 +00001#!/usr/bin/env python3
Darryl Green78696802018-04-06 11:23:22 +01002"""
3This file is part of Mbed TLS (https://tls.mbed.org)
4
5Copyright (c) 2018, Arm Limited, All Rights Reserved
6
7Purpose
8
9This script is a small wrapper around the abi-compliance-checker and
10abi-dumper tools, applying them to compare the ABI and API of the library
11files from two different Git revisions within an Mbed TLS repository.
Darryl Greene62f9bb2019-02-21 13:09:26 +000012The results of the comparison are either formatted as HTML and stored at
Darryl Green4cde8a02019-03-05 15:21:32 +000013a configurable location, or are given as a brief list of problems.
Darryl Greene62f9bb2019-02-21 13:09:26 +000014Returns 0 on success, 1 on ABI/API non-compliance, and 2 if there is an error
15while running the script. Note: must be run from Mbed TLS root.
Darryl Green78696802018-04-06 11:23:22 +010016"""
Darryl Green7c2dd582018-03-01 14:53:49 +000017
18import os
19import sys
20import traceback
21import shutil
22import subprocess
23import argparse
24import logging
25import tempfile
Darryl Green9f357d62019-02-25 11:35:05 +000026import fnmatch
Darryl Green7c2dd582018-03-01 14:53:49 +000027
Darryl Greene62f9bb2019-02-21 13:09:26 +000028import xml.etree.ElementTree as ET
29
Darryl Green7c2dd582018-03-01 14:53:49 +000030
31class AbiChecker(object):
Gilles Peskine712afa72019-02-25 20:36:52 +010032 """API and ABI checker."""
Darryl Green7c2dd582018-03-01 14:53:49 +000033
Darryl Green9f357d62019-02-25 11:35:05 +000034 def __init__(self, report_dir, old_repo, old_rev, old_crypto_rev,
Darryl Green48311452019-02-27 17:33:31 +000035 old_crypto_repo, new_repo, new_rev, new_crypto_rev,
36 new_crypto_repo, keep_all_reports, brief, skip_file=None):
Gilles Peskine712afa72019-02-25 20:36:52 +010037 """Instantiate the API/ABI checker.
38
39 report_dir: directory for output files
Darryl Greenda84e322019-02-19 16:59:33 +000040 old_repo: repository for git revision to compare against
Gilles Peskine712afa72019-02-25 20:36:52 +010041 old_rev: reference git revision to compare against
Darryl Green9f357d62019-02-25 11:35:05 +000042 old_crypto_rev: reference git revision for old crypto submodule
Darryl Green48311452019-02-27 17:33:31 +000043 old_crypto_repo: repository for git revision for old crypto submodule
Darryl Greenda84e322019-02-19 16:59:33 +000044 new_repo: repository for git revision to check
Gilles Peskine712afa72019-02-25 20:36:52 +010045 new_rev: git revision to check
Darryl Green9f357d62019-02-25 11:35:05 +000046 new_crypto_rev: reference git revision for new crypto submodule
Darryl Green48311452019-02-27 17:33:31 +000047 new_crypto_repo: repository for git revision for new crypto submodule
Gilles Peskine712afa72019-02-25 20:36:52 +010048 keep_all_reports: if false, delete old reports
Darryl Greene62f9bb2019-02-21 13:09:26 +000049 brief: if true, output shorter report to stdout
Darryl Greenc2883a22019-02-20 15:01:56 +000050 skip_file: path to file containing symbols and types to skip
Gilles Peskine712afa72019-02-25 20:36:52 +010051 """
Darryl Green7c2dd582018-03-01 14:53:49 +000052 self.repo_path = "."
53 self.log = None
54 self.setup_logger()
55 self.report_dir = os.path.abspath(report_dir)
56 self.keep_all_reports = keep_all_reports
Darryl Green3d3d5522019-02-25 17:01:55 +000057 self.can_remove_report_dir = not (os.path.isdir(self.report_dir) or
58 keep_all_reports)
Darryl Greenda84e322019-02-19 16:59:33 +000059 self.old_repo = old_repo
Darryl Green7c2dd582018-03-01 14:53:49 +000060 self.old_rev = old_rev
Darryl Green9f357d62019-02-25 11:35:05 +000061 self.old_crypto_rev = old_crypto_rev
Darryl Green48311452019-02-27 17:33:31 +000062 self.old_crypto_repo = old_crypto_repo
Darryl Greenda84e322019-02-19 16:59:33 +000063 self.new_repo = new_repo
Darryl Green7c2dd582018-03-01 14:53:49 +000064 self.new_rev = new_rev
Darryl Green9f357d62019-02-25 11:35:05 +000065 self.new_crypto_rev = new_crypto_rev
Darryl Green48311452019-02-27 17:33:31 +000066 self.new_crypto_repo = new_crypto_repo
Darryl Greenc2883a22019-02-20 15:01:56 +000067 self.skip_file = skip_file
Darryl Greene62f9bb2019-02-21 13:09:26 +000068 self.brief = brief
Darryl Green3e7a9802019-02-27 16:53:40 +000069 self.mbedtls_modules = {"old": {}, "new": {}}
Darryl Green7c2dd582018-03-01 14:53:49 +000070 self.old_dumps = {}
71 self.new_dumps = {}
72 self.git_command = "git"
73 self.make_command = "make"
74
Gilles Peskine712afa72019-02-25 20:36:52 +010075 @staticmethod
76 def check_repo_path():
Darryl Greena6f430f2018-03-15 10:12:06 +000077 current_dir = os.path.realpath('.')
78 root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
79 if current_dir != root_dir:
Darryl Green7c2dd582018-03-01 14:53:49 +000080 raise Exception("Must be run from Mbed TLS root")
81
82 def setup_logger(self):
83 self.log = logging.getLogger()
84 self.log.setLevel(logging.INFO)
85 self.log.addHandler(logging.StreamHandler())
86
Gilles Peskine712afa72019-02-25 20:36:52 +010087 @staticmethod
88 def check_abi_tools_are_installed():
Darryl Green7c2dd582018-03-01 14:53:49 +000089 for command in ["abi-dumper", "abi-compliance-checker"]:
90 if not shutil.which(command):
91 raise Exception("{} not installed, aborting".format(command))
92
Darryl Greenda84e322019-02-19 16:59:33 +000093 def get_clean_worktree_for_git_revision(self, remote_repo, git_rev):
Gilles Peskine712afa72019-02-25 20:36:52 +010094 """Make a separate worktree with git_rev checked out.
95 Do not modify the current worktree."""
Darryl Green7c2dd582018-03-01 14:53:49 +000096 git_worktree_path = tempfile.mkdtemp()
Darryl Greenda84e322019-02-19 16:59:33 +000097 if remote_repo:
98 self.log.info(
99 "Checking out git worktree for revision {} from {}".format(
100 git_rev, remote_repo
101 )
102 )
103 fetch_process = subprocess.Popen(
104 [self.git_command, "fetch", remote_repo, git_rev],
105 cwd=self.repo_path,
106 stdout=subprocess.PIPE,
107 stderr=subprocess.STDOUT
108 )
109 fetch_output, _ = fetch_process.communicate()
110 self.log.info(fetch_output.decode("utf-8"))
111 if fetch_process.returncode != 0:
112 raise Exception("Fetching revision failed, aborting")
113 worktree_rev = "FETCH_HEAD"
114 else:
115 self.log.info(
116 "Checking out git worktree for revision {}".format(git_rev)
117 )
118 worktree_rev = git_rev
Darryl Green7c2dd582018-03-01 14:53:49 +0000119 worktree_process = subprocess.Popen(
Darryl Greenda84e322019-02-19 16:59:33 +0000120 [self.git_command, "worktree", "add", "--detach",
121 git_worktree_path, worktree_rev],
Darryl Green7c2dd582018-03-01 14:53:49 +0000122 cwd=self.repo_path,
123 stdout=subprocess.PIPE,
124 stderr=subprocess.STDOUT
125 )
126 worktree_output, _ = worktree_process.communicate()
127 self.log.info(worktree_output.decode("utf-8"))
128 if worktree_process.returncode != 0:
129 raise Exception("Checking out worktree failed, aborting")
130 return git_worktree_path
131
Darryl Green48311452019-02-27 17:33:31 +0000132 def update_git_submodules(self, git_worktree_path, crypto_repo,
133 crypto_rev):
Jaeden Ameroffeb1b82018-11-02 16:35:09 +0000134 process = subprocess.Popen(
135 [self.git_command, "submodule", "update", "--init", '--recursive'],
136 cwd=git_worktree_path,
137 stdout=subprocess.PIPE,
138 stderr=subprocess.STDOUT
139 )
140 output, _ = process.communicate()
141 self.log.info(output.decode("utf-8"))
142 if process.returncode != 0:
143 raise Exception("git submodule update failed, aborting")
Darryl Greene29ce702019-03-05 15:23:25 +0000144 if not (os.path.exists(os.path.join(git_worktree_path, "crypto"))
Darryl Green9f357d62019-02-25 11:35:05 +0000145 and crypto_rev):
Darryl Greene29ce702019-03-05 15:23:25 +0000146 return
147
148 if crypto_repo:
149 shutil.rmtree(os.path.join(git_worktree_path, "crypto"))
150 clone_process = subprocess.Popen(
151 [self.git_command, "clone", crypto_repo,
152 "--branch", crypto_rev, "crypto"],
153 cwd=git_worktree_path,
154 stdout=subprocess.PIPE,
155 stderr=subprocess.STDOUT
156 )
157 clone_output, _ = clone_process.communicate()
158 self.log.info(clone_output.decode("utf-8"))
159 if clone_process.returncode != 0:
160 raise Exception("git clone failed, aborting")
161 else:
162 checkout_process = subprocess.Popen(
163 [self.git_command, "checkout", crypto_rev],
164 cwd=os.path.join(git_worktree_path, "crypto"),
165 stdout=subprocess.PIPE,
166 stderr=subprocess.STDOUT
167 )
168 checkout_output, _ = checkout_process.communicate()
169 self.log.info(checkout_output.decode("utf-8"))
170 if checkout_process.returncode != 0:
171 raise Exception("git checkout failed, aborting")
Jaeden Ameroffeb1b82018-11-02 16:35:09 +0000172
Darryl Green3e7a9802019-02-27 16:53:40 +0000173 def build_shared_libraries(self, git_worktree_path, version):
Gilles Peskine712afa72019-02-25 20:36:52 +0100174 """Build the shared libraries in the specified worktree."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000175 my_environment = os.environ.copy()
176 my_environment["CFLAGS"] = "-g -Og"
177 my_environment["SHARED"] = "1"
Darryl Green9f357d62019-02-25 11:35:05 +0000178 my_environment["USE_CRYPTO_SUBMODULE"] = "1"
Darryl Green7c2dd582018-03-01 14:53:49 +0000179 make_process = subprocess.Popen(
Darryl Greenddf25a62019-02-28 11:52:39 +0000180 [self.make_command, "lib"],
Darryl Green7c2dd582018-03-01 14:53:49 +0000181 env=my_environment,
182 cwd=git_worktree_path,
183 stdout=subprocess.PIPE,
184 stderr=subprocess.STDOUT
185 )
186 make_output, _ = make_process.communicate()
187 self.log.info(make_output.decode("utf-8"))
Darryl Green9f357d62019-02-25 11:35:05 +0000188 for root, dirs, files in os.walk(git_worktree_path):
189 for file in fnmatch.filter(files, "*.so"):
Darryl Green3e7a9802019-02-27 16:53:40 +0000190 self.mbedtls_modules[version][os.path.splitext(file)[0]] = (
191 os.path.join(root, file)
Darryl Green9f357d62019-02-25 11:35:05 +0000192 )
Darryl Green7c2dd582018-03-01 14:53:49 +0000193 if make_process.returncode != 0:
194 raise Exception("make failed, aborting")
195
Darryl Green3e7a9802019-02-27 16:53:40 +0000196 def get_abi_dumps_from_shared_libraries(self, git_ref, git_worktree_path,
197 version):
Gilles Peskine712afa72019-02-25 20:36:52 +0100198 """Generate the ABI dumps for the specified git revision.
199 It must be checked out in git_worktree_path and the shared libraries
200 must have been built."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000201 abi_dumps = {}
Darryl Green3e7a9802019-02-27 16:53:40 +0000202 for mbed_module, module_path in self.mbedtls_modules[version].items():
Darryl Green7c2dd582018-03-01 14:53:49 +0000203 output_path = os.path.join(
Darryl Green3e7a9802019-02-27 16:53:40 +0000204 self.report_dir, version, "{}-{}.dump".format(
205 mbed_module, git_ref
206 )
Darryl Green7c2dd582018-03-01 14:53:49 +0000207 )
208 abi_dump_command = [
209 "abi-dumper",
Darryl Green9f357d62019-02-25 11:35:05 +0000210 module_path,
Darryl Green7c2dd582018-03-01 14:53:49 +0000211 "-o", output_path,
212 "-lver", git_ref
213 ]
214 abi_dump_process = subprocess.Popen(
215 abi_dump_command,
216 stdout=subprocess.PIPE,
217 stderr=subprocess.STDOUT
218 )
219 abi_dump_output, _ = abi_dump_process.communicate()
220 self.log.info(abi_dump_output.decode("utf-8"))
221 if abi_dump_process.returncode != 0:
222 raise Exception("abi-dumper failed, aborting")
223 abi_dumps[mbed_module] = output_path
224 return abi_dumps
225
226 def cleanup_worktree(self, git_worktree_path):
Gilles Peskine712afa72019-02-25 20:36:52 +0100227 """Remove the specified git worktree."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000228 shutil.rmtree(git_worktree_path)
229 worktree_process = subprocess.Popen(
230 [self.git_command, "worktree", "prune"],
231 cwd=self.repo_path,
232 stdout=subprocess.PIPE,
233 stderr=subprocess.STDOUT
234 )
235 worktree_output, _ = worktree_process.communicate()
236 self.log.info(worktree_output.decode("utf-8"))
237 if worktree_process.returncode != 0:
238 raise Exception("Worktree cleanup failed, aborting")
239
Darryl Green48311452019-02-27 17:33:31 +0000240 def get_abi_dump_for_ref(self, remote_repo, git_rev, crypto_repo,
241 crypto_rev, version):
Gilles Peskine712afa72019-02-25 20:36:52 +0100242 """Generate the ABI dumps for the specified git revision."""
Darryl Greenda84e322019-02-19 16:59:33 +0000243 git_worktree_path = self.get_clean_worktree_for_git_revision(
244 remote_repo, git_rev
245 )
Darryl Green48311452019-02-27 17:33:31 +0000246 self.update_git_submodules(git_worktree_path, crypto_repo, crypto_rev)
Darryl Green3e7a9802019-02-27 16:53:40 +0000247 self.build_shared_libraries(git_worktree_path, version)
Darryl Green7c2dd582018-03-01 14:53:49 +0000248 abi_dumps = self.get_abi_dumps_from_shared_libraries(
Darryl Green3e7a9802019-02-27 16:53:40 +0000249 git_rev, git_worktree_path, version
Darryl Green7c2dd582018-03-01 14:53:49 +0000250 )
251 self.cleanup_worktree(git_worktree_path)
252 return abi_dumps
253
Darryl Greene62f9bb2019-02-21 13:09:26 +0000254 def remove_children_with_tag(self, parent, tag):
255 children = parent.getchildren()
256 for child in children:
257 if child.tag == tag:
258 parent.remove(child)
259 else:
260 self.remove_children_with_tag(child, tag)
261
262 def remove_extra_detail_from_report(self, report_root):
263 for tag in ['test_info', 'test_results', 'problem_summary',
264 'added_symbols', 'removed_symbols', 'affected']:
265 self.remove_children_with_tag(report_root, tag)
266
267 for report in report_root:
268 for problems in report.getchildren()[:]:
269 if not problems.getchildren():
270 report.remove(problems)
271
Darryl Green7c2dd582018-03-01 14:53:49 +0000272 def get_abi_compatibility_report(self):
Gilles Peskine712afa72019-02-25 20:36:52 +0100273 """Generate a report of the differences between the reference ABI
274 and the new ABI. ABI dumps from self.old_rev and self.new_rev must
275 be available."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000276 compatibility_report = ""
277 compliance_return_code = 0
Darryl Green3e7a9802019-02-27 16:53:40 +0000278 shared_modules = list(set(self.mbedtls_modules["old"].keys()) &
279 set(self.mbedtls_modules["new"].keys()))
280 for mbed_module in shared_modules:
Darryl Green7c2dd582018-03-01 14:53:49 +0000281 output_path = os.path.join(
282 self.report_dir, "{}-{}-{}.html".format(
283 mbed_module, self.old_rev, self.new_rev
284 )
285 )
286 abi_compliance_command = [
287 "abi-compliance-checker",
288 "-l", mbed_module,
289 "-old", self.old_dumps[mbed_module],
290 "-new", self.new_dumps[mbed_module],
291 "-strict",
Darryl Greene62f9bb2019-02-21 13:09:26 +0000292 "-report-path", output_path,
Darryl Green7c2dd582018-03-01 14:53:49 +0000293 ]
Darryl Greenc2883a22019-02-20 15:01:56 +0000294 if self.skip_file:
295 abi_compliance_command += ["-skip-symbols", self.skip_file,
296 "-skip-types", self.skip_file]
Darryl Greene62f9bb2019-02-21 13:09:26 +0000297 if self.brief:
298 abi_compliance_command += ["-report-format", "xml",
299 "-stdout"]
Darryl Green7c2dd582018-03-01 14:53:49 +0000300 abi_compliance_process = subprocess.Popen(
301 abi_compliance_command,
302 stdout=subprocess.PIPE,
303 stderr=subprocess.STDOUT
304 )
305 abi_compliance_output, _ = abi_compliance_process.communicate()
Darryl Green7c2dd582018-03-01 14:53:49 +0000306 if abi_compliance_process.returncode == 0:
307 compatibility_report += (
308 "No compatibility issues for {}\n".format(mbed_module)
309 )
Darryl Greene62f9bb2019-02-21 13:09:26 +0000310 if not (self.keep_all_reports or self.brief):
Darryl Green7c2dd582018-03-01 14:53:49 +0000311 os.remove(output_path)
312 elif abi_compliance_process.returncode == 1:
Darryl Greene62f9bb2019-02-21 13:09:26 +0000313 if self.brief:
314 self.log.info(
315 "Compatibility issues found for {}".format(mbed_module)
316 )
317 report_root = ET.fromstring(abi_compliance_output.decode("utf-8"))
318 self.remove_extra_detail_from_report(report_root)
319 self.log.info(ET.tostring(report_root).decode("utf-8"))
320 else:
321 compliance_return_code = 1
322 self.can_remove_report_dir = False
323 compatibility_report += (
324 "Compatibility issues found for {}, "
325 "for details see {}\n".format(mbed_module, output_path)
326 )
Darryl Green7c2dd582018-03-01 14:53:49 +0000327 else:
328 raise Exception(
329 "abi-compliance-checker failed with a return code of {},"
330 " aborting".format(abi_compliance_process.returncode)
331 )
332 os.remove(self.old_dumps[mbed_module])
333 os.remove(self.new_dumps[mbed_module])
Darryl Green3d3d5522019-02-25 17:01:55 +0000334 if self.can_remove_report_dir:
Darryl Green7c2dd582018-03-01 14:53:49 +0000335 os.rmdir(self.report_dir)
336 self.log.info(compatibility_report)
337 return compliance_return_code
338
339 def check_for_abi_changes(self):
Gilles Peskine712afa72019-02-25 20:36:52 +0100340 """Generate a report of ABI differences
341 between self.old_rev and self.new_rev."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000342 self.check_repo_path()
343 self.check_abi_tools_are_installed()
Darryl Green9f357d62019-02-25 11:35:05 +0000344 self.old_dumps = self.get_abi_dump_for_ref(self.old_repo, self.old_rev,
Darryl Green48311452019-02-27 17:33:31 +0000345 self.old_crypto_repo,
Darryl Green3e7a9802019-02-27 16:53:40 +0000346 self.old_crypto_rev, "old")
Darryl Green9f357d62019-02-25 11:35:05 +0000347 self.new_dumps = self.get_abi_dump_for_ref(self.new_repo, self.new_rev,
Darryl Green48311452019-02-27 17:33:31 +0000348 self.new_crypto_repo,
Darryl Green3e7a9802019-02-27 16:53:40 +0000349 self.new_crypto_rev, "new")
Darryl Green7c2dd582018-03-01 14:53:49 +0000350 return self.get_abi_compatibility_report()
351
352
353def run_main():
354 try:
355 parser = argparse.ArgumentParser(
356 description=(
Darryl Green418527b2018-04-16 12:02:29 +0100357 """This script is a small wrapper around the
358 abi-compliance-checker and abi-dumper tools, applying them
359 to compare the ABI and API of the library files from two
360 different Git revisions within an Mbed TLS repository.
Darryl Greene62f9bb2019-02-21 13:09:26 +0000361 The results of the comparison are either formatted as HTML and
Darryl Green4cde8a02019-03-05 15:21:32 +0000362 stored at a configurable location, or are given as a brief list
363 of problems. Returns 0 on success, 1 on ABI/API non-compliance,
364 and 2 if there is an error while running the script.
365 Note: must be run from Mbed TLS root."""
Darryl Green7c2dd582018-03-01 14:53:49 +0000366 )
367 )
368 parser.add_argument(
Darryl Green418527b2018-04-16 12:02:29 +0100369 "-r", "--report-dir", type=str, default="reports",
Darryl Green7c2dd582018-03-01 14:53:49 +0000370 help="directory where reports are stored, default is reports",
371 )
372 parser.add_argument(
Darryl Green418527b2018-04-16 12:02:29 +0100373 "-k", "--keep-all-reports", action="store_true",
Darryl Green7c2dd582018-03-01 14:53:49 +0000374 help="keep all reports, even if there are no compatibility issues",
375 )
376 parser.add_argument(
Darryl Greenc5132ff2019-03-01 09:54:44 +0000377 "-o", "--old-rev", type=str, help="revision for old version.",
378 required=True,
Darryl Green7c2dd582018-03-01 14:53:49 +0000379 )
380 parser.add_argument(
Darryl Greenc5132ff2019-03-01 09:54:44 +0000381 "-or", "--old-repo", type=str, help="repository for old version."
Darryl Green9f357d62019-02-25 11:35:05 +0000382 )
383 parser.add_argument(
Darryl Greenc5132ff2019-03-01 09:54:44 +0000384 "-oc", "--old-crypto-rev", type=str,
385 help="revision for old crypto submodule."
Darryl Green7c2dd582018-03-01 14:53:49 +0000386 )
Darryl Greenc2883a22019-02-20 15:01:56 +0000387 parser.add_argument(
Darryl Greenc5132ff2019-03-01 09:54:44 +0000388 "-ocr", "--old-crypto-repo", type=str,
389 help="repository for old crypto submodule."
390 )
391 parser.add_argument(
392 "-n", "--new-rev", type=str, help="revision for new version",
393 required=True,
394 )
395 parser.add_argument(
396 "-nr", "--new-repo", type=str, help="repository for new version."
397 )
398 parser.add_argument(
399 "-nc", "--new-crypto-rev", type=str,
400 help="revision for new crypto version"
401 )
402 parser.add_argument(
403 "-ncr", "--new-crypto-repo", type=str,
404 help="repository for new crypto submodule."
Darryl Green9f357d62019-02-25 11:35:05 +0000405 )
406 parser.add_argument(
Darryl Greenc2883a22019-02-20 15:01:56 +0000407 "-s", "--skip-file", type=str,
408 help="path to file containing symbols and types to skip"
409 )
Darryl Greene62f9bb2019-02-21 13:09:26 +0000410 parser.add_argument(
411 "-b", "--brief", action="store_true",
412 help="output only the list of issues to stdout, instead of a full report",
413 )
Darryl Green7c2dd582018-03-01 14:53:49 +0000414 abi_args = parser.parse_args()
415 abi_check = AbiChecker(
Darryl Greenc5132ff2019-03-01 09:54:44 +0000416 abi_args.report_dir, abi_args.old_repo, abi_args.old_rev,
417 abi_args.old_crypto_rev, abi_args.old_crypto_repo,
418 abi_args.new_repo, abi_args.new_rev, abi_args.new_crypto_rev,
419 abi_args.new_crypto_repo, abi_args.keep_all_reports,
420 abi_args.brief, abi_args.skip_file
Darryl Green7c2dd582018-03-01 14:53:49 +0000421 )
422 return_code = abi_check.check_for_abi_changes()
423 sys.exit(return_code)
Gilles Peskinee915d532019-02-25 21:39:42 +0100424 except Exception: # pylint: disable=broad-except
425 # Print the backtrace and exit explicitly so as to exit with
426 # status 2, not 1.
Darryl Greena6f430f2018-03-15 10:12:06 +0000427 traceback.print_exc()
Darryl Green7c2dd582018-03-01 14:53:49 +0000428 sys.exit(2)
429
430
431if __name__ == "__main__":
432 run_main()