Commit ee33ecb4 authored by Sandro Knauß's avatar Sandro Knauß Committed by Ben Cooksley

Add script to check the ABI of the created libraries.

Differential Revision:
parent ff39367f
......@@ -16,3 +16,4 @@ cacheLocation:
SUSEQt5.11: "/srv/archives/production/SUSEQt5.11/"
FreeBSDQt5.11: "/usr/home/jenkins/archives/production/"
AndroidQt5.11: "/srv/archives/production/AndroidQt5.11/"
ABIReference: "/srv/archives/production/ABIReference"
......@@ -16,3 +16,4 @@ cacheLocation:
SUSEQt5.11: "/srv/archives/sandbox/SUSEQt5.11/"
FreeBSDQt5.11: "/usr/home/jenkins/archives/sandbox/"
AndroidQt5.11: "/srv/archives/sandbox/AndroidQt5.11/"
ABIReference: "/srv/archives/sandbox/ABIReference/"
# check the ABIs against a earlier state.
# It is designed to run after create-abi-dump has created the abidump already.
# abi-compliance-checker creates a html file with the report.
# it can be multiple libraries in one repository (e.g messagelib)
# so we have multiple html files for one repository
# in order to store them as artifacts in Jenkins add:
# archiveArtifacts artifacts: 'compat_reports/*_compat_reports.html', onlyIfSuccessful: false
import os
import logging
import argparse
import subprocess
import sys
from helperslib import Packages
from helperslib.Version import Version
class Library:
def __init__(self, packageName, library):
self.packgeName = packageName
self.library = library
self.candidates = []
def addCandidate(self, key, entry):
entry['packageName'] = key
def candidate(self):
"""Find the best candidate to check the ABI against."""
candidate = None
timestamp = self.library["timestamp"]
if not self.candidates:
return None
# get a list of tagged candidates
released = list(filter(lambda i: i['scmRevision'] in HASH2TAG, self.candidates))
if released:
# get the first released version, that is available
candidate = min(released, key=lambda i: HASH2TAG[i['scmRevision']])
#TODO: we may want to return None, as the library was never released so far.
# get oldest candidate.
candidate = min(self.candidates, key=lambda e:e['timestamp'])
logging.warning("No released version was found, just use the oldest commit.")
# the candidate needs to be older than the current build.
if timestamp > candidate['timestamp']:
return None
return candidate
# Make sure logging is ready to go
# Parse the command line arguments we've been given
parser = argparse.ArgumentParser(description='Utility to check ABI.')
parser.add_argument('--project', type=str, required=True)
parser.add_argument('--branchGroup', type=str, required=True)
parser.add_argument('--platform', type=str, required=True)
parser.add_argument('--environment', type=str, required=True)
arguments = parser.parse_args()
# Initialize the archive manager
ourArchive = Packages.Archive(arguments.environment, 'ABIReference', usingCache = True, contentsSuffix = ".abidump")
# Determine which SCM revision we are storing
# This will be embedded into the package metadata which might help someone doing some debugging
# GIT_COMMIT is set by Jenkins Git plugin, so we can rely on that for most of our builds
scmRevision = ''
if os.getenv('GIT_COMMIT') != '':
scmRevision = os.getenv('GIT_COMMIT')
if not scmRevision:
scmRevision = subprocess.check_output(["git", "log", "--format=%H", "-n 1", "HEAD"]).strip().decode()
# get all tags that are in the current commit
tags = subprocess.check_output(["git", "tag", "--contains", scmRevision]).strip().decode().splitlines()
# resolve tags -> git hashes
taghashes = subprocess.check_output(["git", "rev-parse", *tags]).strip().decode().splitlines()
HASH2TAG = {taghashes[pos]:Version(tag) for pos, tag in enumerate(tags)}
# Do we want to check for newer SONAMEs on other buildGroups
keepBuildGroup = False
if arguments.branchGroup != "kf5-qt5":
keepBuildGroup = True
# Find all libraries, that are build with the same git commit
libraries = []
for key, entry in ourArchive.serverManifest.items():
if entry["project"] == arguments.project and entry["scmRevision"] == scmRevision:
except KeyError:
# Find all availabe reference dumps
# * same libname
# * same SONAME otherwise we have a ABI bump and than it is safe to break ABI
for l in libraries:
libname = l.library["libname"]
soname = l.library["SONAME"]
for key, entry in ourArchive.serverManifest.items():
if key == l.packageName:
if entry['platform'] != arguments.platform:
# We want to search for the library
if entry["libname"] == libname:
# only interested, for builds with the same SONAME
if entry['SONAME'] == soname:
l.addCandidate(key, entry)
elif entry['SONAME'] > soname:
# Ignore new SONAMEs on other branchGroups.
if keepBuildGroup and entry["branchGroup"] != arguments.branchGroup:
logging.warning("We searched for SONAME = {}, but found a newer SONAME = {} in the builds, that should not happen, as SONAMEs should only rise and never go lower!".format(soname, entry['SONAME']))
# Check every libraries ABI and do not fail, if one is not fine.
# Safe the overall retval state
retval = 0
for l in libraries:
library = l.library
libname = library['libname']
candidate = l.candidate()
if not candidate:"Did not found any older build for {}, nothing to check ABI against.",libname)
# get the packages, we want to test against each other
newLibraryPath, _ = ourArchive.retrievePackage(l.packageName)
oldLibraryPath, _ = ourArchive.retrievePackage(candidate['packageName'])"Let's do a ABI check {} against {}", library['scmRevision'], candidate['scmRevision'])
# check ABI and write compat reports
cmd = ["abi-compliance-checker",
"-report-path", "$WORKSPACE/compat_reports/{libname}_compat_report.html".format(libname=libname),
"-l", libname,
"--old", oldLibraryPath,
"--new", newLibraryPath]
ret =
if ret != 0:
logging.error("abi-compliance-checker exited with {ret}", ret=ret)
retval = ret
# We had an issue with one of the ABIs
if retval != 0:
import functools
from typing import List
''' To run doctests for this module.
python3 -m doctest -v helpers/helperslib/
"alpha": 70,
"a": 70,
"beta": 80,
"b": 80,
"rc": 90
class Version:
'''class for Version and correct ordering. '''
def __init__(self, version: str) -> None:
if version.startswith("v"):
self.version = version[1:]
self.version = version
self._parts = None
self._suffix = None
def parts(self) -> List:
'''returns a list with different parts of version.
>>> Version("1.2.3-rc1").parts()
[1, 2, 3]
if self._parts:
return self._parts
ps = self.version.split(".")
self._parts = []
for p in ps:
except ValueError:
v, suffix = p.split("-")
self._suffix = suffix
return self._parts
def suffix(self) -> int:
'''transforms suffix into integer using SUFFIX2NUMBER.
>>> Version("1.2-alpha").suffix()
>>> Version("1.2-beta").suffix()
>>> Version("1.2-rc").suffix()
>>> Version("1.2-rc1").suffix()
if not self._parts:
#no suffix so we are higher than any suffix
if not self._suffix:
return 100
for key, base in SUFFIX2NUMBER.items():
if self._suffix.startswith(key):
addition = 0
# handle case with rc1 or beta2 etc.
if len(self._suffix) > len(key):
addition = int(self._suffix[len(key):])
return base + addition
def __eq__(self, other):
'''returns True if versions are the same.
>>> Version("1.2") == "1.2"
The KDE logic that 5.3.90 == "5.4-rc" is not implemented!
>>> Version("5.4-rc") == Version("5.3.90")
if hasattr(other, "version"):
return self.version == other.version
elif type(other) == str:
return self == Version(other)
def __lt__(self, other):
'''returns True if self < other.
>>> Version("1.2.3-rc1") < Version("1.2.3")
>>> Version("1.2.3") < Version("1.2.3-rc1")
>>> Version("1.2.3") < Version("1.2.4")
>>> Version("1.2.3") < Version("1.2.3")
>>> Version("1.2") < Version("1.2.3")
>>> Version("1.2.3") < Version("1.2")
The KDE logic that 5.3.89 < "5.4-rc" < 5.3.91 is not implemented!
>>> Version("5.3.89") < "5.4-rc"
>>> Version("5.4-rc") < "5.3.91"
>>> Version("5.3.89") < "5.3.91"
if type(other) == str:
return self < Version(other)
if hasattr(other, "parts"):
parts =
otherParts =
for i, part in enumerate(parts):
if part == otherParts[i]:
return part < otherParts[i]
except IndexError:
# @other has less parts than @self.
return False
if len(parts) < len(otherParts):
return True
return self.suffix() < other.suffix()
def __repr__(self):
parts =
version = ".".join([str(i) for i in parts])
if self._suffix:
version += "-{}".format(self._suffix)
return "<{}>".format(version)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment