Newer
Older
#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
#
# Copyright © 2022 Collabora Ltd
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
apertis-abi-compare compares ABI/API between two versions of a package
using abi-compliance-checker and XML descriptor files provided in packages.
If XML descriptor files are not provided, it will try to generate ones based
on the files installed.
apertis-abi-compare takes as input only a path to a folder containing .deb and
a .dsc files. It will download from apt repository the previous version to
compare ABI/API.
Currently, apertis-abi-compare only handles single library packages.
"""
import argparse
from debian import deb822, debfile
import os
import pathlib
import re
import subprocess
import sys
def extract_debs(input_folder, output_folder):
list_file = list(pathlib.Path(input_folder).glob('**/*.deb'))
subprocess.run(['/usr/bin/dpkg-deb',
'-x', file, output_folder], check=True)
builddeps = builddeps.split(",")
# Virtual packages not needed
builddeps_to_remove = ["debhelper-compat","dh-sequence-python3"]
builddeps = [x for x in builddeps if x not in builddeps_to_remove]
return builddeps
xml_descr = list(pathlib.Path(pkgs_path).glob('**/abi-descriptor.xml'))
if len(xml_descr)>1:
sys.exit('ERROR: several abi-descriptor.xml detected!\n' +
'ERROR: apertis-abi-compare only works with single library packages')
else:
print(f'INFO: Using XML descriptor: {xml_descr}')
else:
print('WARNING: XML descriptor not found')
print('WARNING: Trying to generate an XML descriptor')
xml_descr = os.path.join(pkgs_path,'autogen_xml_descriptor.xml')
my_headers = list(pathlib.Path(pkgs_path).glob('usr/include/**/*.h'))
my_headers = list(map(str, my_headers))
my_libs = list(pathlib.Path(pkgs_path).glob('**/*.so'))
my_libs = list(map(str, my_libs))
generate_xml(xml_descr, version, my_headers, my_libs)
print(f'INFO: XML descriptor generated: {xml_descr}')
def generate_xml(xml, version, headers, libs):
root = ET.Element("ABI_Descriptor")
ET.SubElement(root, "version").text = '\n'+version+'\n'
ET.SubElement(root, "headers").text = '\n'+'\n'.join(headers)+'\n'
ET.SubElement(root, "libs").text = '\n'+'\n'.join(libs)+'\n'
tree = ET.ElementTree(root)
ET.indent(tree, space="", level=0)
tree.write(xml, encoding="utf-8")
my_lib = list(pathlib.Path(path).glob('**/*.so'))
if len(my_lib)>1:
print('WARNING: several libraries detected!\n' +
'WARNING: only the first one will be analyzed.')
else:
print('INFO: one library detected!')
my_lib = sorted(my_lib)[0]
my_lib = my_lib.removeprefix("lib").removesuffix(".so")
def run_abi_checker(lib, xml_old, ver_old, xml_new, ver_new):
print(f'INFO: Running abi-compliance-checker to compare {lib}:')
print(f'INFO: - {ver_old} {xml_old}')
print(f'INFO: - {ver_new} {xml_new}')
subprocess.run(['/usr/bin/abi-compliance-checker',
'-lib', lib,
'-old', xml_old, '-v1', ver_old,
'-new', xml_new, '-v2', ver_new]
)
parser = argparse.ArgumentParser(description='Check for ABI/API breakage between a new version and a former one')
parser.add_argument('folder_bin', type=str, help='path to the deb binaries including a .dsc file')
acc_exists = os.path.isfile('/usr/bin/abi-compliance-checker')
sys.exit('ERROR: abi-compliance-checker not found in /usr/bin/\n' +
'ERROR: abi-compliance-checker can be installed with: apt install abi-compliance-checker')
# Extract debs of new version
extract_debs(folder_bin, 'pkg_new_extracted')
# Read dsc of new version to retrieve old bin from apt
file_dsc = list(pathlib.Path(folder_bin).glob('*.dsc'))
if len(file_dsc)!=1:
sys.exit('ERROR: input folder must contain only one .dsc file!\n' +
'ERROR: ' + folder_bin + ' contains ' + str(len(file_dsc)) + ' .dsc file(s).')
else:
file_dsc = file_dsc[0]
my_new_ver = my_dsc['Version']
bin_deb_gen = my_dsc['Binary']
my_builddeps = clean_build_deps(my_dsc['Build-Depends'])
print("INFO: abi-compliance-checker requires having Build-Deps installed")
print("INFO: Trying to install them now:")
print(my_builddeps)
subprocess.run(['sudo','/usr/bin/apt','install'] + my_builddeps)
# Download debs of old version
list_bin_deb = list(bin_deb_gen.split(", "))
folder_old_bin = 'pkg_binaries_old'
os.mkdir(folder_old_bin)
for bin_deb in list_bin_deb:
print(bin_deb)
subprocess.run(['/usr/bin/apt','download', bin_deb], cwd=folder_old_bin)
my_old_deb = list(pathlib.Path(folder_old_bin).glob('*.deb'))[0]
my_old_deb = debfile.DebFile(my_old_deb)
my_old_debcontrol = my_old_deb.debcontrol()
my_old_ver = my_old_debcontrol['Version']
# Extract debs of old version
extract_debs(folder_old_bin, 'pkg_old_extracted')
# Check in xml descriptor is available otherwise generate one
xml_descr_old = define_xml('pkg_old_extracted', my_old_ver)
xml_descr_new = define_xml('pkg_new_extracted', my_new_ver)
run_abi_checker(lib, xml_descr_old, my_old_ver, xml_descr_new, my_new_ver)