blob: fe5dd3f21cde5bdbc0dd51597c91f5a6c3a115ee [file] [log] [blame]
Darryl Green3da15042018-03-01 14:53:49 +00001#!/usr/bin/env python3
Darryl Green4cd7a9b2018-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 Green31321ca2018-04-16 12:02:29 +010015Note: must be run from Mbed TLS root.
Darryl Green4cd7a9b2018-04-06 11:23:22 +010016"""
Darryl Green3da15042018-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 Greenc47ac262018-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 Green3da15042018-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(
Jaeden Amero5857c2f2018-11-02 16:22:37 +000067 [self.git_command, "worktree", "add", "--detach", git_worktree_path, git_rev],
Darryl Green3da15042018-03-01 14:53:49 +000068 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
Jaeden Amero346f9592018-11-02 16:35:09 +000078 def update_git_submodules(self, git_worktree_path):
79 process = subprocess.Popen(
80 [self.git_command, "submodule", "update", "--init", '--recursive'],
81 cwd=git_worktree_path,
82 stdout=subprocess.PIPE,
83 stderr=subprocess.STDOUT
84 )
85 output, _ = process.communicate()
86 self.log.info(output.decode("utf-8"))
87 if process.returncode != 0:
88 raise Exception("git submodule update failed, aborting")
89
Darryl Green3da15042018-03-01 14:53:49 +000090 def build_shared_libraries(self, git_worktree_path):
91 my_environment = os.environ.copy()
92 my_environment["CFLAGS"] = "-g -Og"
93 my_environment["SHARED"] = "1"
94 make_process = subprocess.Popen(
95 self.make_command,
96 env=my_environment,
97 cwd=git_worktree_path,
98 stdout=subprocess.PIPE,
99 stderr=subprocess.STDOUT
100 )
101 make_output, _ = make_process.communicate()
102 self.log.info(make_output.decode("utf-8"))
103 if make_process.returncode != 0:
104 raise Exception("make failed, aborting")
105
106 def get_abi_dumps_from_shared_libraries(self, git_ref, git_worktree_path):
107 abi_dumps = {}
108 for mbed_module in self.mbedtls_modules:
109 output_path = os.path.join(
110 self.report_dir, "{}-{}.dump".format(mbed_module, git_ref)
111 )
112 abi_dump_command = [
113 "abi-dumper",
114 os.path.join(
115 git_worktree_path, "library", mbed_module + ".so"),
116 "-o", output_path,
117 "-lver", git_ref
118 ]
119 abi_dump_process = subprocess.Popen(
120 abi_dump_command,
121 stdout=subprocess.PIPE,
122 stderr=subprocess.STDOUT
123 )
124 abi_dump_output, _ = abi_dump_process.communicate()
125 self.log.info(abi_dump_output.decode("utf-8"))
126 if abi_dump_process.returncode != 0:
127 raise Exception("abi-dumper failed, aborting")
128 abi_dumps[mbed_module] = output_path
129 return abi_dumps
130
131 def cleanup_worktree(self, git_worktree_path):
132 shutil.rmtree(git_worktree_path)
133 worktree_process = subprocess.Popen(
134 [self.git_command, "worktree", "prune"],
135 cwd=self.repo_path,
136 stdout=subprocess.PIPE,
137 stderr=subprocess.STDOUT
138 )
139 worktree_output, _ = worktree_process.communicate()
140 self.log.info(worktree_output.decode("utf-8"))
141 if worktree_process.returncode != 0:
142 raise Exception("Worktree cleanup failed, aborting")
143
144 def get_abi_dump_for_ref(self, git_rev):
145 git_worktree_path = self.get_clean_worktree_for_git_revision(git_rev)
Jaeden Amero346f9592018-11-02 16:35:09 +0000146 self.update_git_submodules(git_worktree_path)
Darryl Green3da15042018-03-01 14:53:49 +0000147 self.build_shared_libraries(git_worktree_path)
148 abi_dumps = self.get_abi_dumps_from_shared_libraries(
149 git_rev, git_worktree_path
150 )
151 self.cleanup_worktree(git_worktree_path)
152 return abi_dumps
153
154 def get_abi_compatibility_report(self):
155 compatibility_report = ""
156 compliance_return_code = 0
157 for mbed_module in self.mbedtls_modules:
158 output_path = os.path.join(
159 self.report_dir, "{}-{}-{}.html".format(
160 mbed_module, self.old_rev, self.new_rev
161 )
162 )
163 abi_compliance_command = [
164 "abi-compliance-checker",
165 "-l", mbed_module,
166 "-old", self.old_dumps[mbed_module],
167 "-new", self.new_dumps[mbed_module],
168 "-strict",
169 "-report-path", output_path
170 ]
171 abi_compliance_process = subprocess.Popen(
172 abi_compliance_command,
173 stdout=subprocess.PIPE,
174 stderr=subprocess.STDOUT
175 )
176 abi_compliance_output, _ = abi_compliance_process.communicate()
177 self.log.info(abi_compliance_output.decode("utf-8"))
178 if abi_compliance_process.returncode == 0:
179 compatibility_report += (
180 "No compatibility issues for {}\n".format(mbed_module)
181 )
182 if not self.keep_all_reports:
183 os.remove(output_path)
184 elif abi_compliance_process.returncode == 1:
185 compliance_return_code = 1
186 self.should_keep_report_dir = True
187 compatibility_report += (
188 "Compatibility issues found for {}, "
189 "for details see {}\n".format(mbed_module, output_path)
190 )
191 else:
192 raise Exception(
193 "abi-compliance-checker failed with a return code of {},"
194 " aborting".format(abi_compliance_process.returncode)
195 )
196 os.remove(self.old_dumps[mbed_module])
197 os.remove(self.new_dumps[mbed_module])
198 if not self.should_keep_report_dir and not self.keep_all_reports:
199 os.rmdir(self.report_dir)
200 self.log.info(compatibility_report)
201 return compliance_return_code
202
203 def check_for_abi_changes(self):
204 self.check_repo_path()
205 self.check_abi_tools_are_installed()
206 self.old_dumps = self.get_abi_dump_for_ref(self.old_rev)
207 self.new_dumps = self.get_abi_dump_for_ref(self.new_rev)
208 return self.get_abi_compatibility_report()
209
210
211def run_main():
212 try:
213 parser = argparse.ArgumentParser(
214 description=(
Darryl Green31321ca2018-04-16 12:02:29 +0100215 """This script is a small wrapper around the
216 abi-compliance-checker and abi-dumper tools, applying them
217 to compare the ABI and API of the library files from two
218 different Git revisions within an Mbed TLS repository.
219 The results of the comparison are formatted as HTML and stored
220 at a configurable location. Returns 0 on success, 1 on ABI/API
221 non-compliance, and 2 if there is an error while running the
222 script. Note: must be run from Mbed TLS root."""
Darryl Green3da15042018-03-01 14:53:49 +0000223 )
224 )
225 parser.add_argument(
Darryl Green31321ca2018-04-16 12:02:29 +0100226 "-r", "--report-dir", type=str, default="reports",
Darryl Green3da15042018-03-01 14:53:49 +0000227 help="directory where reports are stored, default is reports",
228 )
229 parser.add_argument(
Darryl Green31321ca2018-04-16 12:02:29 +0100230 "-k", "--keep-all-reports", action="store_true",
Darryl Green3da15042018-03-01 14:53:49 +0000231 help="keep all reports, even if there are no compatibility issues",
232 )
233 parser.add_argument(
Darryl Green31321ca2018-04-16 12:02:29 +0100234 "-o", "--old-rev", type=str, help="revision for old version",
Darryl Green3da15042018-03-01 14:53:49 +0000235 required=True
236 )
237 parser.add_argument(
Darryl Green31321ca2018-04-16 12:02:29 +0100238 "-n", "--new-rev", type=str, help="revision for new version",
Darryl Green3da15042018-03-01 14:53:49 +0000239 required=True
240 )
241 abi_args = parser.parse_args()
242 abi_check = AbiChecker(
243 abi_args.report_dir, abi_args.old_rev,
244 abi_args.new_rev, abi_args.keep_all_reports
245 )
246 return_code = abi_check.check_for_abi_changes()
247 sys.exit(return_code)
Darryl Greenc47ac262018-03-15 10:12:06 +0000248 except Exception:
249 traceback.print_exc()
Darryl Green3da15042018-03-01 14:53:49 +0000250 sys.exit(2)
251
252
253if __name__ == "__main__":
254 run_main()