diff --git a/archive-configs/production.yaml b/archive-configs/production.yaml --- a/archive-configs/production.yaml +++ b/archive-configs/production.yaml @@ -16,3 +16,4 @@ 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" diff --git a/helpers/check-abi.py b/helpers/check-abi.py new file mode 100755 --- /dev/null +++ b/helpers/check-abi.py @@ -0,0 +1,103 @@ +#!/usr/bin/python3 +import os +import re +import logging +import pathlib +import argparse +import tempfile +import subprocess +from collections import defaultdict +from typing import Dict, List, Union + +from helperslib import Packages, EnvironmentHandler + +# Make sure logging is ready to go +logging.basicConfig(level=logging.DEBUG) + + +# 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('--environment', type=str, required=True) +parser.add_argument('--branchGroup', 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() + +# Find all libaries, that are build with the same git commit +# TODO: if we have project in the metadata, we should check that, too. Otherwise we do strange things if we have the same git hashes in different repositories. +libraries = [] + +for key, entry in ourArchive.serverManifest.items(): + if "scmRevision" in entry and entry["scmRevision"] == scmRevision: + entry["key"] = key + libraries.append(entry) + +# Find the reference dump +# the reference dump, needs to have: +# * same libname +# * same SONAME otherwise we have a ABI bump and than it is safe to break ABI +# * lowest timestamp possible, ABI should not change the whole life of a library's SONAME +# TODO: The interessting part is, how to handle the pre release versions when the stable branch get's updated but it is not released. + +referenceLibraries = {i['libname']:i for i in libraries} +for library in libraries: + libname = library["libname"] + soname = library["SONAME"] + for key, entry in ourArchive.serverManifest.items(): + # We whant to so search for the library + if "libname" in entry and entry["libname"] == libname: + # only interessed, for builds with the same SONAME + if entry['SONAME'] == soname: + # find the oldest build + if entry["timestamp"] < referenceLibraries[libname]["timestamp"]: + entry["key"] = key + referenceLibraries[libname] = entry + if entry['SONAME'] > soname: + # TODO: if we are in a stable build only check for entries in stable branch + 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'])) + +for library in libraries: + libname = library["libname"] + if referenceLibraries[libname]['timestamp'] == library["timestamp"]: + logging.info("Did not found any older build for {}, nothing to check ABI against.".format(libname)) + continue + + # get the packages, we want to test against each other + newLibraryPath, _ = ourArchive.retrievePackage(library['key']) + oldLibraryPath, _ = ourArchive.retrievePackage(referenceLibraries[libname]['key']) + + # abi-compliance-checker is irritated by files that does not have a abidump/dump suffix + # let's create symlinks to make abi-compliance-checker happy + # TODO: can be removed, if Package.Archive is able to store .dump files as those + os.symlink(oldLibraryPath, "old.abidump") + os.symlink(newLibraryPath, "new.abidump") + + logging.info("Let's do a ABI check {} against {}".format(library['scmRevision'], referenceLibraries[libname]['scmRevision'])) + + # let's check the ABI + cmd = ["abi-compliance-checker", "-l", libname, + "--old", "old.abidump", + "--new", "new.abidump"] + subprocess.check_call(cmd) + + # TODO: can be removed, if Package.Archive is able to store .dump files as those + os.remove("old.abidump") + os.remove("new.abidump") + + # abi-compliance-checker creates a html file with the report. + # it can be multiple libraries in one repository (see messagelib) + # v1 = referenceLibraries[libname]['version'] + # v2 = library['version'] + # ./compat_reports/{libname}/{v1}_to_{v2}/compat_report.html