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