Changeset View
Changeset View
Standalone View
Standalone View
helpers/check-abi.py
1 | #!/usr/bin/python3 | 1 | #!/usr/bin/python3 | ||
---|---|---|---|---|---|
2 | 2 | | |||
3 | # check the ABIs against a earlier state. | 3 | # check the ABIs against a earlier state. | ||
4 | # It is designed to run after create-abi-dump has created the abidump already. | 4 | # It is designed to run after create-abi-dump has created the abidump already. | ||
5 | 5 | | |||
6 | # abi-compliance-checker creates a html file with the report. | 6 | # abi-compliance-checker creates a html file with the report. | ||
7 | # it can be multiple libraries in one repository (e.g messagelib) | 7 | # it can be multiple libraries in one repository (e.g messagelib) | ||
8 | # so we have multiple html files for one repository | 8 | # so we have multiple html files for one repository | ||
9 | # in order to store them as artifacts in Jenkins add: | 9 | # in order to store them as artifacts in Jenkins add: | ||
10 | # | 10 | # | ||
11 | # archiveArtifacts artifacts: 'compat_reports/*_compat_reports.html', onlyIfSuccessful: false | 11 | # archiveArtifacts artifacts: 'compat_reports/*_compat_reports.html', onlyIfSuccessful: false | ||
12 | 12 | | |||
13 | import argparse | 13 | import argparse | ||
14 | import collections | 14 | import collections | ||
15 | import decimal | ||||
15 | import logging | 16 | import logging | ||
16 | import os | 17 | import os | ||
18 | import re | ||||
17 | import subprocess | 19 | import subprocess | ||
18 | import sys | 20 | import sys | ||
19 | import time | 21 | import time | ||
22 | import yaml | ||||
20 | 23 | | |||
21 | from helperslib import Packages | 24 | from helperslib import Packages | ||
22 | from helperslib.Version import Version | 25 | from helperslib.Version import Version | ||
23 | 26 | | |||
24 | class Library: | 27 | class Library: | ||
25 | def __init__(self, packageName, library): | 28 | def __init__(self, packageName, library): | ||
26 | self.packageName = packageName | 29 | self.packageName = packageName | ||
27 | self.library = library | 30 | self.library = library | ||
Show All 26 Lines | 51 | else: | |||
54 | 57 | | |||
55 | # the candidate needs to be older than the current build. | 58 | # the candidate needs to be older than the current build. | ||
56 | if timestamp < candidate['timestamp']: | 59 | if timestamp < candidate['timestamp']: | ||
57 | return None | 60 | return None | ||
58 | 61 | | |||
59 | return candidate | 62 | return candidate | ||
60 | 63 | | |||
61 | 64 | | |||
65 | def parseACCOutputToDict(stdout): | ||||
bcooksley: Which programs output are we parsing here?
Some comments indicating what details we're going… | |||||
66 | """Parse output of abi-compliance-checker for further processing and returning a dict. | ||||
67 | extract binary/source compatibility from acc | ||||
68 | and calculate a simple bool for the compatibibility. | ||||
69 | """ | ||||
70 | checkBlock = re.compile(br"""^Binary compatibility: (?P<binary>[0-9.]+)%\s* | ||||
71 | Source compatibility: (?P<source>[0-9.]+)%\s*$""", re.M) | ||||
72 | m = checkBlock.search(stdout).groupdict() | ||||
73 | | ||||
74 | m['binary'] = decimal.Decimal(m['binary'].decode()) | ||||
75 | m['source'] = decimal.Decimal(m['source'].decode()) | ||||
Code Style: 'binaryCompatibility' should start on it's own line (so it lines up with sourceCompatibility) bcooksley: Code Style: 'binaryCompatibility' should start on it's own line (so it lines up with… | |||||
76 | compatibility = m['binary'] == 100 and m['source'] == 100 | ||||
77 | | ||||
78 | return { | ||||
79 | 'binaryCompatibility': float(m['binary']), | ||||
80 | 'sourceCompatibility': float(m['source']), | ||||
81 | 'compatibility': compatibility, | ||||
82 | } | ||||
83 | | ||||
62 | # Make sure logging is ready to go | 84 | # Make sure logging is ready to go | ||
63 | logging.basicConfig(level=logging.DEBUG) | 85 | logging.basicConfig(level=logging.DEBUG) | ||
64 | 86 | | |||
65 | # Parse the command line arguments we've been given | 87 | # Parse the command line arguments we've been given | ||
66 | parser = argparse.ArgumentParser(description='Utility to check ABI.') | 88 | parser = argparse.ArgumentParser(description='Utility to check ABI.') | ||
67 | parser.add_argument('--project', type=str, required=True) | 89 | parser.add_argument('--project', type=str, required=True) | ||
68 | parser.add_argument('--branchGroup', type=str, required=True) | 90 | parser.add_argument('--branchGroup', type=str, required=True) | ||
69 | parser.add_argument('--platform', type=str, required=True) | 91 | parser.add_argument('--platform', type=str, required=True) | ||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Line(s) | 181 | elif entry['SONAME'] > soname: | |||
161 | if keepBuildGroup and entry["branchGroup"] != arguments.branchGroup: | 183 | if keepBuildGroup and entry["branchGroup"] != arguments.branchGroup: | ||
162 | continue | 184 | continue | ||
163 | logging.warning("We searched for SONAME = %s, but found a newer SONAME = %s in the builds, that should not happen, as SONAMEs should only rise and never go lower!", soname, entry['SONAME']) | 185 | logging.warning("We searched for SONAME = %s, but found a newer SONAME = %s in the builds, that should not happen, as SONAMEs should only rise and never go lower!", soname, entry['SONAME']) | ||
164 | 186 | | |||
165 | # Check every libraries ABI and do not fail, if one is not fine. | 187 | # Check every libraries ABI and do not fail, if one is not fine. | ||
166 | # Safe the overall retval state | 188 | # Safe the overall retval state | ||
167 | retval = 0 | 189 | retval = 0 | ||
168 | 190 | | |||
191 | # the dictonary that will be written to abi-compatibility-results.yaml | ||||
192 | resultsYamlFile = {} | ||||
193 | | ||||
169 | for l in libraries: | 194 | for l in libraries: | ||
170 | library = l.library | 195 | library = l.library | ||
171 | libname = library['libname'] | 196 | libname = library['libname'] | ||
172 | logging.info("Do an ABI check for %s", libname) | 197 | logging.info("Do an ABI check for %s", libname) | ||
173 | candidate = l.candidate() | 198 | candidate = l.candidate() | ||
174 | if not candidate: | 199 | if not candidate: | ||
175 | logging.info("Did not found any older build for %s, nothing to check ABI against.",libname) | 200 | logging.info("Did not found any older build for %s, nothing to check ABI against.",libname) | ||
176 | continue | 201 | continue | ||
177 | 202 | | |||
178 | # get the packages, we want to test against each other | 203 | # get the packages, we want to test against each other | ||
179 | newLibraryPath, _ = ourArchive.retrievePackage(l.packageName) | 204 | newLibraryPath, _ = ourArchive.retrievePackage(l.packageName) | ||
180 | oldLibraryPath, _ = ourArchive.retrievePackage(candidate['packageName']) | 205 | oldLibraryPath, _ = ourArchive.retrievePackage(candidate['packageName']) | ||
181 | 206 | | |||
182 | logging.info("check %s(old) -> %s(new)", candidate['scmRevision'], library['scmRevision']) | 207 | logging.info("check %s(old) -> %s(new)", candidate['scmRevision'], library['scmRevision']) | ||
183 | 208 | | |||
209 | reportPath = "compat_reports/{libname}_compat_report.html".format(libname=libname) | ||||
210 | | ||||
211 | # Basic result yml information | ||||
bcooksley: Code Style: Same as above. | |||||
212 | yml = { | ||||
213 | 'reportPath': reportPath, | ||||
214 | 'ownCommit': scmRevision, | ||||
215 | 'otherCommit': candidate['scmRevision'], | ||||
216 | } | ||||
217 | resultsYamlFile[libname] = yml | ||||
218 | | ||||
219 | if candidate['scmRevision'] in HASH2TAG: | ||||
220 | yml['tag'] = HASH2TAG[candidate['scmRevision']].version | ||||
221 | | ||||
184 | # check ABI and write compat reports | 222 | # check ABI and write compat reports | ||
185 | cmd = ["abi-compliance-checker", | 223 | cmd = [ | ||
186 | "-report-path", "compat_reports/{libname}_compat_report.html".format(libname=libname), | 224 | "abi-compliance-checker", | ||
225 | "-report-path", reportPath, | ||||
187 | "-l", libname, | 226 | "-l", libname, | ||
188 | "--old", oldLibraryPath, | 227 | "--old", oldLibraryPath, | ||
189 | "--new", newLibraryPath] | 228 | "--new", newLibraryPath, | ||
190 | ret = subprocess.call(cmd) | 229 | ] | ||
191 | 230 | logging.debug(" ".join(cmd)) | |||
192 | if ret != 0: | 231 | try: | ||
193 | logging.error("abi-compliance-checker exited with %s", ret) | 232 | prog = subprocess.run(cmd, check=True, capture_output=True) | ||
194 | retval = ret | 233 | except subprocess.CalledProcessError as e: | ||
234 | if e.returncode == 1: # that means that we are not compatible, but still valid output. | ||||
235 | logging.warning("abi-compliance-checker exited with 1:\n%s", prog.stdout.decode()) | ||||
236 | | ||||
237 | yml.update(parseACCOutputToDict(e.stdout)) | ||||
238 | else: | ||||
239 | logging.error("abi-compliance-checker exited with %s:\nstdout:\n\ŧ%s\nstderr:\n\t%s", e.returncode, e.stdout.decode(), e.stderr.decode()) | ||||
240 | retval = e.returncode | ||||
241 | yml['error'] = e.returncode | ||||
242 | else: | ||||
243 | logging.debug(prog.stdout.decode()) | ||||
244 | yml.update(parseACCOutputToDict(prog.stdout)) | ||||
245 | | ||||
246 | with open('abi-compatibility-results.yaml', 'w') as f: | ||||
247 | f.write(yaml.dump(resultsYamlFile, default_flow_style=False)) | ||||
195 | 248 | | |||
196 | # We had an issue with one of the ABIs | 249 | # We had an issue with one of the ABIs | ||
197 | if retval != 0: | 250 | if retval != 0: | ||
198 | sys.exit(retval) | 251 | sys.exit(retval) |
Which programs output are we parsing here?
Some comments indicating what details we're going to extract from the output would also be nice for future reference.