Changeset View
Changeset View
Standalone View
Standalone View
helpers/check-abi.py
Show All 23 Lines | |||||
24 | from helperslib.Version import Version | 24 | from helperslib.Version import Version | ||
25 | 25 | | |||
26 | class Library: | 26 | class Library: | ||
27 | def __init__(self, packageName, library): | 27 | def __init__(self, packageName, library): | ||
28 | self.packageName = packageName | 28 | self.packageName = packageName | ||
29 | self.library = library | 29 | self.library = library | ||
30 | self.candidates = [] | 30 | self.candidates = [] | ||
31 | 31 | | |||
32 | def __getitem__(self, key): | ||||
33 | return self.library[key] | ||||
34 | | ||||
35 | @property | ||||
36 | def reportPath(self): | ||||
37 | return "compat_reports/{cmakePackage}_compat_report.html".format(cmakePackage=self['cmakePackage']) | ||||
38 | | ||||
32 | def addCandidate(self, key, entry): | 39 | def addCandidate(self, key, entry): | ||
33 | entry['packageName'] = key | 40 | entry['packageName'] = key | ||
34 | self.candidates.append(entry) | 41 | self.candidates.append(entry) | ||
35 | 42 | | |||
36 | def candidate(self): | 43 | def candidate(self): | ||
37 | """Find the best candidate to check the ABI against.""" | 44 | """Find the best candidate to check the ABI against.""" | ||
38 | candidate = None | 45 | candidate = None | ||
39 | timestamp = self.library["timestamp"] | 46 | timestamp = self.library["timestamp"] | ||
40 | 47 | | |||
41 | if not self.candidates: | 48 | if not self.candidates: | ||
42 | return None | 49 | return None | ||
43 | 50 | | |||
44 | # get a list of tagged candidates | 51 | # get a list of tagged candidates | ||
45 | released = list(filter(lambda i: i['scmRevision'] in HASH2TAG, self.candidates)) | 52 | released = list(filter(lambda i: i['scmRevision'] in HASH2TAG, self.candidates)) | ||
46 | if released: | 53 | if released: | ||
47 | # get the first released version, that is available | 54 | # get the first released version, that is available | ||
48 | candidate = min(released, key=lambda i: HASH2TAG[i['scmRevision']]) | 55 | candidate = min(released, key=lambda i: HASH2TAG[i['scmRevision']]) | ||
49 | logging.info("Found tag %s(%s) to check against.", HASH2TAG[candidate['scmRevision']].version, candidate['scmRevision']) | 56 | candidate['tag'] = HASH2TAG[candidate['scmRevision']] | ||
50 | else: | 57 | else: | ||
51 | #TODO: we may want to return None, as the library was never released so far. | 58 | #TODO: we may want to return None, as the library was never released so far. | ||
52 | 59 | | |||
53 | # get oldest candidate. | 60 | # get oldest candidate. | ||
54 | candidate = min(self.candidates, key=lambda e:e['timestamp']) | 61 | candidate = min(self.candidates, key=lambda e:e['timestamp']) | ||
55 | logging.warning("No released version was found, just use the oldest commit.") | | |||
56 | 62 | | |||
57 | # the candidate needs to be older than the current build. | 63 | # the candidate needs to be older than the current build. | ||
58 | if timestamp < candidate['timestamp']: | 64 | if timestamp < candidate['timestamp']: | ||
59 | return None | 65 | return None | ||
60 | 66 | | |||
61 | return candidate | 67 | return candidate | ||
62 | 68 | | |||
63 | 69 | | |||
70 | class ABICompatibilityResults: | ||||
71 | """Representing the content of abi-compatibility-results.yaml. | ||||
72 | - First add the library via addLibrary | ||||
73 | - Afterwards you can extent the directory via __get__ | ||||
74 | - At the end, create the fia via write | ||||
75 | """ | ||||
76 | def __init__(self): | ||||
77 | self.dict = {} | ||||
78 | | ||||
79 | def __getitem__(self, library): | ||||
80 | return self.dict[library] | ||||
81 | | ||||
82 | def addLibrary(self, library): | ||||
83 | self.dict[library] = {} | ||||
84 | | ||||
85 | def write(self): | ||||
86 | d = {} | ||||
87 | | ||||
88 | for library in self.dict: | ||||
89 | cantidate = library.candidate() | ||||
90 | entry = { | ||||
91 | 'reportPath': library.reportPath, | ||||
92 | 'ownCommit': library['scmRevision'], | ||||
93 | 'otherCommit': candidate['scmRevision'], | ||||
94 | } | ||||
95 | if 'tag' in candidate: | ||||
96 | entry['tag'] = candidate['tag'].version | ||||
97 | entry.update(self[library]) | ||||
98 | d[library['cmakePackage']] = entry | ||||
99 | | ||||
100 | with open('abi-compatibility-results.yaml', 'w') as f: | ||||
101 | f.write(yaml.dump(d, default_flow_style=False)) | ||||
102 | | ||||
64 | def parseACCOutputToDict(stdout): | 103 | def parseACCOutputToDict(stdout): | ||
65 | """Parse output of abi-compliance-checker for further processing and returning a dict. | 104 | """Parse output of abi-compliance-checker for further processing and returning a dict. | ||
66 | extract binary/source compatibility from acc | 105 | extract binary/source compatibility from acc | ||
67 | and calculate a simple bool for the compatibibility. | 106 | and calculate a simple bool for the compatibibility. | ||
68 | """ | 107 | """ | ||
69 | checkBlock = re.compile(br"""^Binary compatibility: (?P<binary>[0-9.]+)%\s* | 108 | checkBlock = re.compile(br"""^Binary compatibility: (?P<binary>[0-9.]+)%\s* | ||
70 | Source compatibility: (?P<source>[0-9.]+)%\s*$""", re.M) | 109 | Source compatibility: (?P<source>[0-9.]+)%\s*$""", re.M) | ||
71 | m = checkBlock.search(stdout).groupdict() | 110 | m = checkBlock.search(stdout).groupdict() | ||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Line(s) | 170 | def updateAccMetadataVersion(entry): | |||
134 | elif entry["accMetadataVersion"] == 2: | 173 | elif entry["accMetadataVersion"] == 2: | ||
135 | return | 174 | return | ||
136 | 175 | | |||
137 | def updateAccMetadataVersion1(entry): | 176 | def updateAccMetadataVersion1(entry): | ||
138 | entry["accMetadataVersion"] = 1 | 177 | entry["accMetadataVersion"] = 1 | ||
139 | entry["cmakePackage"] = entry["libname"] | 178 | entry["cmakePackage"] = entry["libname"] | ||
140 | entry["targets"] = {i:entry["SONAME"] for i in entry["targets"]} | 179 | entry["targets"] = {i:entry["SONAME"] for i in entry["targets"]} | ||
141 | 180 | | |||
181 | # Find all libraries of current git hash. | ||||
142 | for key, entry in ourArchive.serverManifest.items(): | 182 | for key, entry in ourArchive.serverManifest.items(): | ||
143 | updateAccMetadataVersion(entry) | 183 | updateAccMetadataVersion(entry) | ||
144 | try: | 184 | try: | ||
145 | if entry['platform'] != arguments.platform: | 185 | if entry['platform'] != arguments.platform: | ||
146 | continue | 186 | continue | ||
147 | if entry["branchGroup"] != arguments.branchGroup: | 187 | if entry["branchGroup"] != arguments.branchGroup: | ||
148 | continue | 188 | continue | ||
149 | if entry["project"] == arguments.project and entry["scmRevision"] == scmRevision: | 189 | if entry["project"] == arguments.project and entry["scmRevision"] == scmRevision: | ||
150 | libraries.append(Library(key, entry)) | 190 | libraries.append(Library(key, entry)) | ||
151 | except KeyError: | 191 | except KeyError: | ||
152 | continue | 192 | continue | ||
153 | 193 | | |||
154 | if not libraries: | 194 | if not libraries: | ||
155 | if accSettings['NoLibrariesFoundFail']: | 195 | if accSettings['NoLibrariesFoundFail']: | ||
156 | sys.exit("No libraries found and NoLibrariesFoundFail, so we fail hard.") | 196 | sys.exit("No libraries found and NoLibrariesFoundFail, so we fail hard.") | ||
157 | else: | 197 | else: | ||
158 | logging.info("No libraries found.") | 198 | logging.info("No libraries found.") | ||
159 | sys.exit(0) | 199 | sys.exit(0) | ||
160 | 200 | | |||
161 | # Find all availabe reference dumps | 201 | # Find all available reference dumps | ||
162 | # * same cmakePackage | 202 | # * same cmakePackage | ||
163 | # * same SONAME otherwise we have a ABI bump and than it is safe to break ABI | 203 | # * same SONAME, otherwise we have a ABI bump and than it is safe to break ABI | ||
204 | for key, entry in ourArchive.serverManifest.items(): | ||||
205 | if entry['platform'] != arguments.platform: | ||||
206 | continue | ||||
164 | 207 | | |||
165 | for l in libraries: | 208 | # Ignore builds on other branches | ||
166 | cmakePackage = l.library["cmakePackage"] | 209 | if keepBuildGroup and entry["branchGroup"] != arguments.branchGroup: | ||
167 | targets = l.library["targets"] | 210 | continue | ||
168 | soname = max(targets.values()) | 211 | | ||
169 | for key, entry in ourArchive.serverManifest.items(): | 212 | if entry["project"] != arguments.project: | ||
213 | continue | ||||
214 | | ||||
215 | if entry["scmRevision"] == scmRevision: | ||||
216 | continue | ||||
217 | | ||||
218 | for l in libraries: | ||||
219 | cmakePackage = l.library["cmakePackage"] | ||||
220 | targets = l.library["targets"] | ||||
221 | soname = max(targets.values()) | ||||
170 | if key == l.packageName: | 222 | if key == l.packageName: | ||
171 | continue | 223 | break | ||
172 | if entry['platform'] != arguments.platform: | | |||
173 | continue | | |||
174 | 224 | | |||
175 | # We want to search for the cmakePackage | 225 | # We want to search for the cmakePackage | ||
176 | if entry["cmakePackage"] != cmakePackage: | 226 | if entry["cmakePackage"] != cmakePackage: | ||
177 | continue | 227 | continue | ||
178 | 228 | | |||
179 | # Ignore builds on other branches | | |||
180 | if keepBuildGroup and entry["branchGroup"] != arguments.branchGroup: | | |||
181 | continue | | |||
182 | | ||||
183 | # TODO: as we may have bundled multiple libraries in one cmakePackage, | 229 | # TODO: as we may have bundled multiple libraries in one cmakePackage, | ||
184 | # we properly need a smater way. | 230 | # we properly need a smater way. | ||
185 | if max(entry["targets"].values()) == soname: | 231 | if max(entry["targets"].values()) == soname: | ||
186 | l.addCandidate(key, entry) | 232 | l.addCandidate(key, entry) | ||
187 | continue | 233 | break | ||
188 | 234 | | |||
189 | sameSONAME = False | 235 | sameSONAME = False | ||
190 | for name, target in targets.items(): | 236 | for name, target in targets.items(): | ||
191 | try: | 237 | try: | ||
192 | if entry["targets"][name] == target: | 238 | if entry["targets"][name] == target: | ||
193 | sameSONAME = True | 239 | sameSONAME = True | ||
194 | elif entry["targets"][name] > target: | 240 | elif entry["targets"][name] > target: | ||
195 | logging.warning("%s: %s has SONAME = %s, but we searched for SONAME %s", entry['scmRevision'], name, entry["targets"][name], target) | 241 | logging.warning("%s: %s has SONAME = %s, but we searched for SONAME %s", entry['scmRevision'], name, entry["targets"][name], target) | ||
196 | except KeyError: | 242 | except KeyError: | ||
197 | if entry["accMetadataVersion"] == 2: | 243 | if entry["accMetadataVersion"] == 2: | ||
198 | logging.warning("%s: %s is missing.", entry['scmRevision'], name) | 244 | logging.warning("%s: %s is missing.", entry['scmRevision'], name) | ||
199 | 245 | | |||
200 | if sameSONAME: | 246 | if sameSONAME: | ||
201 | l.addCandidate(key, entry) | 247 | l.addCandidate(key, entry) | ||
202 | 248 | | |||
203 | # Check every libraries ABI and do not fail, if one is not fine. | 249 | # Check every libraries ABI and do not fail, if one is not fine. | ||
204 | # Safe the overall retval state | 250 | # Safe the overall retval state | ||
205 | retval = 0 | 251 | retval = 0 | ||
206 | 252 | | |||
207 | # the dictonary that will be written to abi-compatibility-results.yaml | 253 | # the dictonary that will be written to abi-compatibility-results.yaml | ||
208 | resultsYamlFile = {} | 254 | resultsYamlFile = ABICompatibilityResults() | ||
209 | 255 | | |||
210 | for l in libraries: | 256 | for l in libraries: | ||
211 | library = l.library | 257 | library = l.library | ||
212 | cmakePackage = library['cmakePackage'] | 258 | cmakePackage = library['cmakePackage'] | ||
213 | logging.info("Do an ABI check for %s", cmakePackage) | 259 | logging.info("Do an ABI check for %s", cmakePackage) | ||
214 | candidate = l.candidate() | 260 | candidate = l.candidate() | ||
215 | if not candidate: | 261 | if not candidate: | ||
216 | logging.info("Did not found any older build for %s, nothing to check ABI against.", cmakePackage) | 262 | logging.info("Did not found any older build for %s, nothing to check ABI against.", cmakePackage) | ||
217 | continue | 263 | continue | ||
218 | 264 | | |||
265 | logging.info("check %s(old) -> %s(new)", candidate['scmRevision'], library['scmRevision']) | ||||
266 | if 'tag' in candidate: | ||||
267 | logging.info("Found tag %s(%s) to check against.", candidate['tag'].version, candidate['scmRevision']) | ||||
268 | else: | ||||
269 | logging.warning("No released version was found.") | ||||
270 | | ||||
219 | # get the packages, we want to test against each other | 271 | # get the packages, we want to test against each other | ||
220 | newLibraryPath, _ = ourArchive.retrievePackage(l.packageName) | 272 | newLibraryPath, _ = ourArchive.retrievePackage(l.packageName) | ||
221 | oldLibraryPath, _ = ourArchive.retrievePackage(candidate['packageName']) | 273 | oldLibraryPath, _ = ourArchive.retrievePackage(candidate['packageName']) | ||
222 | 274 | | |||
223 | logging.info("check %s(old) -> %s(new)", candidate['scmRevision'], library['scmRevision']) | | |||
224 | | ||||
225 | reportPath = "compat_reports/{cmakePackage}_compat_report.html".format(cmakePackage=cmakePackage) | | |||
226 | | ||||
227 | # Basic result yml information | | |||
228 | yml = { | | |||
229 | 'reportPath': reportPath, | | |||
230 | 'ownCommit': scmRevision, | | |||
231 | 'otherCommit': candidate['scmRevision'], | | |||
232 | } | | |||
233 | resultsYamlFile[cmakePackage] = yml | | |||
234 | 275 | | |||
235 | if candidate['scmRevision'] in HASH2TAG: | 276 | # Add library to results yaml file | ||
236 | yml['tag'] = HASH2TAG[candidate['scmRevision']].version | 277 | resultsYamlFile.addLibrary(l) | ||
237 | 278 | | |||
238 | # check ABI and write compat reports | 279 | # check ABI and write compat reports | ||
239 | cmd = [ | 280 | cmd = [ | ||
240 | "abi-compliance-checker", | 281 | "abi-compliance-checker", | ||
241 | "-report-path", reportPath, | 282 | "-report-path", l.reportPath, | ||
242 | "-l", cmakePackage, | 283 | "-l", cmakePackage, | ||
243 | "--old", oldLibraryPath, | 284 | "--old", oldLibraryPath, | ||
244 | "--new", newLibraryPath, | 285 | "--new", newLibraryPath, | ||
245 | ] | 286 | ] | ||
246 | logging.debug(" ".join(cmd)) | 287 | logging.debug(" ".join(cmd)) | ||
247 | try: | 288 | try: | ||
248 | prog = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 289 | prog = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
249 | except subprocess.CalledProcessError as e: | 290 | except subprocess.CalledProcessError as e: | ||
250 | if e.returncode == 1: # that means that we are not compatible, but still valid output. | 291 | if e.returncode == 1: # ABI not compatible, but still valid output. | ||
251 | logging.warning("abi-compliance-checker exited with 1:\n%s", e.stdout.decode()) | 292 | logging.warning("%s\nabi-compliance-checker exited with 1 (not compatible)", e.stdout.decode()) | ||
252 | 293 | | |||
253 | yml.update(parseACCOutputToDict(e.stdout)) | 294 | resultsYamlFile[l].update(parseACCOutputToDict(e.stdout)) | ||
254 | else: | 295 | else: | ||
255 | logging.error("abi-compliance-checker exited with %s:\nstdout:\n\t%s\nstderr:\n\t%s", e.returncode, e.stdout.decode(), e.stderr.decode()) | 296 | logging.error("abi-compliance-checker exited with %s:\nstdout:\n\t%s\nstderr:\n\t%s", e.returncode, e.stdout.decode(), e.stderr.decode()) | ||
256 | retval = e.returncode | 297 | retval = e.returncode | ||
257 | yml['error'] = e.returncode | 298 | resultsYamlFile[l]['error'] = e.returncode | ||
258 | else: | 299 | else: | ||
259 | logging.debug(prog.stdout.decode()) | 300 | logging.debug(prog.stdout.decode()) | ||
260 | yml.update(parseACCOutputToDict(prog.stdout)) | 301 | resultsYamlFile[l].update(parseACCOutputToDict(prog.stdout)) | ||
261 | 302 | | |||
262 | with open('abi-compatibility-results.yaml', 'w') as f: | 303 | resultsYamlFile.write() | ||
263 | f.write(yaml.dump(resultsYamlFile, default_flow_style=False)) | | |||
264 | 304 | | |||
265 | # We had an issue with one of the ABIs | 305 | # We had an issue with one of the ABIs | ||
266 | if retval != 0 and accSettings['checkABIDumpFailHard']: | 306 | if retval != 0 and accSettings['checkABIDumpFailHard']: | ||
267 | sys.exit("Errors detected and checkABIDumpFailHard is set, so we fail hard.") | 307 | sys.exit("Errors detected and checkABIDumpFailHard is set, so we fail hard.") |