1e01aa904Sopenharmony_ci#!/usr/bin/env python 2e01aa904Sopenharmony_ci# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 3e01aa904Sopenharmony_ci# -*- coding: utf-8 -*- 4e01aa904Sopenharmony_ci# -*- Mode: Python 5e01aa904Sopenharmony_ci# 6e01aa904Sopenharmony_ci# Copyright (C) 2013-2016 Red Hat, Inc. 7e01aa904Sopenharmony_ci# 8e01aa904Sopenharmony_ci# Author: Chenxiong Qi 9e01aa904Sopenharmony_ci 10e01aa904Sopenharmony_cifrom __future__ import print_function 11e01aa904Sopenharmony_ci 12e01aa904Sopenharmony_ciimport argparse 13e01aa904Sopenharmony_ciimport functools 14e01aa904Sopenharmony_ciimport glob 15e01aa904Sopenharmony_ciimport logging 16e01aa904Sopenharmony_ciimport mimetypes 17e01aa904Sopenharmony_ciimport os 18e01aa904Sopenharmony_ciimport re 19e01aa904Sopenharmony_ciimport shutil 20e01aa904Sopenharmony_ciimport six 21e01aa904Sopenharmony_ciimport subprocess 22e01aa904Sopenharmony_ciimport sys 23e01aa904Sopenharmony_ci 24e01aa904Sopenharmony_cifrom collections import namedtuple 25e01aa904Sopenharmony_cifrom itertools import chain 26e01aa904Sopenharmony_ci 27e01aa904Sopenharmony_ciimport xdg.BaseDirectory 28e01aa904Sopenharmony_ci 29e01aa904Sopenharmony_ciimport rpm 30e01aa904Sopenharmony_ciimport koji 31e01aa904Sopenharmony_ci 32e01aa904Sopenharmony_ci# @file 33e01aa904Sopenharmony_ci# 34e01aa904Sopenharmony_ci# You might have known that abipkgdiff is a command line tool to compare two 35e01aa904Sopenharmony_ci# RPM packages to find potential differences of ABI. This is really useful for 36e01aa904Sopenharmony_ci# Fedora packagers and developers. Usually, excpet the RPM packages built 37e01aa904Sopenharmony_ci# locally, if a packager wants to compare RPM packages he just built with 38e01aa904Sopenharmony_ci# specific RPM packages that were already built and availabe in Koji, 39e01aa904Sopenharmony_ci# fedabipkgdiff is the right tool for him. 40e01aa904Sopenharmony_ci# 41e01aa904Sopenharmony_ci# With fedabipkgdiff, packager is able to specify certain criteria to tell 42e01aa904Sopenharmony_ci# fedabipkgdiff which RPM packages he wants to compare, then fedabipkgdiff will 43e01aa904Sopenharmony_ci# find them, download them, and boom, run the abipkgdiff for you. 44e01aa904Sopenharmony_ci# 45e01aa904Sopenharmony_ci# Currently, fedabipkgdiff returns 0 if everything works well, otherwise, 1 if 46e01aa904Sopenharmony_ci# something wrong. 47e01aa904Sopenharmony_ci 48e01aa904Sopenharmony_ci 49e01aa904Sopenharmony_ci# First, try proper Koji initialization. 50e01aa904Sopenharmony_citry: 51e01aa904Sopenharmony_ci koji_config = koji.read_config('koji') 52e01aa904Sopenharmony_ci DEFAULT_KOJI_SERVER = koji_config['server'] 53e01aa904Sopenharmony_ci DEFAULT_KOJI_TOPURL = koji_config['topurl'] 54e01aa904Sopenharmony_ciexcept koji.ConfigurationError: 55e01aa904Sopenharmony_ci # ..., but if that fails because of a rather strict interpretation where 56e01aa904Sopenharmony_ci # 'read_config' looks for configuration files, just use dummy values. 57e01aa904Sopenharmony_ci # These fail upon use unless overridden, which for libabigail test suite 58e01aa904Sopenharmony_ci # usage they always are (all relevant artifacts are shipped in the 59e01aa904Sopenharmony_ci # libabigail distribution). 60e01aa904Sopenharmony_ci DEFAULT_KOJI_SERVER = 'dummy_DEFAULT_KOJI_SERVER' 61e01aa904Sopenharmony_ci DEFAULT_KOJI_TOPURL = 'dummy_DEFAULT_KOJI_TOPURL' 62e01aa904Sopenharmony_ci 63e01aa904Sopenharmony_ci 64e01aa904Sopenharmony_ci# The working directory where to hold all data including downloaded RPM 65e01aa904Sopenharmony_ci# packages Currently, it's not configurable and hardcode here. In the future 66e01aa904Sopenharmony_ci# version of fedabipkgdiff, I'll make it configurable by users. 67e01aa904Sopenharmony_ciHOME_DIR = os.path.join(xdg.BaseDirectory.xdg_cache_home, 68e01aa904Sopenharmony_ci os.path.splitext(os.path.basename(__file__))[0]) 69e01aa904Sopenharmony_ci 70e01aa904Sopenharmony_ciDEFAULT_ABIPKGDIFF = 'abipkgdiff' 71e01aa904Sopenharmony_ci 72e01aa904Sopenharmony_ci# Mask for determining if underlying fedabipkgdiff succeeds or not. 73e01aa904Sopenharmony_ci# This is for when the compared ABIs are equal 74e01aa904Sopenharmony_ciABIDIFF_OK = 0 75e01aa904Sopenharmony_ci# This bit is set if there an application error. 76e01aa904Sopenharmony_ciABIDIFF_ERROR = 1 77e01aa904Sopenharmony_ci# This bit is set if the tool is invoked in an non appropriate manner. 78e01aa904Sopenharmony_ciABIDIFF_USAGE_ERROR = 1 << 1 79e01aa904Sopenharmony_ci# This bit is set if the ABIs being compared are different. 80e01aa904Sopenharmony_ciABIDIFF_ABI_CHANGE = 1 << 2 81e01aa904Sopenharmony_ci 82e01aa904Sopenharmony_ci 83e01aa904Sopenharmony_ci# Used to construct abipkgdiff command line argument, package and associated 84e01aa904Sopenharmony_ci# debuginfo package 85e01aa904Sopenharmony_ci# fedabipkgdiff runs abipkgdiff in this form 86e01aa904Sopenharmony_ci# 87e01aa904Sopenharmony_ci# abipkgdiff \ 88e01aa904Sopenharmony_ci# --d1 /path/to/package1-debuginfo.rpm \ 89e01aa904Sopenharmony_ci# --d2 /path/to/package2-debuginfo.rpm \ 90e01aa904Sopenharmony_ci# /path/to/package1.rpm \ 91e01aa904Sopenharmony_ci# /path/to/package2.rpm 92e01aa904Sopenharmony_ci# 93e01aa904Sopenharmony_ci# ComparisonHalf is a three-elements tuple in format 94e01aa904Sopenharmony_ci# 95e01aa904Sopenharmony_ci# (package1.rpm, [package1-debuginfo.rpm..] package1-devel.rpm) 96e01aa904Sopenharmony_ci# 97e01aa904Sopenharmony_ci# - the first element is the subject representing the package to 98e01aa904Sopenharmony_ci# compare. It's a dict representing the RPM we are interested in. 99e01aa904Sopenharmony_ci# That dict was retrieved from Koji XMLRPC API. 100e01aa904Sopenharmony_ci# - the rest are ancillary packages used for the comparison. So, the 101e01aa904Sopenharmony_ci# second one is a vector containing the needed debuginfo packages 102e01aa904Sopenharmony_ci# (yes there can be more than one), and the last one is the package 103e01aa904Sopenharmony_ci# containing API of the ELF shared libraries carried by subject. 104e01aa904Sopenharmony_ci# All the packages are dicts representing RPMs and those dicts were 105e01aa904Sopenharmony_ci# retrieved fromt he KOji XMLRPC API. 106e01aa904Sopenharmony_ci# 107e01aa904Sopenharmony_ci# So, before calling abipkgdiff, fedabipkgdiff must prepare and pass 108e01aa904Sopenharmony_ci# the following information 109e01aa904Sopenharmony_ci# 110e01aa904Sopenharmony_ci# (/path/to/package1.rpm, [/paths/to/package1-debuginfo.rpm ..] /path/to/package1-devel.rpm) 111e01aa904Sopenharmony_ci# (/path/to/package2.rpm, [/paths/to/package2-debuginfo.rpm ..] /path/to/package1-devel.rpm) 112e01aa904Sopenharmony_ci# 113e01aa904Sopenharmony_ciComparisonHalf = namedtuple('ComparisonHalf', 114e01aa904Sopenharmony_ci ['subject', 'ancillary_debug', 'ancillary_devel']) 115e01aa904Sopenharmony_ci 116e01aa904Sopenharmony_ci 117e01aa904Sopenharmony_ciglobal_config = None 118e01aa904Sopenharmony_cipathinfo = None 119e01aa904Sopenharmony_cisession = None 120e01aa904Sopenharmony_ci 121e01aa904Sopenharmony_ci# There is no way to configure the log format so far. I hope I would have time 122e01aa904Sopenharmony_ci# to make it available so that if fedabipkgdiff is scheduled and run by some 123e01aa904Sopenharmony_ci# service, the logs logged into log file is muc usable. 124e01aa904Sopenharmony_cilogging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.CRITICAL) 125e01aa904Sopenharmony_cilogger = logging.getLogger(os.path.basename(__file__)) 126e01aa904Sopenharmony_ci 127e01aa904Sopenharmony_ci 128e01aa904Sopenharmony_ciclass KojiPackageNotFound(Exception): 129e01aa904Sopenharmony_ci """Package is not found in Koji""" 130e01aa904Sopenharmony_ci 131e01aa904Sopenharmony_ci 132e01aa904Sopenharmony_ciclass PackageNotFound(Exception): 133e01aa904Sopenharmony_ci """Package is not found locally""" 134e01aa904Sopenharmony_ci 135e01aa904Sopenharmony_ci 136e01aa904Sopenharmony_ciclass RpmNotFound(Exception): 137e01aa904Sopenharmony_ci """RPM is not found""" 138e01aa904Sopenharmony_ci 139e01aa904Sopenharmony_ci 140e01aa904Sopenharmony_ciclass NoBuildsError(Exception): 141e01aa904Sopenharmony_ci """No builds returned from a method to select specific builds""" 142e01aa904Sopenharmony_ci 143e01aa904Sopenharmony_ci 144e01aa904Sopenharmony_ciclass NoCompleteBuilds(Exception): 145e01aa904Sopenharmony_ci """No complete builds for a package 146e01aa904Sopenharmony_ci 147e01aa904Sopenharmony_ci This is a serious problem, nothing can be done if there is no complete 148e01aa904Sopenharmony_ci builds for a package. 149e01aa904Sopenharmony_ci """ 150e01aa904Sopenharmony_ci 151e01aa904Sopenharmony_ci 152e01aa904Sopenharmony_ciclass InvalidDistroError(Exception): 153e01aa904Sopenharmony_ci """Invalid distro error""" 154e01aa904Sopenharmony_ci 155e01aa904Sopenharmony_ci 156e01aa904Sopenharmony_ciclass CannotFindLatestBuildError(Exception): 157e01aa904Sopenharmony_ci """Cannot find latest build from a package""" 158e01aa904Sopenharmony_ci 159e01aa904Sopenharmony_ci 160e01aa904Sopenharmony_ciclass SetCleanCacheAction(argparse._StoreTrueAction): 161e01aa904Sopenharmony_ci """Custom Action making clean-cache as bundle of clean-cache-before and clean-cache-after""" 162e01aa904Sopenharmony_ci 163e01aa904Sopenharmony_ci def __call__(self, parser, namespace, values, option_string=None): 164e01aa904Sopenharmony_ci setattr(namespace, 'clean_cache_before', self.const) 165e01aa904Sopenharmony_ci setattr(namespace, 'clean_cache_after', self.const) 166e01aa904Sopenharmony_ci 167e01aa904Sopenharmony_ci 168e01aa904Sopenharmony_cidef is_distro_valid(distro): 169e01aa904Sopenharmony_ci """Adjust if a distro is valid 170e01aa904Sopenharmony_ci 171e01aa904Sopenharmony_ci Currently, check for Fedora and RHEL. 172e01aa904Sopenharmony_ci 173e01aa904Sopenharmony_ci :param str distro: a string representing a distro value. 174e01aa904Sopenharmony_ci :return: True if distro is the one specific to Fedora, like fc24, el7. 175e01aa904Sopenharmony_ci "rtype: bool 176e01aa904Sopenharmony_ci """ 177e01aa904Sopenharmony_ci return re.match(r'^(fc|el)\d{1,2}$', distro) is not None 178e01aa904Sopenharmony_ci 179e01aa904Sopenharmony_ci 180e01aa904Sopenharmony_cidef get_distro_from_string(str): 181e01aa904Sopenharmony_ci """Get the part of a string that designates the Fedora distro version number 182e01aa904Sopenharmony_ci 183e01aa904Sopenharmony_ci For instance, when passed the string '2.3.fc12', this function 184e01aa904Sopenharmony_ci returns the string 'fc12'. 185e01aa904Sopenharmony_ci 186e01aa904Sopenharmony_ci :param str the string to consider 187e01aa904Sopenharmony_ci :return: The sub-string of the parameter that represents the 188e01aa904Sopenharmony_ci Fedora distro version number, or None if the parameter does not 189e01aa904Sopenharmony_ci contain such a sub-string. 190e01aa904Sopenharmony_ci """ 191e01aa904Sopenharmony_ci 192e01aa904Sopenharmony_ci m = re.match(r'(.*)((fc|el)\d{1,2})(.*)', str) 193e01aa904Sopenharmony_ci if not m: 194e01aa904Sopenharmony_ci return None 195e01aa904Sopenharmony_ci 196e01aa904Sopenharmony_ci distro = m.group(2) 197e01aa904Sopenharmony_ci return distro 198e01aa904Sopenharmony_ci 199e01aa904Sopenharmony_ci 200e01aa904Sopenharmony_cidef match_nvr(s): 201e01aa904Sopenharmony_ci """Determine if a string is a N-V-R""" 202e01aa904Sopenharmony_ci return re.match(r'^([^/]+)-(.+)-(.+)$', s) is not None 203e01aa904Sopenharmony_ci 204e01aa904Sopenharmony_ci 205e01aa904Sopenharmony_cidef match_nvra(s): 206e01aa904Sopenharmony_ci """Determine if a string is a N-V-R.A""" 207e01aa904Sopenharmony_ci return re.match(r'^([^/]+)-(.+)-(.+)\.(.+)$', s) is not None 208e01aa904Sopenharmony_ci 209e01aa904Sopenharmony_ci 210e01aa904Sopenharmony_cidef is_rpm_file(filename): 211e01aa904Sopenharmony_ci """Return if a file is a RPM""" 212e01aa904Sopenharmony_ci isfile = os.path.isfile(filename) 213e01aa904Sopenharmony_ci mimetype = mimetypes.guess_type(filename)[0] if isfile else None 214e01aa904Sopenharmony_ci isrpm = (mimetype == 'application/x-redhat-package-manager' 215e01aa904Sopenharmony_ci or mimetype == 'application/x-rpm') 216e01aa904Sopenharmony_ci 217e01aa904Sopenharmony_ci # Most systems won't have rpm defined as a mimetype 218e01aa904Sopenharmony_ci if not mimetype and filename.endswith('.rpm'): 219e01aa904Sopenharmony_ci isrpm = True 220e01aa904Sopenharmony_ci logger.debug('is_rpm_file(\'%s\'): isfile=%s, mimetype=\'%s\', isrpm=%s', 221e01aa904Sopenharmony_ci filename, isfile, mimetype, isrpm) 222e01aa904Sopenharmony_ci return isrpm 223e01aa904Sopenharmony_ci 224e01aa904Sopenharmony_ci 225e01aa904Sopenharmony_cidef cmp_nvr(left, right): 226e01aa904Sopenharmony_ci """Compare function for sorting a sequence of NVRs 227e01aa904Sopenharmony_ci 228e01aa904Sopenharmony_ci This is the compare function used in sorted function to sort builds so that 229e01aa904Sopenharmony_ci fedabipkgdiff is able to select the latest build. Return value follows the 230e01aa904Sopenharmony_ci rules described in the part of paramter cmp of sorted documentation. 231e01aa904Sopenharmony_ci 232e01aa904Sopenharmony_ci :param str left: left nvr to compare. 233e01aa904Sopenharmony_ci :param str right: right nvr to compare. 234e01aa904Sopenharmony_ci :return: -1, 0, or 1 that represents left is considered smaller than, 235e01aa904Sopenharmony_ci equal to, or larger than the right individually. 236e01aa904Sopenharmony_ci :rtype: int 237e01aa904Sopenharmony_ci """ 238e01aa904Sopenharmony_ci left_nvr = koji.parse_NVR(left['nvr']) 239e01aa904Sopenharmony_ci right_nvr = koji.parse_NVR(right['nvr']) 240e01aa904Sopenharmony_ci return rpm.labelCompare( 241e01aa904Sopenharmony_ci (left_nvr['epoch'], left_nvr['version'], left_nvr['release']), 242e01aa904Sopenharmony_ci (right_nvr['epoch'], right_nvr['version'], right_nvr['release'])) 243e01aa904Sopenharmony_ci 244e01aa904Sopenharmony_ci 245e01aa904Sopenharmony_cidef log_call(func): 246e01aa904Sopenharmony_ci """A decorator that logs a method invocation 247e01aa904Sopenharmony_ci 248e01aa904Sopenharmony_ci Method's name and all arguments, either positional or keyword arguments, 249e01aa904Sopenharmony_ci will be logged by logger.debug. Also, return value from the decorated 250e01aa904Sopenharmony_ci method will be logged just after the invocation is done. 251e01aa904Sopenharmony_ci 252e01aa904Sopenharmony_ci This decorator does not catch any exception thrown from the decorated 253e01aa904Sopenharmony_ci method. If there is any exception thrown from decorated method, you can 254e01aa904Sopenharmony_ci catch them in the caller and obviously, no return value is logged. 255e01aa904Sopenharmony_ci 256e01aa904Sopenharmony_ci :param callable func: a callable object to decorate 257e01aa904Sopenharmony_ci """ 258e01aa904Sopenharmony_ci def proxy(*args, **kwargs): 259e01aa904Sopenharmony_ci logger.debug('Call %s, args: %s, kwargs: %s', 260e01aa904Sopenharmony_ci func.__name__, 261e01aa904Sopenharmony_ci args if args else '', 262e01aa904Sopenharmony_ci kwargs if kwargs else '') 263e01aa904Sopenharmony_ci result = func(*args, **kwargs) 264e01aa904Sopenharmony_ci logger.debug('Result from %s: %s', func.__name__, result) 265e01aa904Sopenharmony_ci return result 266e01aa904Sopenharmony_ci return proxy 267e01aa904Sopenharmony_ci 268e01aa904Sopenharmony_ci 269e01aa904Sopenharmony_cidef delete_download_cache(): 270e01aa904Sopenharmony_ci """Delete download cache directory""" 271e01aa904Sopenharmony_ci download_dir = get_download_dir() 272e01aa904Sopenharmony_ci if global_config.dry_run: 273e01aa904Sopenharmony_ci print('DRY-RUN: Delete cached downloaded RPM packages at {0}'.format(download_dir)) 274e01aa904Sopenharmony_ci else: 275e01aa904Sopenharmony_ci logger.debug('Delete cached downloaded RPM packages at {0}'.format(download_dir)) 276e01aa904Sopenharmony_ci shutil.rmtree(download_dir) 277e01aa904Sopenharmony_ci 278e01aa904Sopenharmony_ci 279e01aa904Sopenharmony_ciclass RPM(object): 280e01aa904Sopenharmony_ci """Wrapper around an RPM descriptor received from Koji 281e01aa904Sopenharmony_ci 282e01aa904Sopenharmony_ci The RPM descriptor that is returned from Koji XMLRPC API is a 283e01aa904Sopenharmony_ci dict. This wrapper class makes it eaiser to access all these 284e01aa904Sopenharmony_ci properties in the way of object.property. 285e01aa904Sopenharmony_ci """ 286e01aa904Sopenharmony_ci 287e01aa904Sopenharmony_ci def __init__(self, rpm_info): 288e01aa904Sopenharmony_ci """Initialize a RPM object 289e01aa904Sopenharmony_ci 290e01aa904Sopenharmony_ci :param dict rpm_info: a dict representing an RPM descriptor 291e01aa904Sopenharmony_ci received from the Koji API, either listRPMs or getRPM 292e01aa904Sopenharmony_ci """ 293e01aa904Sopenharmony_ci self.rpm_info = rpm_info 294e01aa904Sopenharmony_ci 295e01aa904Sopenharmony_ci def __str__(self): 296e01aa904Sopenharmony_ci """Return the string representation of this RPM 297e01aa904Sopenharmony_ci 298e01aa904Sopenharmony_ci Return the string representation of RPM information returned from Koji 299e01aa904Sopenharmony_ci directly so that RPM can be treated in same way. 300e01aa904Sopenharmony_ci """ 301e01aa904Sopenharmony_ci return str(self.rpm_info) 302e01aa904Sopenharmony_ci 303e01aa904Sopenharmony_ci def __getattr__(self, name): 304e01aa904Sopenharmony_ci """Access RPM information in the way of object.property 305e01aa904Sopenharmony_ci 306e01aa904Sopenharmony_ci :param str name: the property name to access. 307e01aa904Sopenharmony_ci :raises AttributeError: if name is not one of keys of RPM information. 308e01aa904Sopenharmony_ci """ 309e01aa904Sopenharmony_ci if name in self.rpm_info: 310e01aa904Sopenharmony_ci return self.rpm_info[name] 311e01aa904Sopenharmony_ci else: 312e01aa904Sopenharmony_ci raise AttributeError('No attribute name {0}'.format(name)) 313e01aa904Sopenharmony_ci 314e01aa904Sopenharmony_ci def is_peer(self, another_rpm): 315e01aa904Sopenharmony_ci """Determine if this is the peer of a given rpm. 316e01aa904Sopenharmony_ci 317e01aa904Sopenharmony_ci Here is what "peer" means. 318e01aa904Sopenharmony_ci 319e01aa904Sopenharmony_ci Consider a package P for which the tripplet Name, Version, 320e01aa904Sopenharmony_ci Release is made of the values {N,V,R}. Then, consider a 321e01aa904Sopenharmony_ci package P' for which the similar tripplet is {N', V', R'}. 322e01aa904Sopenharmony_ci 323e01aa904Sopenharmony_ci P' is a peer of P if N == N', and either V != V' or R != R'. 324e01aa904Sopenharmony_ci given package with a given NVR is another package with a N'V' 325e01aa904Sopenharmony_ci """ 326e01aa904Sopenharmony_ci return self.name == another_rpm.name and \ 327e01aa904Sopenharmony_ci self.arch == another_rpm.arch and \ 328e01aa904Sopenharmony_ci not (self.version == another_rpm.version 329e01aa904Sopenharmony_ci and self.release == another_rpm.release) 330e01aa904Sopenharmony_ci 331e01aa904Sopenharmony_ci @property 332e01aa904Sopenharmony_ci def nvra(self): 333e01aa904Sopenharmony_ci """Return a RPM's N-V-R-A representation 334e01aa904Sopenharmony_ci 335e01aa904Sopenharmony_ci An example: libabigail-1.0-0.8.rc4.1.fc23.x86_64 336e01aa904Sopenharmony_ci """ 337e01aa904Sopenharmony_ci nvra, _ = os.path.splitext(self.filename) 338e01aa904Sopenharmony_ci return nvra 339e01aa904Sopenharmony_ci 340e01aa904Sopenharmony_ci @property 341e01aa904Sopenharmony_ci def filename(self): 342e01aa904Sopenharmony_ci """Return a RPM file name 343e01aa904Sopenharmony_ci 344e01aa904Sopenharmony_ci An example: libabigail-1.0-0.8.rc4.1.fc23.x86_64.rpm 345e01aa904Sopenharmony_ci """ 346e01aa904Sopenharmony_ci return os.path.basename(pathinfo.rpm(self.rpm_info)) 347e01aa904Sopenharmony_ci 348e01aa904Sopenharmony_ci @property 349e01aa904Sopenharmony_ci def is_debuginfo(self): 350e01aa904Sopenharmony_ci """Check if the name of the current RPM denotes a debug info package""" 351e01aa904Sopenharmony_ci return koji.is_debuginfo(self.rpm_info['name']) 352e01aa904Sopenharmony_ci 353e01aa904Sopenharmony_ci @property 354e01aa904Sopenharmony_ci def is_devel(self): 355e01aa904Sopenharmony_ci """Check if the name of current RPM denotes a development package""" 356e01aa904Sopenharmony_ci return self.rpm_info['name'].endswith('-devel') 357e01aa904Sopenharmony_ci 358e01aa904Sopenharmony_ci @property 359e01aa904Sopenharmony_ci def download_url(self): 360e01aa904Sopenharmony_ci """Get the URL from where to download this RPM""" 361e01aa904Sopenharmony_ci build = session.getBuild(self.build_id) 362e01aa904Sopenharmony_ci return os.path.join(pathinfo.build(build), pathinfo.rpm(self.rpm_info)) 363e01aa904Sopenharmony_ci 364e01aa904Sopenharmony_ci @property 365e01aa904Sopenharmony_ci def downloaded_file(self): 366e01aa904Sopenharmony_ci """Get a pridictable downloaded file name with absolute path""" 367e01aa904Sopenharmony_ci # arch should be removed from the result returned from PathInfo.rpm 368e01aa904Sopenharmony_ci filename = os.path.basename(pathinfo.rpm(self.rpm_info)) 369e01aa904Sopenharmony_ci return os.path.join(get_download_dir(), filename) 370e01aa904Sopenharmony_ci 371e01aa904Sopenharmony_ci @property 372e01aa904Sopenharmony_ci def is_downloaded(self): 373e01aa904Sopenharmony_ci """Check if this RPM was already downloaded to local disk""" 374e01aa904Sopenharmony_ci return os.path.exists(self.downloaded_file) 375e01aa904Sopenharmony_ci 376e01aa904Sopenharmony_ci 377e01aa904Sopenharmony_ciclass LocalRPM(RPM): 378e01aa904Sopenharmony_ci """Representing a local RPM 379e01aa904Sopenharmony_ci 380e01aa904Sopenharmony_ci Local RPM means the one that could be already downloaded or built from 381e01aa904Sopenharmony_ci where I can find it 382e01aa904Sopenharmony_ci """ 383e01aa904Sopenharmony_ci 384e01aa904Sopenharmony_ci def __init__(self, filename): 385e01aa904Sopenharmony_ci """Initialize local RPM with a filename 386e01aa904Sopenharmony_ci 387e01aa904Sopenharmony_ci :param str filename: a filename pointing to a RPM file in local 388e01aa904Sopenharmony_ci disk. Note that, this file must not exist necessarily. 389e01aa904Sopenharmony_ci """ 390e01aa904Sopenharmony_ci self.local_filename = filename 391e01aa904Sopenharmony_ci self.rpm_info = koji.parse_NVRA(os.path.basename(filename)) 392e01aa904Sopenharmony_ci 393e01aa904Sopenharmony_ci @property 394e01aa904Sopenharmony_ci def downloaded_file(self): 395e01aa904Sopenharmony_ci """Return filename of this RPM 396e01aa904Sopenharmony_ci 397e01aa904Sopenharmony_ci Returned filename is just the one passed when initializing this RPM. 398e01aa904Sopenharmony_ci 399e01aa904Sopenharmony_ci :return: filename of this RPM 400e01aa904Sopenharmony_ci :rtype: str 401e01aa904Sopenharmony_ci """ 402e01aa904Sopenharmony_ci return self.local_filename 403e01aa904Sopenharmony_ci 404e01aa904Sopenharmony_ci @property 405e01aa904Sopenharmony_ci def download_url(self): 406e01aa904Sopenharmony_ci raise NotImplementedError('LocalRPM has no URL to download') 407e01aa904Sopenharmony_ci 408e01aa904Sopenharmony_ci def _find_rpm(self, rpm_filename): 409e01aa904Sopenharmony_ci """Search an RPM from the directory of the current instance of LocalRPM 410e01aa904Sopenharmony_ci 411e01aa904Sopenharmony_ci :param str rpm_filename: filename of rpm to find, for example 412e01aa904Sopenharmony_ci foo-devel-0.1-1.fc24. 413e01aa904Sopenharmony_ci :return: an instance of LocalRPM representing the found rpm, or None if 414e01aa904Sopenharmony_ci no RPM was found. 415e01aa904Sopenharmony_ci """ 416e01aa904Sopenharmony_ci search_dir = os.path.dirname(os.path.abspath(self.local_filename)) 417e01aa904Sopenharmony_ci filename = os.path.join(search_dir, rpm_filename) 418e01aa904Sopenharmony_ci return LocalRPM(filename) if os.path.exists(filename) else None 419e01aa904Sopenharmony_ci 420e01aa904Sopenharmony_ci @log_call 421e01aa904Sopenharmony_ci def find_debuginfo(self): 422e01aa904Sopenharmony_ci """Find debuginfo RPM package from a directory""" 423e01aa904Sopenharmony_ci filename = \ 424e01aa904Sopenharmony_ci '%(name)s-debuginfo-%(version)s-%(release)s.%(arch)s.rpm' % \ 425e01aa904Sopenharmony_ci self.rpm_info 426e01aa904Sopenharmony_ci return self._find_rpm(filename) 427e01aa904Sopenharmony_ci 428e01aa904Sopenharmony_ci @log_call 429e01aa904Sopenharmony_ci def find_devel(self): 430e01aa904Sopenharmony_ci """Find development package from a directory""" 431e01aa904Sopenharmony_ci filename = \ 432e01aa904Sopenharmony_ci '%(name)s-devel-%(version)s-%(release)s.%(arch)s.rpm' % \ 433e01aa904Sopenharmony_ci self.rpm_info 434e01aa904Sopenharmony_ci return self._find_rpm(filename) 435e01aa904Sopenharmony_ci 436e01aa904Sopenharmony_ci 437e01aa904Sopenharmony_ciclass RPMCollection(object): 438e01aa904Sopenharmony_ci """Collection of RPMs 439e01aa904Sopenharmony_ci 440e01aa904Sopenharmony_ci This is a simple collection containing RPMs collected from a 441e01aa904Sopenharmony_ci directory on the local filesystem or retrieved from Koji. 442e01aa904Sopenharmony_ci 443e01aa904Sopenharmony_ci A collection can contain one or more sets of RPMs. Each set of 444e01aa904Sopenharmony_ci RPMs being for a particular architecture. 445e01aa904Sopenharmony_ci 446e01aa904Sopenharmony_ci For a given architecture, a set of RPMs is made of one RPM and its 447e01aa904Sopenharmony_ci ancillary RPMs. An ancillary RPM is either a debuginfo RPM or a 448e01aa904Sopenharmony_ci devel RPM. 449e01aa904Sopenharmony_ci 450e01aa904Sopenharmony_ci So a given RPMCollection would (informally) look like: 451e01aa904Sopenharmony_ci 452e01aa904Sopenharmony_ci { 453e01aa904Sopenharmony_ci i686 => {foo.i686.rpm, foo-debuginfo.i686.rpm, foo-devel.i686.rpm} 454e01aa904Sopenharmony_ci x86_64 => {foo.x86_64.rpm, foo-debuginfo.x86_64.rpm, foo-devel.x86_64.rpm,} 455e01aa904Sopenharmony_ci } 456e01aa904Sopenharmony_ci 457e01aa904Sopenharmony_ci """ 458e01aa904Sopenharmony_ci 459e01aa904Sopenharmony_ci def __init__(self, rpms=None): 460e01aa904Sopenharmony_ci # Mapping from arch to a list of rpm_infos. 461e01aa904Sopenharmony_ci # Note that *all* RPMs of the collections are present in this 462e01aa904Sopenharmony_ci # map; that is the RPM to consider and its ancillary RPMs. 463e01aa904Sopenharmony_ci self.rpms = {} 464e01aa904Sopenharmony_ci 465e01aa904Sopenharmony_ci # Mapping from arch to another mapping containing index of debuginfo 466e01aa904Sopenharmony_ci # and development package 467e01aa904Sopenharmony_ci # e.g. 468e01aa904Sopenharmony_ci # self.ancillary_rpms = {'i686', {'debuginfo': foo-debuginfo.rpm, 469e01aa904Sopenharmony_ci # 'devel': foo-devel.rpm}} 470e01aa904Sopenharmony_ci self.ancillary_rpms = {} 471e01aa904Sopenharmony_ci 472e01aa904Sopenharmony_ci if rpms: 473e01aa904Sopenharmony_ci for rpm in rpms: 474e01aa904Sopenharmony_ci self.add(rpm) 475e01aa904Sopenharmony_ci 476e01aa904Sopenharmony_ci @classmethod 477e01aa904Sopenharmony_ci def gather_from_dir(cls, rpm_file, all_rpms=None): 478e01aa904Sopenharmony_ci """Gather RPM collection from local directory""" 479e01aa904Sopenharmony_ci dir_name = os.path.dirname(os.path.abspath(rpm_file)) 480e01aa904Sopenharmony_ci filename = os.path.basename(rpm_file) 481e01aa904Sopenharmony_ci 482e01aa904Sopenharmony_ci nvra = koji.parse_NVRA(filename) 483e01aa904Sopenharmony_ci rpm_files = glob.glob(os.path.join( 484e01aa904Sopenharmony_ci dir_name, '*-%(version)s-%(release)s.%(arch)s.rpm' % nvra)) 485e01aa904Sopenharmony_ci rpm_col = cls() 486e01aa904Sopenharmony_ci 487e01aa904Sopenharmony_ci if all_rpms: 488e01aa904Sopenharmony_ci selector = lambda rpm: True 489e01aa904Sopenharmony_ci else: 490e01aa904Sopenharmony_ci selector = lambda rpm: local_rpm.is_devel or \ 491e01aa904Sopenharmony_ci local_rpm.is_debuginfo or local_rpm.filename == filename 492e01aa904Sopenharmony_ci 493e01aa904Sopenharmony_ci found_debuginfo = 1 494e01aa904Sopenharmony_ci 495e01aa904Sopenharmony_ci for rpm_file in rpm_files: 496e01aa904Sopenharmony_ci local_rpm = LocalRPM(rpm_file) 497e01aa904Sopenharmony_ci 498e01aa904Sopenharmony_ci if local_rpm.is_debuginfo: 499e01aa904Sopenharmony_ci found_debuginfo <<= 1 500e01aa904Sopenharmony_ci if found_debuginfo == 4: 501e01aa904Sopenharmony_ci raise RuntimeError( 502e01aa904Sopenharmony_ci 'Found more than one debuginfo package in ' 503e01aa904Sopenharmony_ci 'this directory. At the moment, fedabipkgdiff ' 504e01aa904Sopenharmony_ci 'is not able to deal with this case. ' 505e01aa904Sopenharmony_ci 'Please create two separate directories and ' 506e01aa904Sopenharmony_ci 'put an RPM and its ancillary debuginfo and ' 507e01aa904Sopenharmony_ci 'devel RPMs in each directory.') 508e01aa904Sopenharmony_ci 509e01aa904Sopenharmony_ci if selector(local_rpm): 510e01aa904Sopenharmony_ci rpm_col.add(local_rpm) 511e01aa904Sopenharmony_ci 512e01aa904Sopenharmony_ci return rpm_col 513e01aa904Sopenharmony_ci 514e01aa904Sopenharmony_ci def add(self, rpm): 515e01aa904Sopenharmony_ci """Add a RPM into this collection""" 516e01aa904Sopenharmony_ci self.rpms.setdefault(rpm.arch, []).append(rpm) 517e01aa904Sopenharmony_ci 518e01aa904Sopenharmony_ci devel_debuginfo_default = {'debuginfo': None, 'devel': None} 519e01aa904Sopenharmony_ci 520e01aa904Sopenharmony_ci if rpm.is_debuginfo: 521e01aa904Sopenharmony_ci self.ancillary_rpms.setdefault( 522e01aa904Sopenharmony_ci rpm.arch, devel_debuginfo_default)['debuginfo'] = rpm 523e01aa904Sopenharmony_ci 524e01aa904Sopenharmony_ci if rpm.is_devel: 525e01aa904Sopenharmony_ci self.ancillary_rpms.setdefault( 526e01aa904Sopenharmony_ci rpm.arch, devel_debuginfo_default)['devel'] = rpm 527e01aa904Sopenharmony_ci 528e01aa904Sopenharmony_ci def rpms_iter(self, arches=None, default_behavior=True): 529e01aa904Sopenharmony_ci """Iterator of RPMs to go through RPMs with specific arches""" 530e01aa904Sopenharmony_ci arches = sorted(self.rpms.keys()) 531e01aa904Sopenharmony_ci 532e01aa904Sopenharmony_ci for arch in arches: 533e01aa904Sopenharmony_ci for _rpm in self.rpms[arch]: 534e01aa904Sopenharmony_ci yield _rpm 535e01aa904Sopenharmony_ci 536e01aa904Sopenharmony_ci def get_sibling_debuginfo(self, rpm): 537e01aa904Sopenharmony_ci """Get sibling debuginfo package of given rpm 538e01aa904Sopenharmony_ci 539e01aa904Sopenharmony_ci The sibling debuginfo is a debug info package for the 540e01aa904Sopenharmony_ci 'rpm'. Note that if there are several debuginfo packages 541e01aa904Sopenharmony_ci associated to 'rpm' and users want to get the one which name 542e01aa904Sopenharmony_ci matches exactly 'rpm', then they might want to use the member 543e01aa904Sopenharmony_ci function 'get_matching_debuginfo' instead. 544e01aa904Sopenharmony_ci 545e01aa904Sopenharmony_ci """ 546e01aa904Sopenharmony_ci if rpm.arch not in self.ancillary_rpms: 547e01aa904Sopenharmony_ci return None 548e01aa904Sopenharmony_ci return self.ancillary_rpms[rpm.arch].get('debuginfo') 549e01aa904Sopenharmony_ci 550e01aa904Sopenharmony_ci def get_matching_debuginfo(self, rpm): 551e01aa904Sopenharmony_ci """Get the debuginfo package that matches a given one """ 552e01aa904Sopenharmony_ci all_debuginfo_list = self.get_all_debuginfo_rpms(rpm) 553e01aa904Sopenharmony_ci debuginfo_pkg = None 554e01aa904Sopenharmony_ci for d in all_debuginfo_list: 555e01aa904Sopenharmony_ci if d.name == '{0}-debuginfo'.format(rpm.name): 556e01aa904Sopenharmony_ci debuginfo_pkg = d 557e01aa904Sopenharmony_ci break 558e01aa904Sopenharmony_ci if not debuginfo_pkg: 559e01aa904Sopenharmony_ci debuginfo_pkg = self.get_sibling_debuginfo(rpm) 560e01aa904Sopenharmony_ci 561e01aa904Sopenharmony_ci return debuginfo_pkg 562e01aa904Sopenharmony_ci 563e01aa904Sopenharmony_ci def get_sibling_devel(self, rpm): 564e01aa904Sopenharmony_ci """Get sibling devel package of given rpm""" 565e01aa904Sopenharmony_ci if rpm.arch not in self.ancillary_rpms: 566e01aa904Sopenharmony_ci return None 567e01aa904Sopenharmony_ci return self.ancillary_rpms[rpm.arch].get('devel') 568e01aa904Sopenharmony_ci 569e01aa904Sopenharmony_ci def get_peer_rpm(self, rpm): 570e01aa904Sopenharmony_ci """Get peer rpm of rpm from this collection""" 571e01aa904Sopenharmony_ci if rpm.arch not in self.rpms: 572e01aa904Sopenharmony_ci return None 573e01aa904Sopenharmony_ci for _rpm in self.rpms[rpm.arch]: 574e01aa904Sopenharmony_ci if _rpm.is_peer(rpm): 575e01aa904Sopenharmony_ci return _rpm 576e01aa904Sopenharmony_ci return None 577e01aa904Sopenharmony_ci 578e01aa904Sopenharmony_ci def get_all_debuginfo_rpms(self, rpm_info): 579e01aa904Sopenharmony_ci """Return a list of descriptors of all the debuginfo RPMs associated 580e01aa904Sopenharmony_ci to a given RPM. 581e01aa904Sopenharmony_ci 582e01aa904Sopenharmony_ci :param: dict rpm_info a dict representing an RPM. This was 583e01aa904Sopenharmony_ci received from the Koji API, either from listRPMs or getRPM. 584e01aa904Sopenharmony_ci :return: a list of dicts containing RPM descriptors (dicts) 585e01aa904Sopenharmony_ci for the debuginfo RPMs associated to rpm_info 586e01aa904Sopenharmony_ci :retype: dict 587e01aa904Sopenharmony_ci """ 588e01aa904Sopenharmony_ci rpm_infos = self.rpms[rpm_info.arch] 589e01aa904Sopenharmony_ci result = [] 590e01aa904Sopenharmony_ci for r in rpm_infos: 591e01aa904Sopenharmony_ci if r.is_debuginfo: 592e01aa904Sopenharmony_ci result.append(r) 593e01aa904Sopenharmony_ci return result 594e01aa904Sopenharmony_ci 595e01aa904Sopenharmony_ci 596e01aa904Sopenharmony_cidef generate_comparison_halves(rpm_col1, rpm_col2): 597e01aa904Sopenharmony_ci """Iterate RPM collection and peer's to generate comparison halves""" 598e01aa904Sopenharmony_ci for _rpm in rpm_col1.rpms_iter(): 599e01aa904Sopenharmony_ci if _rpm.is_debuginfo: 600e01aa904Sopenharmony_ci continue 601e01aa904Sopenharmony_ci if _rpm.is_devel and not global_config.check_all_subpackages: 602e01aa904Sopenharmony_ci continue 603e01aa904Sopenharmony_ci 604e01aa904Sopenharmony_ci if global_config.self_compare: 605e01aa904Sopenharmony_ci rpm2 = _rpm 606e01aa904Sopenharmony_ci else: 607e01aa904Sopenharmony_ci rpm2 = rpm_col2.get_peer_rpm(_rpm) 608e01aa904Sopenharmony_ci if rpm2 is None: 609e01aa904Sopenharmony_ci logger.warning('Peer RPM of {0} is not found.'.format(_rpm.filename)) 610e01aa904Sopenharmony_ci continue 611e01aa904Sopenharmony_ci 612e01aa904Sopenharmony_ci debuginfo_list1 = [] 613e01aa904Sopenharmony_ci debuginfo_list2 = [] 614e01aa904Sopenharmony_ci 615e01aa904Sopenharmony_ci # If this is a *devel* package we are looking at, then get all 616e01aa904Sopenharmony_ci # the debug info packages associated to with the main package 617e01aa904Sopenharmony_ci # and stick them into the resulting comparison half. 618e01aa904Sopenharmony_ci 619e01aa904Sopenharmony_ci if _rpm.is_devel: 620e01aa904Sopenharmony_ci debuginfo_list1 = rpm_col1.get_all_debuginfo_rpms(_rpm) 621e01aa904Sopenharmony_ci else: 622e01aa904Sopenharmony_ci debuginfo_list1.append(rpm_col1.get_matching_debuginfo(_rpm)) 623e01aa904Sopenharmony_ci 624e01aa904Sopenharmony_ci devel1 = rpm_col1.get_sibling_devel(_rpm) 625e01aa904Sopenharmony_ci 626e01aa904Sopenharmony_ci if global_config.self_compare: 627e01aa904Sopenharmony_ci debuginfo_list2 = debuginfo_list1 628e01aa904Sopenharmony_ci devel2 = devel1 629e01aa904Sopenharmony_ci else: 630e01aa904Sopenharmony_ci if rpm2.is_devel: 631e01aa904Sopenharmony_ci debuginfo_list2 = rpm_col2.get_all_debuginfo_rpms(rpm2) 632e01aa904Sopenharmony_ci else: 633e01aa904Sopenharmony_ci debuginfo_list2.append(rpm_col2.get_matching_debuginfo(rpm2)) 634e01aa904Sopenharmony_ci devel2 = rpm_col2.get_sibling_devel(rpm2) 635e01aa904Sopenharmony_ci 636e01aa904Sopenharmony_ci yield (ComparisonHalf(subject=_rpm, 637e01aa904Sopenharmony_ci ancillary_debug=debuginfo_list1, 638e01aa904Sopenharmony_ci ancillary_devel=devel1), 639e01aa904Sopenharmony_ci ComparisonHalf(subject=rpm2, 640e01aa904Sopenharmony_ci ancillary_debug=debuginfo_list2, 641e01aa904Sopenharmony_ci ancillary_devel=devel2)) 642e01aa904Sopenharmony_ci 643e01aa904Sopenharmony_ci 644e01aa904Sopenharmony_ciclass Brew(object): 645e01aa904Sopenharmony_ci """Interface to Koji XMLRPC API with enhancements specific to fedabipkgdiff 646e01aa904Sopenharmony_ci 647e01aa904Sopenharmony_ci kojihub XMLRPC APIs are well-documented in koji's source code. For more 648e01aa904Sopenharmony_ci details information, please refer to class RootExports within kojihub.py. 649e01aa904Sopenharmony_ci 650e01aa904Sopenharmony_ci For details of APIs used within fedabipkgdiff, refer to from line 651e01aa904Sopenharmony_ci 652e01aa904Sopenharmony_ci https://pagure.io/koji/blob/master/f/hub/kojihub.py#_7835 653e01aa904Sopenharmony_ci """ 654e01aa904Sopenharmony_ci 655e01aa904Sopenharmony_ci def __init__(self, baseurl): 656e01aa904Sopenharmony_ci """Initialize Brew 657e01aa904Sopenharmony_ci 658e01aa904Sopenharmony_ci :param str baseurl: the kojihub URL to initialize a session, that is 659e01aa904Sopenharmony_ci used to access koji XMLRPC APIs. 660e01aa904Sopenharmony_ci """ 661e01aa904Sopenharmony_ci self.session = koji.ClientSession(baseurl) 662e01aa904Sopenharmony_ci 663e01aa904Sopenharmony_ci @log_call 664e01aa904Sopenharmony_ci def listRPMs(self, buildID=None, arches=None, selector=None): 665e01aa904Sopenharmony_ci """Get list of RPMs of a build from Koji 666e01aa904Sopenharmony_ci 667e01aa904Sopenharmony_ci Call kojihub.listRPMs to get list of RPMs. Return selected RPMs without 668e01aa904Sopenharmony_ci changing each RPM information. 669e01aa904Sopenharmony_ci 670e01aa904Sopenharmony_ci A RPM returned from listRPMs contains following keys: 671e01aa904Sopenharmony_ci 672e01aa904Sopenharmony_ci - id 673e01aa904Sopenharmony_ci - name 674e01aa904Sopenharmony_ci - version 675e01aa904Sopenharmony_ci - release 676e01aa904Sopenharmony_ci - nvr (synthesized for sorting purposes) 677e01aa904Sopenharmony_ci - arch 678e01aa904Sopenharmony_ci - epoch 679e01aa904Sopenharmony_ci - payloadhash 680e01aa904Sopenharmony_ci - size 681e01aa904Sopenharmony_ci - buildtime 682e01aa904Sopenharmony_ci - build_id 683e01aa904Sopenharmony_ci - buildroot_id 684e01aa904Sopenharmony_ci - external_repo_id 685e01aa904Sopenharmony_ci - external_repo_name 686e01aa904Sopenharmony_ci - metadata_only 687e01aa904Sopenharmony_ci - extra 688e01aa904Sopenharmony_ci 689e01aa904Sopenharmony_ci :param int buildID: id of a build from which to list RPMs. 690e01aa904Sopenharmony_ci :param arches: to restrict to list RPMs with specified arches. 691e01aa904Sopenharmony_ci :type arches: list or tuple 692e01aa904Sopenharmony_ci :param selector: called to determine if a RPM should be selected and 693e01aa904Sopenharmony_ci included in the final returned result. Selector must be a callable 694e01aa904Sopenharmony_ci object and accepts one parameter of a RPM. 695e01aa904Sopenharmony_ci :type selector: a callable object 696e01aa904Sopenharmony_ci :return: a list of RPMs, each of them is a dict object 697e01aa904Sopenharmony_ci :rtype: list 698e01aa904Sopenharmony_ci """ 699e01aa904Sopenharmony_ci if selector: 700e01aa904Sopenharmony_ci assert hasattr(selector, '__call__'), 'selector must be callable.' 701e01aa904Sopenharmony_ci rpms = self.session.listRPMs(buildID=buildID, arches=arches) 702e01aa904Sopenharmony_ci if selector: 703e01aa904Sopenharmony_ci rpms = [rpm for rpm in rpms if selector(rpm)] 704e01aa904Sopenharmony_ci return rpms 705e01aa904Sopenharmony_ci 706e01aa904Sopenharmony_ci @log_call 707e01aa904Sopenharmony_ci def getRPM(self, rpminfo): 708e01aa904Sopenharmony_ci """Get a RPM from koji 709e01aa904Sopenharmony_ci 710e01aa904Sopenharmony_ci Call kojihub.getRPM, and returns the result directly without any 711e01aa904Sopenharmony_ci change. 712e01aa904Sopenharmony_ci 713e01aa904Sopenharmony_ci When not found a RPM, koji.getRPM will return None, then 714e01aa904Sopenharmony_ci this method will raise RpmNotFound error immediately to claim what is 715e01aa904Sopenharmony_ci happening. I want to raise fedabipkgdiff specific error rather than 716e01aa904Sopenharmony_ci koji's GenericError and then raise RpmNotFound again, so I just simply 717e01aa904Sopenharmony_ci don't use strict parameter to call koji.getRPM. 718e01aa904Sopenharmony_ci 719e01aa904Sopenharmony_ci :param rpminfo: rpminfo may be a N-V-R.A or a map containing name, 720e01aa904Sopenharmony_ci version, release, and arch. For example, file-5.25-5.fc24.x86_64, and 721e01aa904Sopenharmony_ci `{'name': 'file', 'version': '5.25', 'release': '5.fc24', 'arch': 722e01aa904Sopenharmony_ci 'x86_64'}`. 723e01aa904Sopenharmony_ci :type rpminfo: str or dict 724e01aa904Sopenharmony_ci :return: a map containing RPM information, that contains same keys as 725e01aa904Sopenharmony_ci method `Brew.listRPMs`. 726e01aa904Sopenharmony_ci :rtype: dict 727e01aa904Sopenharmony_ci :raises RpmNotFound: if a RPM cannot be found with rpminfo. 728e01aa904Sopenharmony_ci """ 729e01aa904Sopenharmony_ci rpm = self.session.getRPM(rpminfo) 730e01aa904Sopenharmony_ci if rpm is None: 731e01aa904Sopenharmony_ci raise RpmNotFound('Cannot find RPM {0}'.format(rpminfo)) 732e01aa904Sopenharmony_ci return rpm 733e01aa904Sopenharmony_ci 734e01aa904Sopenharmony_ci @log_call 735e01aa904Sopenharmony_ci def listBuilds(self, packageID, state=None, topone=None, 736e01aa904Sopenharmony_ci selector=None, order_by=None, reverse=None): 737e01aa904Sopenharmony_ci """Get list of builds from Koji 738e01aa904Sopenharmony_ci 739e01aa904Sopenharmony_ci Call kojihub.listBuilds, and return selected builds without changing 740e01aa904Sopenharmony_ci each build information. 741e01aa904Sopenharmony_ci 742e01aa904Sopenharmony_ci By default, only builds with COMPLETE state are queried and returns 743e01aa904Sopenharmony_ci afterwards. 744e01aa904Sopenharmony_ci 745e01aa904Sopenharmony_ci :param int packageID: id of package to list builds from. 746e01aa904Sopenharmony_ci :param int state: build state. There are five states of a build in 747e01aa904Sopenharmony_ci Koji. fedabipkgdiff only cares about builds with COMPLETE state. If 748e01aa904Sopenharmony_ci state is omitted, builds with COMPLETE state are queried from Koji by 749e01aa904Sopenharmony_ci default. 750e01aa904Sopenharmony_ci :param bool topone: just return the top first build. 751e01aa904Sopenharmony_ci :param selector: a callable object used to select specific subset of 752e01aa904Sopenharmony_ci builds. Selector will be called immediately after Koji returns queried 753e01aa904Sopenharmony_ci builds. When each call to selector, a build is passed to 754e01aa904Sopenharmony_ci selector. Return True if select current build, False if not. 755e01aa904Sopenharmony_ci :type selector: a callable object 756e01aa904Sopenharmony_ci :param str order_by: the attribute name by which to order the builds, 757e01aa904Sopenharmony_ci for example, name, version, or nvr. 758e01aa904Sopenharmony_ci :param bool reverse: whether to order builds reversely. 759e01aa904Sopenharmony_ci :return: a list of builds, even if there is only one build. 760e01aa904Sopenharmony_ci :rtype: list 761e01aa904Sopenharmony_ci :raises TypeError: if selector is not callable, or if order_by is not a 762e01aa904Sopenharmony_ci string value. 763e01aa904Sopenharmony_ci """ 764e01aa904Sopenharmony_ci if state is None: 765e01aa904Sopenharmony_ci state = koji.BUILD_STATES['COMPLETE'] 766e01aa904Sopenharmony_ci 767e01aa904Sopenharmony_ci if selector is not None and not hasattr(selector, '__call__'): 768e01aa904Sopenharmony_ci raise TypeError( 769e01aa904Sopenharmony_ci '{0} is not a callable object.'.format(str(selector))) 770e01aa904Sopenharmony_ci 771e01aa904Sopenharmony_ci if order_by is not None and not isinstance(order_by, six.string_types): 772e01aa904Sopenharmony_ci raise TypeError('order_by {0} is invalid.'.format(order_by)) 773e01aa904Sopenharmony_ci 774e01aa904Sopenharmony_ci builds = self.session.listBuilds(packageID=packageID, state=state) 775e01aa904Sopenharmony_ci if selector is not None: 776e01aa904Sopenharmony_ci builds = [build for build in builds if selector(build)] 777e01aa904Sopenharmony_ci if order_by is not None: 778e01aa904Sopenharmony_ci # FIXME: is it possible to sort builds by using opts parameter of 779e01aa904Sopenharmony_ci # listBuilds 780e01aa904Sopenharmony_ci if order_by == 'nvr': 781e01aa904Sopenharmony_ci if six.PY2: 782e01aa904Sopenharmony_ci builds = sorted(builds, cmp=cmp_nvr, reverse=reverse) 783e01aa904Sopenharmony_ci else: 784e01aa904Sopenharmony_ci builds = sorted(builds, 785e01aa904Sopenharmony_ci key=functools.cmp_to_key(cmp_nvr), 786e01aa904Sopenharmony_ci reverse=reverse) 787e01aa904Sopenharmony_ci else: 788e01aa904Sopenharmony_ci builds = sorted( 789e01aa904Sopenharmony_ci builds, key=lambda b: b[order_by], reverse=reverse) 790e01aa904Sopenharmony_ci if topone: 791e01aa904Sopenharmony_ci builds = builds[0:1] 792e01aa904Sopenharmony_ci 793e01aa904Sopenharmony_ci return builds 794e01aa904Sopenharmony_ci 795e01aa904Sopenharmony_ci @log_call 796e01aa904Sopenharmony_ci def getPackage(self, name): 797e01aa904Sopenharmony_ci """Get a package from Koji 798e01aa904Sopenharmony_ci 799e01aa904Sopenharmony_ci :param str name: a package name. 800e01aa904Sopenharmony_ci :return: a mapping containing package information. For example, 801e01aa904Sopenharmony_ci `{'id': 1, 'name': 'package'}`. 802e01aa904Sopenharmony_ci :rtype: dict 803e01aa904Sopenharmony_ci """ 804e01aa904Sopenharmony_ci package = self.session.getPackage(name) 805e01aa904Sopenharmony_ci if package is None: 806e01aa904Sopenharmony_ci package = self.session.getPackage(name.rsplit('-', 1)[0]) 807e01aa904Sopenharmony_ci if package is None: 808e01aa904Sopenharmony_ci raise KojiPackageNotFound( 809e01aa904Sopenharmony_ci 'Cannot find package {0}.'.format(name)) 810e01aa904Sopenharmony_ci return package 811e01aa904Sopenharmony_ci 812e01aa904Sopenharmony_ci @log_call 813e01aa904Sopenharmony_ci def getBuild(self, buildID): 814e01aa904Sopenharmony_ci """Get a build from Koji 815e01aa904Sopenharmony_ci 816e01aa904Sopenharmony_ci Call kojihub.getBuild. Return got build directly without change. 817e01aa904Sopenharmony_ci 818e01aa904Sopenharmony_ci :param int buildID: id of build to get from Koji. 819e01aa904Sopenharmony_ci :return: the found build. Return None, if not found a build with 820e01aa904Sopenharmony_ci buildID. 821e01aa904Sopenharmony_ci :rtype: dict 822e01aa904Sopenharmony_ci """ 823e01aa904Sopenharmony_ci return self.session.getBuild(buildID) 824e01aa904Sopenharmony_ci 825e01aa904Sopenharmony_ci @log_call 826e01aa904Sopenharmony_ci def get_rpm_build_id(self, name, version, release, arch=None): 827e01aa904Sopenharmony_ci """Get build ID that contains a RPM with specific nvra 828e01aa904Sopenharmony_ci 829e01aa904Sopenharmony_ci If arch is not omitted, a RPM can be identified by its N-V-R-A. 830e01aa904Sopenharmony_ci 831e01aa904Sopenharmony_ci If arch is omitted, name is used to get associated package, and then 832e01aa904Sopenharmony_ci to get the build. 833e01aa904Sopenharmony_ci 834e01aa904Sopenharmony_ci Example: 835e01aa904Sopenharmony_ci 836e01aa904Sopenharmony_ci >>> brew = Brew('url to kojihub') 837e01aa904Sopenharmony_ci >>> brew.get_rpm_build_id('httpd', '2.4.18', '2.fc24') 838e01aa904Sopenharmony_ci >>> brew.get_rpm_build_id('httpd', '2.4.18', '2.fc25', 'x86_64') 839e01aa904Sopenharmony_ci 840e01aa904Sopenharmony_ci :param str name: name of a rpm 841e01aa904Sopenharmony_ci :param str version: version of a rpm 842e01aa904Sopenharmony_ci :param str release: release of a rpm 843e01aa904Sopenharmony_ci :param arch: arch of a rpm 844e01aa904Sopenharmony_ci :type arch: str or None 845e01aa904Sopenharmony_ci :return: id of the build from where the RPM is built 846e01aa904Sopenharmony_ci :rtype: dict 847e01aa904Sopenharmony_ci :raises KojiPackageNotFound: if name is not found from Koji if arch 848e01aa904Sopenharmony_ci is None. 849e01aa904Sopenharmony_ci """ 850e01aa904Sopenharmony_ci if arch is None: 851e01aa904Sopenharmony_ci package = self.getPackage(name) 852e01aa904Sopenharmony_ci selector = lambda item: item['version'] == version and \ 853e01aa904Sopenharmony_ci item['release'] == release 854e01aa904Sopenharmony_ci builds = self.listBuilds(packageID=package['id'], 855e01aa904Sopenharmony_ci selector=selector) 856e01aa904Sopenharmony_ci if not builds: 857e01aa904Sopenharmony_ci raise NoBuildsError( 858e01aa904Sopenharmony_ci 'No builds are selected from package {0}.'.format( 859e01aa904Sopenharmony_ci package['name'])) 860e01aa904Sopenharmony_ci return builds[0]['build_id'] 861e01aa904Sopenharmony_ci else: 862e01aa904Sopenharmony_ci rpm = self.getRPM({'name': name, 863e01aa904Sopenharmony_ci 'version': version, 864e01aa904Sopenharmony_ci 'release': release, 865e01aa904Sopenharmony_ci 'arch': arch, 866e01aa904Sopenharmony_ci }) 867e01aa904Sopenharmony_ci return rpm['build_id'] 868e01aa904Sopenharmony_ci 869e01aa904Sopenharmony_ci @log_call 870e01aa904Sopenharmony_ci def get_package_latest_build(self, package_name, distro): 871e01aa904Sopenharmony_ci """Get latest build from a package, for a particular distro. 872e01aa904Sopenharmony_ci 873e01aa904Sopenharmony_ci Example: 874e01aa904Sopenharmony_ci 875e01aa904Sopenharmony_ci >>> brew = Brew('url to kojihub') 876e01aa904Sopenharmony_ci >>> brew.get_package_latest_build('httpd', 'fc24') 877e01aa904Sopenharmony_ci 878e01aa904Sopenharmony_ci :param str package_name: from which package to get the latest build 879e01aa904Sopenharmony_ci :param str distro: which distro the latest build belongs to 880e01aa904Sopenharmony_ci :return: the found build 881e01aa904Sopenharmony_ci :rtype: dict or None 882e01aa904Sopenharmony_ci :raises NoCompleteBuilds: if there is no latest build of a package. 883e01aa904Sopenharmony_ci """ 884e01aa904Sopenharmony_ci package = self.getPackage(package_name) 885e01aa904Sopenharmony_ci selector = lambda item: item['release'].find(distro) > -1 886e01aa904Sopenharmony_ci 887e01aa904Sopenharmony_ci builds = self.listBuilds(packageID=package['id'], 888e01aa904Sopenharmony_ci selector=selector, 889e01aa904Sopenharmony_ci order_by='nvr', 890e01aa904Sopenharmony_ci reverse=True) 891e01aa904Sopenharmony_ci if not builds: 892e01aa904Sopenharmony_ci # So we found no build which distro string exactly matches 893e01aa904Sopenharmony_ci # the 'distro' parameter. 894e01aa904Sopenharmony_ci # 895e01aa904Sopenharmony_ci # Now lets try to get builds which distro string are less 896e01aa904Sopenharmony_ci # than the value of the 'distro' parameter. This is for 897e01aa904Sopenharmony_ci # cases when, for instance, the build of package foo that 898e01aa904Sopenharmony_ci # is present in current Fedora 27 is foo-1.fc26. That 899e01aa904Sopenharmony_ci # build originates from Fedora 26 but is being re-used in 900e01aa904Sopenharmony_ci # Fedora 27. So we want this function to pick up that 901e01aa904Sopenharmony_ci # foo-1.fc26, even though we want the builds of foo that 902e01aa904Sopenharmony_ci # match the distro string fc27. 903e01aa904Sopenharmony_ci 904e01aa904Sopenharmony_ci selector = lambda build: get_distro_from_string(build['release']) and \ 905e01aa904Sopenharmony_ci get_distro_from_string(build['release']) <= distro 906e01aa904Sopenharmony_ci 907e01aa904Sopenharmony_ci builds = self.listBuilds(packageID=package['id'], 908e01aa904Sopenharmony_ci selector=selector, 909e01aa904Sopenharmony_ci order_by='nvr', 910e01aa904Sopenharmony_ci reverse=True); 911e01aa904Sopenharmony_ci 912e01aa904Sopenharmony_ci if not builds: 913e01aa904Sopenharmony_ci raise NoCompleteBuilds( 914e01aa904Sopenharmony_ci 'No complete builds of package {0}'.format(package_name)) 915e01aa904Sopenharmony_ci 916e01aa904Sopenharmony_ci return builds[0] 917e01aa904Sopenharmony_ci 918e01aa904Sopenharmony_ci @log_call 919e01aa904Sopenharmony_ci def select_rpms_from_a_build(self, build_id, package_name, arches=None, 920e01aa904Sopenharmony_ci select_subpackages=None): 921e01aa904Sopenharmony_ci """Select specific RPMs within a build 922e01aa904Sopenharmony_ci 923e01aa904Sopenharmony_ci RPMs could be filtered be specific criterias by the parameters. 924e01aa904Sopenharmony_ci 925e01aa904Sopenharmony_ci By default, fedabipkgdiff requires the RPM package, as well as 926e01aa904Sopenharmony_ci its associated debuginfo and devel packages. These three 927e01aa904Sopenharmony_ci packages are selected, and noarch and src are excluded. 928e01aa904Sopenharmony_ci 929e01aa904Sopenharmony_ci :param int build_id: from which build to select rpms. 930e01aa904Sopenharmony_ci :param str package_name: which rpm to select that matches this name. 931e01aa904Sopenharmony_ci :param arches: which arches to select. If arches omits, rpms with all 932e01aa904Sopenharmony_ci arches except noarch and src will be selected. 933e01aa904Sopenharmony_ci :type arches: list, tuple or None 934e01aa904Sopenharmony_ci :param bool select_subpackages: indicate whether to select all RPMs 935e01aa904Sopenharmony_ci with specific arch from build. 936e01aa904Sopenharmony_ci :return: a list of RPMs returned from listRPMs 937e01aa904Sopenharmony_ci :rtype: list 938e01aa904Sopenharmony_ci """ 939e01aa904Sopenharmony_ci excluded_arches = ('noarch', 'src') 940e01aa904Sopenharmony_ci 941e01aa904Sopenharmony_ci def rpms_selector(package_name, excluded_arches): 942e01aa904Sopenharmony_ci return lambda rpm: \ 943e01aa904Sopenharmony_ci rpm['arch'] not in excluded_arches and \ 944e01aa904Sopenharmony_ci (rpm['name'] == package_name or 945e01aa904Sopenharmony_ci rpm['name'].endswith('-debuginfo') or 946e01aa904Sopenharmony_ci rpm['name'].endswith('-devel')) 947e01aa904Sopenharmony_ci 948e01aa904Sopenharmony_ci if select_subpackages: 949e01aa904Sopenharmony_ci selector = lambda rpm: rpm['arch'] not in excluded_arches 950e01aa904Sopenharmony_ci else: 951e01aa904Sopenharmony_ci selector = rpms_selector(package_name, excluded_arches) 952e01aa904Sopenharmony_ci rpm_infos = self.listRPMs(buildID=build_id, 953e01aa904Sopenharmony_ci arches=arches, 954e01aa904Sopenharmony_ci selector=selector) 955e01aa904Sopenharmony_ci return RPMCollection((RPM(rpm_info) for rpm_info in rpm_infos)) 956e01aa904Sopenharmony_ci 957e01aa904Sopenharmony_ci @log_call 958e01aa904Sopenharmony_ci def get_latest_built_rpms(self, package_name, distro, arches=None): 959e01aa904Sopenharmony_ci """Get RPMs from latest build of a package 960e01aa904Sopenharmony_ci 961e01aa904Sopenharmony_ci :param str package_name: from which package to get the rpms 962e01aa904Sopenharmony_ci :param str distro: which distro the rpms belong to 963e01aa904Sopenharmony_ci :param arches: which arches the rpms belong to 964e01aa904Sopenharmony_ci :type arches: str or None 965e01aa904Sopenharmony_ci :return: the selected RPMs 966e01aa904Sopenharmony_ci :rtype: list 967e01aa904Sopenharmony_ci """ 968e01aa904Sopenharmony_ci latest_build = self.get_package_latest_build(package_name, distro) 969e01aa904Sopenharmony_ci # Get rpm and debuginfo rpm from each arch 970e01aa904Sopenharmony_ci return self.select_rpms_from_a_build(latest_build['build_id'], 971e01aa904Sopenharmony_ci package_name, 972e01aa904Sopenharmony_ci arches=arches) 973e01aa904Sopenharmony_ci 974e01aa904Sopenharmony_ci 975e01aa904Sopenharmony_ci@log_call 976e01aa904Sopenharmony_cidef get_session(): 977e01aa904Sopenharmony_ci """Get instance of Brew to talk with Koji""" 978e01aa904Sopenharmony_ci return Brew(global_config.koji_server) 979e01aa904Sopenharmony_ci 980e01aa904Sopenharmony_ci 981e01aa904Sopenharmony_ci@log_call 982e01aa904Sopenharmony_cidef get_download_dir(): 983e01aa904Sopenharmony_ci """Return the directory holding all downloaded RPMs 984e01aa904Sopenharmony_ci 985e01aa904Sopenharmony_ci If directory does not exist, it is created automatically. 986e01aa904Sopenharmony_ci 987e01aa904Sopenharmony_ci :return: path to directory holding downloaded RPMs. 988e01aa904Sopenharmony_ci :rtype: str 989e01aa904Sopenharmony_ci """ 990e01aa904Sopenharmony_ci download_dir = os.path.join(HOME_DIR, 'downloads') 991e01aa904Sopenharmony_ci if not os.path.exists(download_dir): 992e01aa904Sopenharmony_ci os.makedirs(download_dir) 993e01aa904Sopenharmony_ci return download_dir 994e01aa904Sopenharmony_ci 995e01aa904Sopenharmony_ci 996e01aa904Sopenharmony_ci@log_call 997e01aa904Sopenharmony_cidef download_rpm(url): 998e01aa904Sopenharmony_ci """Using curl to download a RPM from Koji 999e01aa904Sopenharmony_ci 1000e01aa904Sopenharmony_ci Currently, curl is called and runs in a spawned process. pycurl would be a 1001e01aa904Sopenharmony_ci good way instead. This would be changed in the future. 1002e01aa904Sopenharmony_ci 1003e01aa904Sopenharmony_ci :param str url: URL of a RPM to download. 1004e01aa904Sopenharmony_ci :return: True if a RPM is downloaded successfully, False otherwise. 1005e01aa904Sopenharmony_ci :rtype: bool 1006e01aa904Sopenharmony_ci """ 1007e01aa904Sopenharmony_ci cmd = 'curl --location --silent {0} -o {1}'.format( 1008e01aa904Sopenharmony_ci url, os.path.join(get_download_dir(), 1009e01aa904Sopenharmony_ci os.path.basename(url))) 1010e01aa904Sopenharmony_ci if global_config.dry_run: 1011e01aa904Sopenharmony_ci print('DRY-RUN: {0}'.format(cmd)) 1012e01aa904Sopenharmony_ci return 1013e01aa904Sopenharmony_ci 1014e01aa904Sopenharmony_ci return_code = subprocess.call(cmd, shell=True) 1015e01aa904Sopenharmony_ci if return_code > 0: 1016e01aa904Sopenharmony_ci logger.error('curl fails with returned code: %d.', return_code) 1017e01aa904Sopenharmony_ci return False 1018e01aa904Sopenharmony_ci return True 1019e01aa904Sopenharmony_ci 1020e01aa904Sopenharmony_ci 1021e01aa904Sopenharmony_ci@log_call 1022e01aa904Sopenharmony_cidef download_rpms(rpms): 1023e01aa904Sopenharmony_ci """Download RPMs 1024e01aa904Sopenharmony_ci 1025e01aa904Sopenharmony_ci :param list rpms: list of RPMs to download. 1026e01aa904Sopenharmony_ci """ 1027e01aa904Sopenharmony_ci def _download(rpm): 1028e01aa904Sopenharmony_ci if rpm.is_downloaded: 1029e01aa904Sopenharmony_ci logger.debug('Reuse %s', rpm.downloaded_file) 1030e01aa904Sopenharmony_ci else: 1031e01aa904Sopenharmony_ci logger.debug('Download %s', rpm.download_url) 1032e01aa904Sopenharmony_ci download_rpm(rpm.download_url) 1033e01aa904Sopenharmony_ci 1034e01aa904Sopenharmony_ci for rpm in rpms: 1035e01aa904Sopenharmony_ci _download(rpm) 1036e01aa904Sopenharmony_ci 1037e01aa904Sopenharmony_ci 1038e01aa904Sopenharmony_ci@log_call 1039e01aa904Sopenharmony_cidef build_path_to_abipkgdiff(): 1040e01aa904Sopenharmony_ci """Build the path to the 'abipkgidiff' program to use. 1041e01aa904Sopenharmony_ci 1042e01aa904Sopenharmony_ci The path to 'abipkgdiff' is either the argument of the 1043e01aa904Sopenharmony_ci --abipkgdiff command line option, or the path to 'abipkgdiff' as 1044e01aa904Sopenharmony_ci found in the $PATH environment variable. 1045e01aa904Sopenharmony_ci 1046e01aa904Sopenharmony_ci :return: str a string representing the path to the 'abipkgdiff' 1047e01aa904Sopenharmony_ci command. 1048e01aa904Sopenharmony_ci """ 1049e01aa904Sopenharmony_ci if global_config.abipkgdiff: 1050e01aa904Sopenharmony_ci return global_config.abipkgdiff 1051e01aa904Sopenharmony_ci return DEFAULT_ABIPKGDIFF 1052e01aa904Sopenharmony_ci 1053e01aa904Sopenharmony_ci 1054e01aa904Sopenharmony_cidef format_debug_info_pkg_options(option, debuginfo_list): 1055e01aa904Sopenharmony_ci """Given a list of debug info package descriptors return an option 1056e01aa904Sopenharmony_ci string that looks like: 1057e01aa904Sopenharmony_ci 1058e01aa904Sopenharmony_ci option dbg.rpm1 option dbgrpm2 ... 1059e01aa904Sopenharmony_ci 1060e01aa904Sopenharmony_ci :param: list debuginfo_list a list of instances of the RPM class 1061e01aa904Sopenharmony_ci representing the debug info rpms to use to construct the option 1062e01aa904Sopenharmony_ci string. 1063e01aa904Sopenharmony_ci 1064e01aa904Sopenharmony_ci :return: str a string representing the option string that 1065e01aa904Sopenharmony_ci concatenate the 'option' parameter before the path to each RPM 1066e01aa904Sopenharmony_ci contained in 'debuginfo_list'. 1067e01aa904Sopenharmony_ci """ 1068e01aa904Sopenharmony_ci options = [] 1069e01aa904Sopenharmony_ci 1070e01aa904Sopenharmony_ci for dbg_pkg in debuginfo_list: 1071e01aa904Sopenharmony_ci if dbg_pkg and dbg_pkg.downloaded_file: 1072e01aa904Sopenharmony_ci options.append(' {0} {1}'.format(option, dbg_pkg.downloaded_file)) 1073e01aa904Sopenharmony_ci 1074e01aa904Sopenharmony_ci return ' '.join(options) if options else '' 1075e01aa904Sopenharmony_ci 1076e01aa904Sopenharmony_ci@log_call 1077e01aa904Sopenharmony_cidef abipkgdiff(cmp_half1, cmp_half2): 1078e01aa904Sopenharmony_ci """Run abipkgdiff against found two RPM packages 1079e01aa904Sopenharmony_ci 1080e01aa904Sopenharmony_ci Construct and execute abipkgdiff to get ABI diff 1081e01aa904Sopenharmony_ci 1082e01aa904Sopenharmony_ci abipkgdiff \ 1083e01aa904Sopenharmony_ci --d1 package1-debuginfo --d2 package2-debuginfo \ 1084e01aa904Sopenharmony_ci package1-rpm package2-rpm 1085e01aa904Sopenharmony_ci 1086e01aa904Sopenharmony_ci Output to stdout or stderr from abipkgdiff is not captured. abipkgdiff is 1087e01aa904Sopenharmony_ci called synchronously. fedabipkgdiff does not return until underlying 1088e01aa904Sopenharmony_ci abipkgdiff finishes. 1089e01aa904Sopenharmony_ci 1090e01aa904Sopenharmony_ci :param ComparisonHalf cmp_half1: the first comparison half. 1091e01aa904Sopenharmony_ci :param ComparisonHalf cmp_half2: the second comparison half. 1092e01aa904Sopenharmony_ci :return: return code of underlying abipkgdiff execution. 1093e01aa904Sopenharmony_ci :rtype: int 1094e01aa904Sopenharmony_ci """ 1095e01aa904Sopenharmony_ci abipkgdiff_tool = build_path_to_abipkgdiff() 1096e01aa904Sopenharmony_ci 1097e01aa904Sopenharmony_ci suppressions = '' 1098e01aa904Sopenharmony_ci 1099e01aa904Sopenharmony_ci if global_config.suppr: 1100e01aa904Sopenharmony_ci suppressions = '--suppressions {0}'.format(global_config.suppr) 1101e01aa904Sopenharmony_ci 1102e01aa904Sopenharmony_ci if global_config.no_devel_pkg: 1103e01aa904Sopenharmony_ci devel_pkg1 = '' 1104e01aa904Sopenharmony_ci devel_pkg2 = '' 1105e01aa904Sopenharmony_ci else: 1106e01aa904Sopenharmony_ci if cmp_half1.ancillary_devel is None: 1107e01aa904Sopenharmony_ci msg = 'Development package for {0} does not exist.'.format(cmp_half1.subject.filename) 1108e01aa904Sopenharmony_ci if global_config.error_on_warning: 1109e01aa904Sopenharmony_ci raise RuntimeError(msg) 1110e01aa904Sopenharmony_ci else: 1111e01aa904Sopenharmony_ci devel_pkg1 = '' 1112e01aa904Sopenharmony_ci logger.warning('{0} Ignored.'.format(msg)) 1113e01aa904Sopenharmony_ci else: 1114e01aa904Sopenharmony_ci devel_pkg1 = '--devel-pkg1 {0}'.format(cmp_half1.ancillary_devel.downloaded_file) 1115e01aa904Sopenharmony_ci 1116e01aa904Sopenharmony_ci if cmp_half2.ancillary_devel is None: 1117e01aa904Sopenharmony_ci msg = 'Development package for {0} does not exist.'.format(cmp_half2.subject.filename) 1118e01aa904Sopenharmony_ci if global_config.error_on_warning: 1119e01aa904Sopenharmony_ci raise RuntimeError(msg) 1120e01aa904Sopenharmony_ci else: 1121e01aa904Sopenharmony_ci devel_pkg2 = '' 1122e01aa904Sopenharmony_ci logger.warning('{0} Ignored.'.format(msg)) 1123e01aa904Sopenharmony_ci else: 1124e01aa904Sopenharmony_ci devel_pkg2 = '--devel-pkg2 {0}'.format(cmp_half2.ancillary_devel.downloaded_file) 1125e01aa904Sopenharmony_ci 1126e01aa904Sopenharmony_ci if cmp_half1.ancillary_debug is None: 1127e01aa904Sopenharmony_ci msg = 'Debuginfo package for {0} does not exist.'.format(cmp_half1.subject.filename) 1128e01aa904Sopenharmony_ci if global_config.error_on_warning: 1129e01aa904Sopenharmony_ci raise RuntimeError(msg) 1130e01aa904Sopenharmony_ci else: 1131e01aa904Sopenharmony_ci debuginfo_pkg1 = '' 1132e01aa904Sopenharmony_ci logger.warning('{0} Ignored.'.format(msg)) 1133e01aa904Sopenharmony_ci else: 1134e01aa904Sopenharmony_ci debuginfo_pkg1 = format_debug_info_pkg_options("--d1", cmp_half1.ancillary_debug) 1135e01aa904Sopenharmony_ci 1136e01aa904Sopenharmony_ci if cmp_half2.ancillary_debug is None: 1137e01aa904Sopenharmony_ci msg = 'Debuginfo package for {0} does not exist.'.format(cmp_half2.subject.filename) 1138e01aa904Sopenharmony_ci if global_config.error_on_warning: 1139e01aa904Sopenharmony_ci raise RuntimeError(msg) 1140e01aa904Sopenharmony_ci else: 1141e01aa904Sopenharmony_ci debuginfo_pkg2 = '' 1142e01aa904Sopenharmony_ci logger.warning('{0} Ignored.'.format(msg)) 1143e01aa904Sopenharmony_ci else: 1144e01aa904Sopenharmony_ci debuginfo_pkg2 = format_debug_info_pkg_options("--d2", cmp_half2.ancillary_debug); 1145e01aa904Sopenharmony_ci 1146e01aa904Sopenharmony_ci cmd = [] 1147e01aa904Sopenharmony_ci 1148e01aa904Sopenharmony_ci if global_config.self_compare: 1149e01aa904Sopenharmony_ci cmd = [ 1150e01aa904Sopenharmony_ci abipkgdiff_tool, 1151e01aa904Sopenharmony_ci '--dso-only' if global_config.dso_only else '', 1152e01aa904Sopenharmony_ci '--self-check', 1153e01aa904Sopenharmony_ci debuginfo_pkg1, 1154e01aa904Sopenharmony_ci cmp_half1.subject.downloaded_file, 1155e01aa904Sopenharmony_ci ] 1156e01aa904Sopenharmony_ci else: 1157e01aa904Sopenharmony_ci cmd = [ 1158e01aa904Sopenharmony_ci abipkgdiff_tool, 1159e01aa904Sopenharmony_ci suppressions, 1160e01aa904Sopenharmony_ci '--show-identical-binaries' if global_config.show_identical_binaries else '', 1161e01aa904Sopenharmony_ci '--no-default-suppression' if global_config.no_default_suppr else '', 1162e01aa904Sopenharmony_ci '--dso-only' if global_config.dso_only else '', 1163e01aa904Sopenharmony_ci debuginfo_pkg1, 1164e01aa904Sopenharmony_ci debuginfo_pkg2, 1165e01aa904Sopenharmony_ci devel_pkg1, 1166e01aa904Sopenharmony_ci devel_pkg2, 1167e01aa904Sopenharmony_ci cmp_half1.subject.downloaded_file, 1168e01aa904Sopenharmony_ci cmp_half2.subject.downloaded_file, 1169e01aa904Sopenharmony_ci ] 1170e01aa904Sopenharmony_ci cmd = [s for s in cmd if s != ''] 1171e01aa904Sopenharmony_ci 1172e01aa904Sopenharmony_ci if global_config.dry_run: 1173e01aa904Sopenharmony_ci print('DRY-RUN: {0}'.format(' '.join(cmd))) 1174e01aa904Sopenharmony_ci return 1175e01aa904Sopenharmony_ci 1176e01aa904Sopenharmony_ci logger.debug('Run: %s', ' '.join(cmd)) 1177e01aa904Sopenharmony_ci 1178e01aa904Sopenharmony_ci print('Comparing the ABI of binaries between {0} and {1}:'.format( 1179e01aa904Sopenharmony_ci cmp_half1.subject.filename, cmp_half2.subject.filename)) 1180e01aa904Sopenharmony_ci print() 1181e01aa904Sopenharmony_ci 1182e01aa904Sopenharmony_ci proc = subprocess.Popen(' '.join(cmd), shell=True, 1183e01aa904Sopenharmony_ci stdout=subprocess.PIPE, stderr=subprocess.PIPE, 1184e01aa904Sopenharmony_ci universal_newlines=True) 1185e01aa904Sopenharmony_ci # So we could have done: stdout, stderr = proc.communicate() 1186e01aa904Sopenharmony_ci # But then the documentatin of proc.communicate says: 1187e01aa904Sopenharmony_ci # 1188e01aa904Sopenharmony_ci # Note: The data read is buffered in memory, so do not use this 1189e01aa904Sopenharmony_ci # method if the data size is large or unlimited. " 1190e01aa904Sopenharmony_ci # 1191e01aa904Sopenharmony_ci # In practice, we are seeing random cases where this 1192e01aa904Sopenharmony_ci # proc.communicate() function does *NOT* terminate and seems to be 1193e01aa904Sopenharmony_ci # in a deadlock state. So we are avoiding it altogether. We are 1194e01aa904Sopenharmony_ci # then busy looping, waiting for the spawn process to finish, and 1195e01aa904Sopenharmony_ci # then we get its output. 1196e01aa904Sopenharmony_ci # 1197e01aa904Sopenharmony_ci 1198e01aa904Sopenharmony_ci while True: 1199e01aa904Sopenharmony_ci if proc.poll() != None: 1200e01aa904Sopenharmony_ci break 1201e01aa904Sopenharmony_ci 1202e01aa904Sopenharmony_ci stdout = ''.join(proc.stdout.readlines()) 1203e01aa904Sopenharmony_ci stderr = ''.join(proc.stderr.readlines()) 1204e01aa904Sopenharmony_ci 1205e01aa904Sopenharmony_ci is_ok = proc.returncode == ABIDIFF_OK 1206e01aa904Sopenharmony_ci is_internal_error = proc.returncode & ABIDIFF_ERROR or proc.returncode & ABIDIFF_USAGE_ERROR 1207e01aa904Sopenharmony_ci has_abi_change = proc.returncode & ABIDIFF_ABI_CHANGE 1208e01aa904Sopenharmony_ci 1209e01aa904Sopenharmony_ci if is_internal_error: 1210e01aa904Sopenharmony_ci six.print_(stderr, file=sys.stderr) 1211e01aa904Sopenharmony_ci elif is_ok or has_abi_change: 1212e01aa904Sopenharmony_ci print(stdout) 1213e01aa904Sopenharmony_ci 1214e01aa904Sopenharmony_ci return proc.returncode 1215e01aa904Sopenharmony_ci 1216e01aa904Sopenharmony_ci 1217e01aa904Sopenharmony_ci@log_call 1218e01aa904Sopenharmony_cidef run_abipkgdiff(rpm_col1, rpm_col2): 1219e01aa904Sopenharmony_ci """Run abipkgdiff 1220e01aa904Sopenharmony_ci 1221e01aa904Sopenharmony_ci If one of the executions finds ABI differences, the return code is the 1222e01aa904Sopenharmony_ci return code from abipkgdiff. 1223e01aa904Sopenharmony_ci 1224e01aa904Sopenharmony_ci :param RPMCollection rpm_col1: a collection of RPMs 1225e01aa904Sopenharmony_ci :param RPMCollection rpm_col2: same as rpm_col1 1226e01aa904Sopenharmony_ci :return: exit code of the last non-zero returned from underlying abipkgdiff 1227e01aa904Sopenharmony_ci :rtype: int 1228e01aa904Sopenharmony_ci """ 1229e01aa904Sopenharmony_ci return_codes = [ 1230e01aa904Sopenharmony_ci abipkgdiff(cmp_half1, cmp_half2) for cmp_half1, cmp_half2 1231e01aa904Sopenharmony_ci in generate_comparison_halves(rpm_col1, rpm_col2)] 1232e01aa904Sopenharmony_ci return max(return_codes, key=abs) if return_codes else 0 1233e01aa904Sopenharmony_ci 1234e01aa904Sopenharmony_ci 1235e01aa904Sopenharmony_ci@log_call 1236e01aa904Sopenharmony_cidef diff_local_rpm_with_latest_rpm_from_koji(): 1237e01aa904Sopenharmony_ci """Diff against local rpm and remove latest rpm 1238e01aa904Sopenharmony_ci 1239e01aa904Sopenharmony_ci This operation handles a local rpm and debuginfo rpm and remote ones 1240e01aa904Sopenharmony_ci located in remote Koji server, that has specific distro specificed by 1241e01aa904Sopenharmony_ci argument --from. 1242e01aa904Sopenharmony_ci 1243e01aa904Sopenharmony_ci 1/ Suppose the packager has just locally built a package named 1244e01aa904Sopenharmony_ci foo-3.0.fc24.rpm. To compare the ABI of this locally build package with the 1245e01aa904Sopenharmony_ci latest stable package from Fedora 23, one would do: 1246e01aa904Sopenharmony_ci 1247e01aa904Sopenharmony_ci fedabipkgdiff --from fc23 ./foo-3.0.fc24.rpm 1248e01aa904Sopenharmony_ci """ 1249e01aa904Sopenharmony_ci 1250e01aa904Sopenharmony_ci from_distro = global_config.from_distro 1251e01aa904Sopenharmony_ci if not is_distro_valid(from_distro): 1252e01aa904Sopenharmony_ci raise InvalidDistroError('Invalid distro {0}'.format(from_distro)) 1253e01aa904Sopenharmony_ci 1254e01aa904Sopenharmony_ci local_rpm_file = global_config.NVR[0] 1255e01aa904Sopenharmony_ci if not os.path.exists(local_rpm_file): 1256e01aa904Sopenharmony_ci raise ValueError('{0} does not exist.'.format(local_rpm_file)) 1257e01aa904Sopenharmony_ci 1258e01aa904Sopenharmony_ci local_rpm = LocalRPM(local_rpm_file) 1259e01aa904Sopenharmony_ci rpm_col1 = session.get_latest_built_rpms(local_rpm.name, 1260e01aa904Sopenharmony_ci from_distro, 1261e01aa904Sopenharmony_ci arches=local_rpm.arch) 1262e01aa904Sopenharmony_ci rpm_col2 = RPMCollection.gather_from_dir(local_rpm_file) 1263e01aa904Sopenharmony_ci 1264e01aa904Sopenharmony_ci if global_config.clean_cache_before: 1265e01aa904Sopenharmony_ci delete_download_cache() 1266e01aa904Sopenharmony_ci 1267e01aa904Sopenharmony_ci download_rpms(rpm_col1.rpms_iter()) 1268e01aa904Sopenharmony_ci result = run_abipkgdiff(rpm_col1, rpm_col2) 1269e01aa904Sopenharmony_ci 1270e01aa904Sopenharmony_ci if global_config.clean_cache_after: 1271e01aa904Sopenharmony_ci delete_download_cache() 1272e01aa904Sopenharmony_ci 1273e01aa904Sopenharmony_ci return result 1274e01aa904Sopenharmony_ci 1275e01aa904Sopenharmony_ci 1276e01aa904Sopenharmony_ci@log_call 1277e01aa904Sopenharmony_cidef diff_latest_rpms_based_on_distros(): 1278e01aa904Sopenharmony_ci """abipkgdiff rpms based on two distros 1279e01aa904Sopenharmony_ci 1280e01aa904Sopenharmony_ci 2/ Suppose the packager wants to see how the ABIs of the package foo 1281e01aa904Sopenharmony_ci evolved between fedora 19 and fedora 22. She would thus type the command: 1282e01aa904Sopenharmony_ci 1283e01aa904Sopenharmony_ci fedabipkgdiff --from fc19 --to fc22 foo 1284e01aa904Sopenharmony_ci """ 1285e01aa904Sopenharmony_ci 1286e01aa904Sopenharmony_ci from_distro = global_config.from_distro 1287e01aa904Sopenharmony_ci to_distro = global_config.to_distro 1288e01aa904Sopenharmony_ci 1289e01aa904Sopenharmony_ci if not is_distro_valid(from_distro): 1290e01aa904Sopenharmony_ci raise InvalidDistroError('Invalid distro {0}'.format(from_distro)) 1291e01aa904Sopenharmony_ci 1292e01aa904Sopenharmony_ci if not is_distro_valid(to_distro): 1293e01aa904Sopenharmony_ci raise InvalidDistroError('Invalid distro {0}'.format(to_distro)) 1294e01aa904Sopenharmony_ci 1295e01aa904Sopenharmony_ci package_name = global_config.NVR[0] 1296e01aa904Sopenharmony_ci 1297e01aa904Sopenharmony_ci rpm_col1 = session.get_latest_built_rpms(package_name, 1298e01aa904Sopenharmony_ci distro=global_config.from_distro) 1299e01aa904Sopenharmony_ci rpm_col2 = session.get_latest_built_rpms(package_name, 1300e01aa904Sopenharmony_ci distro=global_config.to_distro) 1301e01aa904Sopenharmony_ci 1302e01aa904Sopenharmony_ci if global_config.clean_cache_before: 1303e01aa904Sopenharmony_ci delete_download_cache() 1304e01aa904Sopenharmony_ci 1305e01aa904Sopenharmony_ci download_rpms(chain(rpm_col1.rpms_iter(), rpm_col2.rpms_iter())) 1306e01aa904Sopenharmony_ci result = run_abipkgdiff(rpm_col1, rpm_col2) 1307e01aa904Sopenharmony_ci 1308e01aa904Sopenharmony_ci if global_config.clean_cache_after: 1309e01aa904Sopenharmony_ci delete_download_cache() 1310e01aa904Sopenharmony_ci 1311e01aa904Sopenharmony_ci return result 1312e01aa904Sopenharmony_ci 1313e01aa904Sopenharmony_ci 1314e01aa904Sopenharmony_ci@log_call 1315e01aa904Sopenharmony_cidef diff_two_nvras_from_koji(): 1316e01aa904Sopenharmony_ci """Diff two nvras from koji 1317e01aa904Sopenharmony_ci 1318e01aa904Sopenharmony_ci The arch probably omits, that means febabipkgdiff will diff all arches. If 1319e01aa904Sopenharmony_ci specificed, the specific arch will be handled. 1320e01aa904Sopenharmony_ci 1321e01aa904Sopenharmony_ci 3/ Suppose the packager wants to compare the ABI of two packages designated 1322e01aa904Sopenharmony_ci by their name and version. She would issue a command like this: 1323e01aa904Sopenharmony_ci 1324e01aa904Sopenharmony_ci fedabipkgdiff foo-1.0.fc19 foo-3.0.fc24 1325e01aa904Sopenharmony_ci fedabipkgdiff foo-1.0.fc19.i686 foo-1.0.fc24.i686 1326e01aa904Sopenharmony_ci """ 1327e01aa904Sopenharmony_ci left_rpm = koji.parse_NVRA(global_config.NVR[0]) 1328e01aa904Sopenharmony_ci right_rpm = koji.parse_NVRA(global_config.NVR[1]) 1329e01aa904Sopenharmony_ci 1330e01aa904Sopenharmony_ci if is_distro_valid(left_rpm['arch']) and \ 1331e01aa904Sopenharmony_ci is_distro_valid(right_rpm['arch']): 1332e01aa904Sopenharmony_ci nvr = koji.parse_NVR(global_config.NVR[0]) 1333e01aa904Sopenharmony_ci params1 = (nvr['name'], nvr['version'], nvr['release'], None) 1334e01aa904Sopenharmony_ci 1335e01aa904Sopenharmony_ci nvr = koji.parse_NVR(global_config.NVR[1]) 1336e01aa904Sopenharmony_ci params2 = (nvr['name'], nvr['version'], nvr['release'], None) 1337e01aa904Sopenharmony_ci else: 1338e01aa904Sopenharmony_ci params1 = (left_rpm['name'], 1339e01aa904Sopenharmony_ci left_rpm['version'], 1340e01aa904Sopenharmony_ci left_rpm['release'], 1341e01aa904Sopenharmony_ci left_rpm['arch']) 1342e01aa904Sopenharmony_ci params2 = (right_rpm['name'], 1343e01aa904Sopenharmony_ci right_rpm['version'], 1344e01aa904Sopenharmony_ci right_rpm['release'], 1345e01aa904Sopenharmony_ci right_rpm['arch']) 1346e01aa904Sopenharmony_ci 1347e01aa904Sopenharmony_ci build_id = session.get_rpm_build_id(*params1) 1348e01aa904Sopenharmony_ci rpm_col1 = session.select_rpms_from_a_build( 1349e01aa904Sopenharmony_ci build_id, params1[0], arches=params1[3], 1350e01aa904Sopenharmony_ci select_subpackages=global_config.check_all_subpackages) 1351e01aa904Sopenharmony_ci 1352e01aa904Sopenharmony_ci build_id = session.get_rpm_build_id(*params2) 1353e01aa904Sopenharmony_ci rpm_col2 = session.select_rpms_from_a_build( 1354e01aa904Sopenharmony_ci build_id, params2[0], arches=params2[3], 1355e01aa904Sopenharmony_ci select_subpackages=global_config.check_all_subpackages) 1356e01aa904Sopenharmony_ci 1357e01aa904Sopenharmony_ci if global_config.clean_cache_before: 1358e01aa904Sopenharmony_ci delete_download_cache() 1359e01aa904Sopenharmony_ci 1360e01aa904Sopenharmony_ci download_rpms(chain(rpm_col1.rpms_iter(), rpm_col2.rpms_iter())) 1361e01aa904Sopenharmony_ci result = run_abipkgdiff(rpm_col1, rpm_col2) 1362e01aa904Sopenharmony_ci 1363e01aa904Sopenharmony_ci if global_config.clean_cache_after: 1364e01aa904Sopenharmony_ci delete_download_cache() 1365e01aa904Sopenharmony_ci 1366e01aa904Sopenharmony_ci return result 1367e01aa904Sopenharmony_ci 1368e01aa904Sopenharmony_ci 1369e01aa904Sopenharmony_ci@log_call 1370e01aa904Sopenharmony_cidef self_compare_rpms_from_distro(): 1371e01aa904Sopenharmony_ci """Compare ABI between same package from a distro 1372e01aa904Sopenharmony_ci 1373e01aa904Sopenharmony_ci Doing ABI comparison on self package should return no 1374e01aa904Sopenharmony_ci ABI change and hence return code should be 0. This is useful 1375e01aa904Sopenharmony_ci to ensure that functionality of libabigail itself 1376e01aa904Sopenharmony_ci didn't break. This utility can be invoked like this: 1377e01aa904Sopenharmony_ci 1378e01aa904Sopenharmony_ci fedabipkgdiff --self-compare -a --from fc25 foo 1379e01aa904Sopenharmony_ci """ 1380e01aa904Sopenharmony_ci 1381e01aa904Sopenharmony_ci from_distro = global_config.from_distro 1382e01aa904Sopenharmony_ci 1383e01aa904Sopenharmony_ci if not is_distro_valid(from_distro): 1384e01aa904Sopenharmony_ci raise InvalidDistroError('Invalid distro {0}'.format(from_distro)) 1385e01aa904Sopenharmony_ci 1386e01aa904Sopenharmony_ci package_name = global_config.NVR[0] 1387e01aa904Sopenharmony_ci 1388e01aa904Sopenharmony_ci rpm_col1 = session.get_latest_built_rpms(package_name, 1389e01aa904Sopenharmony_ci distro=global_config.from_distro) 1390e01aa904Sopenharmony_ci 1391e01aa904Sopenharmony_ci if global_config.clean_cache_before: 1392e01aa904Sopenharmony_ci delete_download_cache() 1393e01aa904Sopenharmony_ci 1394e01aa904Sopenharmony_ci download_rpms(rpm_col1.rpms_iter()) 1395e01aa904Sopenharmony_ci result = run_abipkgdiff(rpm_col1, rpm_col1) 1396e01aa904Sopenharmony_ci 1397e01aa904Sopenharmony_ci if global_config.clean_cache_after: 1398e01aa904Sopenharmony_ci delete_download_cache() 1399e01aa904Sopenharmony_ci 1400e01aa904Sopenharmony_ci return result 1401e01aa904Sopenharmony_ci 1402e01aa904Sopenharmony_ci 1403e01aa904Sopenharmony_ci@log_call 1404e01aa904Sopenharmony_cidef diff_from_two_rpm_files(from_rpm_file, to_rpm_file): 1405e01aa904Sopenharmony_ci """Diff two RPM files""" 1406e01aa904Sopenharmony_ci rpm_col1 = RPMCollection.gather_from_dir(from_rpm_file) 1407e01aa904Sopenharmony_ci rpm_col2 = RPMCollection.gather_from_dir(to_rpm_file) 1408e01aa904Sopenharmony_ci if global_config.clean_cache_before: 1409e01aa904Sopenharmony_ci delete_download_cache() 1410e01aa904Sopenharmony_ci download_rpms(chain(rpm_col1.rpms_iter(), rpm_col2.rpms_iter())) 1411e01aa904Sopenharmony_ci result = run_abipkgdiff(rpm_col1, rpm_col2) 1412e01aa904Sopenharmony_ci if global_config.clean_cache_after: 1413e01aa904Sopenharmony_ci delete_download_cache() 1414e01aa904Sopenharmony_ci return result 1415e01aa904Sopenharmony_ci 1416e01aa904Sopenharmony_ci 1417e01aa904Sopenharmony_cidef build_commandline_args_parser(): 1418e01aa904Sopenharmony_ci parser = argparse.ArgumentParser( 1419e01aa904Sopenharmony_ci description='Compare ABI of shared libraries in RPM packages from the ' 1420e01aa904Sopenharmony_ci 'Koji build system') 1421e01aa904Sopenharmony_ci 1422e01aa904Sopenharmony_ci parser.add_argument( 1423e01aa904Sopenharmony_ci 'NVR', 1424e01aa904Sopenharmony_ci nargs='*', 1425e01aa904Sopenharmony_ci help='RPM package N-V-R, N-V-R-A, N, or local RPM ' 1426e01aa904Sopenharmony_ci 'file names with relative or absolute path.') 1427e01aa904Sopenharmony_ci parser.add_argument( 1428e01aa904Sopenharmony_ci '--dry-run', 1429e01aa904Sopenharmony_ci required=False, 1430e01aa904Sopenharmony_ci dest='dry_run', 1431e01aa904Sopenharmony_ci action='store_true', 1432e01aa904Sopenharmony_ci help='Don\'t actually do the work. The commands that should be ' 1433e01aa904Sopenharmony_ci 'run will be sent to stdout.') 1434e01aa904Sopenharmony_ci parser.add_argument( 1435e01aa904Sopenharmony_ci '--from', 1436e01aa904Sopenharmony_ci required=False, 1437e01aa904Sopenharmony_ci metavar='DISTRO', 1438e01aa904Sopenharmony_ci dest='from_distro', 1439e01aa904Sopenharmony_ci help='baseline Fedora distribution name, for example, fc23') 1440e01aa904Sopenharmony_ci parser.add_argument( 1441e01aa904Sopenharmony_ci '--to', 1442e01aa904Sopenharmony_ci required=False, 1443e01aa904Sopenharmony_ci metavar='DISTRO', 1444e01aa904Sopenharmony_ci dest='to_distro', 1445e01aa904Sopenharmony_ci help='Fedora distribution name to compare against the baseline, for ' 1446e01aa904Sopenharmony_ci 'example, fc24') 1447e01aa904Sopenharmony_ci parser.add_argument( 1448e01aa904Sopenharmony_ci '-a', 1449e01aa904Sopenharmony_ci '--all-subpackages', 1450e01aa904Sopenharmony_ci required=False, 1451e01aa904Sopenharmony_ci action='store_true', 1452e01aa904Sopenharmony_ci dest='check_all_subpackages', 1453e01aa904Sopenharmony_ci help='Check all subpackages instead of only the package specificed in ' 1454e01aa904Sopenharmony_ci 'command line.') 1455e01aa904Sopenharmony_ci parser.add_argument( 1456e01aa904Sopenharmony_ci '--dso-only', 1457e01aa904Sopenharmony_ci required=False, 1458e01aa904Sopenharmony_ci action='store_true', 1459e01aa904Sopenharmony_ci dest='dso_only', 1460e01aa904Sopenharmony_ci help='Compare the ABI of shared libraries only. If this option is not ' 1461e01aa904Sopenharmony_ci 'provided, the tool compares the ABI of all ELF binaries.') 1462e01aa904Sopenharmony_ci parser.add_argument( 1463e01aa904Sopenharmony_ci '--debug', 1464e01aa904Sopenharmony_ci required=False, 1465e01aa904Sopenharmony_ci action='store_true', 1466e01aa904Sopenharmony_ci dest='debug', 1467e01aa904Sopenharmony_ci help='show debug output') 1468e01aa904Sopenharmony_ci parser.add_argument( 1469e01aa904Sopenharmony_ci '--traceback', 1470e01aa904Sopenharmony_ci required=False, 1471e01aa904Sopenharmony_ci action='store_true', 1472e01aa904Sopenharmony_ci dest='show_traceback', 1473e01aa904Sopenharmony_ci help='show traceback when there is an exception thrown.') 1474e01aa904Sopenharmony_ci parser.add_argument( 1475e01aa904Sopenharmony_ci '--server', 1476e01aa904Sopenharmony_ci required=False, 1477e01aa904Sopenharmony_ci metavar='URL', 1478e01aa904Sopenharmony_ci dest='koji_server', 1479e01aa904Sopenharmony_ci default=DEFAULT_KOJI_SERVER, 1480e01aa904Sopenharmony_ci help='URL of koji XMLRPC service. Default is {0}'.format( 1481e01aa904Sopenharmony_ci DEFAULT_KOJI_SERVER)) 1482e01aa904Sopenharmony_ci parser.add_argument( 1483e01aa904Sopenharmony_ci '--topurl', 1484e01aa904Sopenharmony_ci required=False, 1485e01aa904Sopenharmony_ci metavar='URL', 1486e01aa904Sopenharmony_ci dest='koji_topurl', 1487e01aa904Sopenharmony_ci default=DEFAULT_KOJI_TOPURL, 1488e01aa904Sopenharmony_ci help='URL for RPM files access') 1489e01aa904Sopenharmony_ci parser.add_argument( 1490e01aa904Sopenharmony_ci '--abipkgdiff', 1491e01aa904Sopenharmony_ci required=False, 1492e01aa904Sopenharmony_ci metavar='ABIPKGDIFF', 1493e01aa904Sopenharmony_ci dest='abipkgdiff', 1494e01aa904Sopenharmony_ci default='', 1495e01aa904Sopenharmony_ci help="The path to the 'abipkgtool' command to use. " 1496e01aa904Sopenharmony_ci "By default use the one found in $PATH.") 1497e01aa904Sopenharmony_ci parser.add_argument( 1498e01aa904Sopenharmony_ci '--suppressions', 1499e01aa904Sopenharmony_ci required=False, 1500e01aa904Sopenharmony_ci metavar='SUPPR', 1501e01aa904Sopenharmony_ci dest='suppr', 1502e01aa904Sopenharmony_ci default='', 1503e01aa904Sopenharmony_ci help='The suppression specification file to use during comparison') 1504e01aa904Sopenharmony_ci parser.add_argument( 1505e01aa904Sopenharmony_ci '--no-default-suppression', 1506e01aa904Sopenharmony_ci required=False, 1507e01aa904Sopenharmony_ci action='store_true', 1508e01aa904Sopenharmony_ci dest='no_default_suppr', 1509e01aa904Sopenharmony_ci help='Do not load default suppression specifications') 1510e01aa904Sopenharmony_ci parser.add_argument( 1511e01aa904Sopenharmony_ci '--no-devel-pkg', 1512e01aa904Sopenharmony_ci required=False, 1513e01aa904Sopenharmony_ci action='store_true', 1514e01aa904Sopenharmony_ci dest='no_devel_pkg', 1515e01aa904Sopenharmony_ci help='Do not compare ABI with development package') 1516e01aa904Sopenharmony_ci parser.add_argument( 1517e01aa904Sopenharmony_ci '--show-identical-binaries', 1518e01aa904Sopenharmony_ci required=False, 1519e01aa904Sopenharmony_ci action='store_true', 1520e01aa904Sopenharmony_ci dest='show_identical_binaries', 1521e01aa904Sopenharmony_ci help='Show information about binaries whose ABI are identical') 1522e01aa904Sopenharmony_ci parser.add_argument( 1523e01aa904Sopenharmony_ci '--error-on-warning', 1524e01aa904Sopenharmony_ci required=False, 1525e01aa904Sopenharmony_ci action='store_true', 1526e01aa904Sopenharmony_ci dest='error_on_warning', 1527e01aa904Sopenharmony_ci help='Raise error instead of warning') 1528e01aa904Sopenharmony_ci parser.add_argument( 1529e01aa904Sopenharmony_ci '--clean-cache', 1530e01aa904Sopenharmony_ci required=False, 1531e01aa904Sopenharmony_ci action=SetCleanCacheAction, 1532e01aa904Sopenharmony_ci dest='clean_cache', 1533e01aa904Sopenharmony_ci default=None, 1534e01aa904Sopenharmony_ci help='A convenient way to clean cache without specifying ' 1535e01aa904Sopenharmony_ci '--clean-cache-before and --clean-cache-after at same time') 1536e01aa904Sopenharmony_ci parser.add_argument( 1537e01aa904Sopenharmony_ci '--clean-cache-before', 1538e01aa904Sopenharmony_ci required=False, 1539e01aa904Sopenharmony_ci action='store_true', 1540e01aa904Sopenharmony_ci dest='clean_cache_before', 1541e01aa904Sopenharmony_ci default=None, 1542e01aa904Sopenharmony_ci help='Clean cache before ABI comparison') 1543e01aa904Sopenharmony_ci parser.add_argument( 1544e01aa904Sopenharmony_ci '--clean-cache-after', 1545e01aa904Sopenharmony_ci required=False, 1546e01aa904Sopenharmony_ci action='store_true', 1547e01aa904Sopenharmony_ci dest='clean_cache_after', 1548e01aa904Sopenharmony_ci default=None, 1549e01aa904Sopenharmony_ci help='Clean cache after ABI comparison') 1550e01aa904Sopenharmony_ci parser.add_argument( 1551e01aa904Sopenharmony_ci '--self-compare', 1552e01aa904Sopenharmony_ci required=False, 1553e01aa904Sopenharmony_ci action='store_true', 1554e01aa904Sopenharmony_ci dest='self_compare', 1555e01aa904Sopenharmony_ci default=None, 1556e01aa904Sopenharmony_ci help='ABI comparison on same package') 1557e01aa904Sopenharmony_ci return parser 1558e01aa904Sopenharmony_ci 1559e01aa904Sopenharmony_ci 1560e01aa904Sopenharmony_cidef main(): 1561e01aa904Sopenharmony_ci parser = build_commandline_args_parser() 1562e01aa904Sopenharmony_ci 1563e01aa904Sopenharmony_ci args = parser.parse_args() 1564e01aa904Sopenharmony_ci 1565e01aa904Sopenharmony_ci global global_config 1566e01aa904Sopenharmony_ci global_config = args 1567e01aa904Sopenharmony_ci 1568e01aa904Sopenharmony_ci global pathinfo 1569e01aa904Sopenharmony_ci pathinfo = koji.PathInfo(topdir=global_config.koji_topurl) 1570e01aa904Sopenharmony_ci 1571e01aa904Sopenharmony_ci global session 1572e01aa904Sopenharmony_ci session = get_session() 1573e01aa904Sopenharmony_ci 1574e01aa904Sopenharmony_ci if global_config.debug: 1575e01aa904Sopenharmony_ci logger.setLevel(logging.DEBUG) 1576e01aa904Sopenharmony_ci 1577e01aa904Sopenharmony_ci logger.debug(args) 1578e01aa904Sopenharmony_ci 1579e01aa904Sopenharmony_ci if global_config.from_distro and global_config.self_compare and \ 1580e01aa904Sopenharmony_ci global_config.NVR: 1581e01aa904Sopenharmony_ci return self_compare_rpms_from_distro() 1582e01aa904Sopenharmony_ci 1583e01aa904Sopenharmony_ci if global_config.from_distro and global_config.to_distro is None and \ 1584e01aa904Sopenharmony_ci global_config.NVR: 1585e01aa904Sopenharmony_ci return diff_local_rpm_with_latest_rpm_from_koji() 1586e01aa904Sopenharmony_ci 1587e01aa904Sopenharmony_ci if global_config.from_distro and global_config.to_distro and \ 1588e01aa904Sopenharmony_ci global_config.NVR: 1589e01aa904Sopenharmony_ci return diff_latest_rpms_based_on_distros() 1590e01aa904Sopenharmony_ci 1591e01aa904Sopenharmony_ci if global_config.from_distro is None and global_config.to_distro is None: 1592e01aa904Sopenharmony_ci if len(global_config.NVR) > 1: 1593e01aa904Sopenharmony_ci left_one = global_config.NVR[0] 1594e01aa904Sopenharmony_ci right_one = global_config.NVR[1] 1595e01aa904Sopenharmony_ci 1596e01aa904Sopenharmony_ci if is_rpm_file(left_one) and is_rpm_file(right_one): 1597e01aa904Sopenharmony_ci return diff_from_two_rpm_files(left_one, right_one) 1598e01aa904Sopenharmony_ci 1599e01aa904Sopenharmony_ci both_nvr = match_nvr(left_one) and match_nvr(right_one) 1600e01aa904Sopenharmony_ci both_nvra = match_nvra(left_one) and match_nvra(right_one) 1601e01aa904Sopenharmony_ci 1602e01aa904Sopenharmony_ci if both_nvr or both_nvra: 1603e01aa904Sopenharmony_ci return diff_two_nvras_from_koji() 1604e01aa904Sopenharmony_ci 1605e01aa904Sopenharmony_ci six.print_('Unknown arguments. Please refer to --help.', file=sys.stderr) 1606e01aa904Sopenharmony_ci return 1 1607e01aa904Sopenharmony_ci 1608e01aa904Sopenharmony_ci 1609e01aa904Sopenharmony_ciif __name__ == '__main__': 1610e01aa904Sopenharmony_ci try: 1611e01aa904Sopenharmony_ci sys.exit(main()) 1612e01aa904Sopenharmony_ci except KeyboardInterrupt: 1613e01aa904Sopenharmony_ci if global_config is None: 1614e01aa904Sopenharmony_ci raise 1615e01aa904Sopenharmony_ci if global_config.debug: 1616e01aa904Sopenharmony_ci logger.debug('Terminate by user') 1617e01aa904Sopenharmony_ci else: 1618e01aa904Sopenharmony_ci six.print_('Terminate by user', file=sys.stderr) 1619e01aa904Sopenharmony_ci if global_config.show_traceback: 1620e01aa904Sopenharmony_ci raise 1621e01aa904Sopenharmony_ci else: 1622e01aa904Sopenharmony_ci sys.exit(2) 1623e01aa904Sopenharmony_ci except Exception as e: 1624e01aa904Sopenharmony_ci if global_config is None: 1625e01aa904Sopenharmony_ci raise 1626e01aa904Sopenharmony_ci if global_config.debug: 1627e01aa904Sopenharmony_ci logger.debug(str(e)) 1628e01aa904Sopenharmony_ci else: 1629e01aa904Sopenharmony_ci six.print_(str(e), file=sys.stderr) 1630e01aa904Sopenharmony_ci if global_config.show_traceback: 1631e01aa904Sopenharmony_ci raise 1632e01aa904Sopenharmony_ci else: 1633e01aa904Sopenharmony_ci sys.exit(1) 1634