blob: 98d8be422b69872e44d7f72ab8e14fc6dc0dba2f [file] [log] [blame]
Darryl Green3da15042018-03-01 14:53:49 +00001#!/usr/bin/env python3
Darryl Greene3e6b182018-03-12 15:44:31 +00002#
Darryl Greenc47ac262018-03-15 10:12:06 +00003# This file is part of Mbed TLS (https://tls.mbed.org)
Darryl Greene3e6b182018-03-12 15:44:31 +00004#
5# Copyright (c) 2018, Arm Limited, All Rights Reserved
6#
7# Purpose
8#
Darryl Green3da15042018-03-01 14:53:49 +00009# This script is a small wrapper around the abi-compliance-checker and
10# abi-dumper tools, applying them to compare the ABI and API of the library
11# files from two different Git revisions within an Mbed TLS repository.
12# The results of the comparison are formatted as HTML and stored at
13# a configurable location. Returns 0 on success, 1 on ABI/API non-compliance,
14# and 2 if there is an error while running the script.
15# Note: must be run from Mbed TLS root.
16
17import os
18import sys
19import traceback
20import shutil
21import subprocess
22import argparse
23import logging
24import tempfile
25
26
27class AbiChecker(object):
28
29 def __init__(self, report_dir, old_rev, new_rev, keep_all_reports):
30 self.repo_path = "."
31 self.log = None
32 self.setup_logger()
33 self.report_dir = os.path.abspath(report_dir)
34 self.keep_all_reports = keep_all_reports
35 self.should_keep_report_dir = os.path.isdir(self.report_dir)
36 self.old_rev = old_rev
37 self.new_rev = new_rev
38 self.mbedtls_modules = ["libmbedcrypto", "libmbedtls", "libmbedx509"]
39 self.old_dumps = {}
40 self.new_dumps = {}
41 self.git_command = "git"
42 self.make_command = "make"
43
44 def check_repo_path(self):
Darryl Greenc47ac262018-03-15 10:12:06 +000045 current_dir = os.path.realpath('.')
46 root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
47 if current_dir != root_dir:
Darryl Green3da15042018-03-01 14:53:49 +000048 raise Exception("Must be run from Mbed TLS root")
49
50 def setup_logger(self):
51 self.log = logging.getLogger()
52 self.log.setLevel(logging.INFO)
53 self.log.addHandler(logging.StreamHandler())
54
55 def check_abi_tools_are_installed(self):
56 for command in ["abi-dumper", "abi-compliance-checker"]:
57 if not shutil.which(command):
58 raise Exception("{} not installed, aborting".format(command))
59
60 def get_clean_worktree_for_git_revision(self, git_rev):
61 self.log.info(
62 "Checking out git worktree for revision {}".format(git_rev)
63 )
64 git_worktree_path = tempfile.mkdtemp()
65 worktree_process = subprocess.Popen(
66 [self.git_command, "worktree", "add", git_worktree_path, git_rev],
67 cwd=self.repo_path,
68 stdout=subprocess.PIPE,
69 stderr=subprocess.STDOUT
70 )
71 worktree_output, _ = worktree_process.communicate()
72 self.log.info(worktree_output.decode("utf-8"))
73 if worktree_process.returncode != 0:
74 raise Exception("Checking out worktree failed, aborting")
75 return git_worktree_path
76
77 def build_shared_libraries(self, git_worktree_path):
78 my_environment = os.environ.copy()
79 my_environment["CFLAGS"] = "-g -Og"
80 my_environment["SHARED"] = "1"
81 make_process = subprocess.Popen(
82 self.make_command,
83 env=my_environment,
84 cwd=git_worktree_path,
85 stdout=subprocess.PIPE,
86 stderr=subprocess.STDOUT
87 )
88 make_output, _ = make_process.communicate()
89 self.log.info(make_output.decode("utf-8"))
90 if make_process.returncode != 0:
91 raise Exception("make failed, aborting")
92
93 def get_abi_dumps_from_shared_libraries(self, git_ref, git_worktree_path):
94 abi_dumps = {}
95 for mbed_module in self.mbedtls_modules:
96 output_path = os.path.join(
97 self.report_dir, "{}-{}.dump".format(mbed_module, git_ref)
98 )
99 abi_dump_command = [
100 "abi-dumper",
101 os.path.join(
102 git_worktree_path, "library", mbed_module + ".so"),
103 "-o", output_path,
104 "-lver", git_ref
105 ]
106 abi_dump_process = subprocess.Popen(
107 abi_dump_command,
108 stdout=subprocess.PIPE,
109 stderr=subprocess.STDOUT
110 )
111 abi_dump_output, _ = abi_dump_process.communicate()
112 self.log.info(abi_dump_output.decode("utf-8"))
113 if abi_dump_process.returncode != 0:
114 raise Exception("abi-dumper failed, aborting")
115 abi_dumps[mbed_module] = output_path
116 return abi_dumps
117
118 def cleanup_worktree(self, git_worktree_path):
119 shutil.rmtree(git_worktree_path)
120 worktree_process = subprocess.Popen(
121 [self.git_command, "worktree", "prune"],
122 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("Worktree cleanup failed, aborting")
130
131 def get_abi_dump_for_ref(self, git_rev):
132 git_worktree_path = self.get_clean_worktree_for_git_revision(git_rev)
133 self.build_shared_libraries(git_worktree_path)
134 abi_dumps = self.get_abi_dumps_from_shared_libraries(
135 git_rev, git_worktree_path
136 )
137 self.cleanup_worktree(git_worktree_path)
138 return abi_dumps
139
140 def get_abi_compatibility_report(self):
141 compatibility_report = ""
142 compliance_return_code = 0
143 for mbed_module in self.mbedtls_modules:
144 output_path = os.path.join(
145 self.report_dir, "{}-{}-{}.html".format(
146 mbed_module, self.old_rev, self.new_rev
147 )
148 )
149 abi_compliance_command = [
150 "abi-compliance-checker",
151 "-l", mbed_module,
152 "-old", self.old_dumps[mbed_module],
153 "-new", self.new_dumps[mbed_module],
154 "-strict",
155 "-report-path", output_path
156 ]
157 abi_compliance_process = subprocess.Popen(
158 abi_compliance_command,
159 stdout=subprocess.PIPE,
160 stderr=subprocess.STDOUT
161 )
162 abi_compliance_output, _ = abi_compliance_process.communicate()
163 self.log.info(abi_compliance_output.decode("utf-8"))
164 if abi_compliance_process.returncode == 0:
165 compatibility_report += (
166 "No compatibility issues for {}\n".format(mbed_module)
167 )
168 if not self.keep_all_reports:
169 os.remove(output_path)
170 elif abi_compliance_process.returncode == 1:
171 compliance_return_code = 1
172 self.should_keep_report_dir = True
173 compatibility_report += (
174 "Compatibility issues found for {}, "
175 "for details see {}\n".format(mbed_module, output_path)
176 )
177 else:
178 raise Exception(
179 "abi-compliance-checker failed with a return code of {},"
180 " aborting".format(abi_compliance_process.returncode)
181 )
182 os.remove(self.old_dumps[mbed_module])
183 os.remove(self.new_dumps[mbed_module])
184 if not self.should_keep_report_dir and not self.keep_all_reports:
185 os.rmdir(self.report_dir)
186 self.log.info(compatibility_report)
187 return compliance_return_code
188
189 def check_for_abi_changes(self):
190 self.check_repo_path()
191 self.check_abi_tools_are_installed()
192 self.old_dumps = self.get_abi_dump_for_ref(self.old_rev)
193 self.new_dumps = self.get_abi_dump_for_ref(self.new_rev)
194 return self.get_abi_compatibility_report()
195
196
197def run_main():
198 try:
199 parser = argparse.ArgumentParser(
200 description=(
201 "This script is a small wrapper around the "
202 "abi-compliance-checker and abi-dumper tools, applying them "
203 "to compare the ABI and API of the library files from two "
204 "different Git revisions within an Mbed TLS repository."
205 " The results of the comparison are formatted as HTML and"
206 " stored at a configurable location. Returns 0 on success, "
207 "1 on ABI/API non-compliance, and 2 if there is an error "
208 "while running the script. # Note: must be run from "
209 "Mbed TLS root."
210 )
211 )
212 parser.add_argument(
213 "-r", "--report_dir", type=str, default="reports",
214 help="directory where reports are stored, default is reports",
215 )
216 parser.add_argument(
217 "-k", "--keep_all_reports", action="store_true",
218 help="keep all reports, even if there are no compatibility issues",
219 )
220 parser.add_argument(
221 "-o", "--old_rev", type=str, help="revision for old version",
222 required=True
223 )
224 parser.add_argument(
225 "-n", "--new_rev", type=str, help="revision for new version",
226 required=True
227 )
228 abi_args = parser.parse_args()
229 abi_check = AbiChecker(
230 abi_args.report_dir, abi_args.old_rev,
231 abi_args.new_rev, abi_args.keep_all_reports
232 )
233 return_code = abi_check.check_for_abi_changes()
234 sys.exit(return_code)
Darryl Greenc47ac262018-03-15 10:12:06 +0000235 except Exception:
236 traceback.print_exc()
Darryl Green3da15042018-03-01 14:53:49 +0000237 sys.exit(2)
238
239
240if __name__ == "__main__":
241 run_main()