| |
@@ -1,4 +1,4 @@
|
| |
- #!/usr/bin/python -tt
|
| |
+ #! /usr/bin/python3
|
| |
# This program is free software; you can redistribute it and/or modify
|
| |
# it under the terms of the GNU General Public License as published by
|
| |
# the Free Software Foundation; either version 2 of the License, or
|
| |
@@ -19,13 +19,13 @@
|
| |
# This script used for the following test case:
|
| |
# https://fedoraproject.org/wiki/QA:Testcase_Mediakit_FileConflicts
|
| |
|
| |
- """Go through a yum packagesack and return the list of newest pkgs which
|
| |
+ """Go through a dnf packagesack and return the list of newest pkgs which
|
| |
possibly have conflicting files"""
|
| |
|
| |
+ from collections import defaultdict
|
| |
+ from optparse import OptionParser
|
| |
+ from urllib.request import urlopen
|
| |
|
| |
- import yum
|
| |
- import yum.Errors
|
| |
- import yum.misc
|
| |
import datetime
|
| |
import time
|
| |
import sys
|
| |
@@ -33,11 +33,11 @@
|
| |
import os.path
|
| |
import tempfile
|
| |
import shutil
|
| |
- import urllib
|
| |
- from collections import defaultdict
|
| |
- from rpmUtils.miscutils import rangeCompare as rpmRangeCompare
|
| |
+ import re
|
| |
|
| |
- from optparse import OptionParser
|
| |
+ import dnf
|
| |
+ import rpm
|
| |
+ import hawkey
|
| |
|
| |
# iterate all pkgs, build a filedict of filename = [list of pkg objects]
|
| |
# then go through that list, if any file has more than one pkg then put those
|
| |
@@ -47,30 +47,8 @@
|
| |
# pkgs which are multilib sets
|
| |
# pkgs which have fully matching overlapping files (md5sums + timestamps)
|
| |
|
| |
- def parseArgs():
|
| |
- usage = """
|
| |
- Check given repos for possible file/package conflicts
|
| |
-
|
| |
- %s [-c <config file>] [-r <repoid>] [-r <repoid2>]
|
| |
- """ % sys.argv[0]
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("-c", "--config", default='/etc/yum.conf',
|
| |
- help='config file to use (defaults to /etc/yum.conf)')
|
| |
- parser.add_option("-r", "--repoid", default=[], action='append',
|
| |
- help=("specify repo ids to query, can be specified multiple times "
|
| |
- "(default is all enabled)"))
|
| |
- parser.add_option("-t", "--tempcache", default=False, action="store_true",
|
| |
- help="Use a temp dir for storing/accessing yum-cache")
|
| |
- parser.add_option("--repofrompath", action="append",
|
| |
- help=("specify repoid & paths of additional repositories "
|
| |
- "- unique repoid and complete path required, can be specified multiple "
|
| |
- "times. Example. --repofrompath=myrepo,/path/to/repo"))
|
| |
- (opts, args) = parser.parse_args()
|
| |
- return (opts, args)
|
| |
-
|
| |
-
|
| |
- # not a great check but it does toss out all the glibc.i686 vs glibc.i386 ones
|
| |
def pkg_names_match(pkglist):
|
| |
+ "not a great check but it does toss out all the glibc.i686 vs glibc.i386 ones"
|
| |
p = pkglist[0]
|
| |
for pkg in pkglist[1:]:
|
| |
if p.name != pkg.name:
|
| |
@@ -78,9 +56,8 @@
|
| |
|
| |
return True
|
| |
|
| |
-
|
| |
- # if they have the same sourcerpm it is highly doubtful it is a legit conflict
|
| |
def sourcerpms_match(pkglist):
|
| |
+ "if they have the same sourcerpm it is highly doubtful it is a legit conflict"
|
| |
p = pkglist[0]
|
| |
for pkg in pkglist[1:]:
|
| |
if p.sourcerpm != pkg.sourcerpm:
|
| |
@@ -88,203 +65,401 @@
|
| |
|
| |
return True
|
| |
|
| |
+ # And this is just ugly piece of yum we are going to copy n paste
|
| |
+ def compareEVR(left_tuple, right_tuple):
|
| |
+ """
|
| |
+ return 1: a is newer than b
|
| |
+ 0: a and b are the same version
|
| |
+ -1: b is newer than a
|
| |
+ """
|
| |
+ (e1, v1, r1) = left_tuple
|
| |
+ (e2, v2, r2) = right_tuple
|
| |
+ if e1 is None:
|
| |
+ e1 = '0'
|
| |
+ else:
|
| |
+ e1 = str(e1)
|
| |
+ v1 = str(v1)
|
| |
+ r1 = str(r1)
|
| |
+ if e2 is None:
|
| |
+ e2 = '0'
|
| |
+ else:
|
| |
+ e2 = str(e2)
|
| |
+ v2 = str(v2)
|
| |
+ r2 = str(r2)
|
| |
+ #print '%s, %s, %s vs %s, %s, %s' % (e1, v1, r1, e2, v2, r2)
|
| |
+ rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
|
| |
+ #print '%s, %s, %s vs %s, %s, %s = %s' % (e1, v1, r1, e2, v2, r2, rc)
|
| |
+ return rc
|
| |
+
|
| |
+ def rangeCompare(reqtuple, provtuple):
|
| |
+ """returns true if provtuple satisfies reqtuple"""
|
| |
+ (reqn, reqf, (reqe, reqv, reqr)) = reqtuple
|
| |
+ (n, f, (e, v, r)) = provtuple
|
| |
+ if reqn != n:
|
| |
+ return 0
|
| |
+
|
| |
+ # unversioned satisfies everything
|
| |
+ if not f or not reqf:
|
| |
+ return 1
|
| |
+
|
| |
+ # and you thought we were done having fun
|
| |
+ # if the requested release is left out then we have
|
| |
+ # to remove release from the package prco to make sure the match
|
| |
+ # is a success - ie: if the request is EQ foo 1:3.0.0 and we have
|
| |
+ # foo 1:3.0.0-15 then we have to drop the 15 so we can match
|
| |
+ if reqr is None:
|
| |
+ r = None
|
| |
+ if reqe is None:
|
| |
+ e = None
|
| |
+ if reqv is None: # just for the record if ver is None then we're going to segfault
|
| |
+ v = None
|
| |
+
|
| |
+ # if we just require foo-version, then foo-version-* will match
|
| |
+ if r is None:
|
| |
+ reqr = None
|
| |
+
|
| |
+ rc = compareEVR((e, v, r), (reqe, reqv, reqr))
|
| |
+
|
| |
+ # does not match unless
|
| |
+ if rc >= 1:
|
| |
+ if reqf in ['GT', 'GE', 4, 12, '>', '>=']:
|
| |
+ return 1
|
| |
+ if reqf in ['EQ', 8, '=']:
|
| |
+ if f in ['LE', 10, 'LT', 2, '<=', '<']:
|
| |
+ return 1
|
| |
+ if reqf in ['LE', 'LT', 'EQ', 10, 2, 8, '<=', '<', '=']:
|
| |
+ if f in ['LE', 'LT', 10, 2, '<=', '<']:
|
| |
+ return 1
|
| |
+
|
| |
+ if rc == 0:
|
| |
+ if reqf in ['GT', 4, '>']:
|
| |
+ if f in ['GT', 'GE', 4, 12, '>', '>=']:
|
| |
+ return 1
|
| |
+ if reqf in ['GE', 12, '>=']:
|
| |
+ if f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10, '>', '>=', '=', '<=']:
|
| |
+ return 1
|
| |
+ if reqf in ['EQ', 8, '=']:
|
| |
+ if f in ['EQ', 'GE', 'LE', 8, 12, 10, '=', '>=', '<=']:
|
| |
+ return 1
|
| |
+ if reqf in ['LE', 10, '<=']:
|
| |
+ if f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12, '=', '<=', '<', '>=']:
|
| |
+ return 1
|
| |
+ if reqf in ['LT', 2, '<']:
|
| |
+ if f in ['LE', 'LT', 10, 2, '<=', '<']:
|
| |
+ return 1
|
| |
+ if rc <= -1:
|
| |
+ if reqf in ['GT', 'GE', 'EQ', 4, 12, 8, '>', '>=', '=']:
|
| |
+ if f in ['GT', 'GE', 4, 12, '>', '>=']:
|
| |
+ return 1
|
| |
+ if reqf in ['LE', 'LT', 10, 2, '<=', '<']:
|
| |
+ return 1
|
| |
+ # if rc >= 1:
|
| |
+ # if reqf in ['GT', 'GE', 4, 12, '>', '>=']:
|
| |
+ # return 1
|
| |
+ # if rc == 0:
|
| |
+ # if reqf in ['GE', 'LE', 'EQ', 8, 10, 12, '>=', '<=', '=']:
|
| |
+ # return 1
|
| |
+ # if rc <= -1:
|
| |
+ # if reqf in ['LT', 'LE', 2, 10, '<', '<=']:
|
| |
+ # return 1
|
| |
+
|
| |
+ return 0
|
| |
+
|
| |
+ # yum code ends here
|
| |
+
|
| |
+ def prco_to_tuple(prco_string):
|
| |
+ """
|
| |
+ Creates dependency tuple containing name and evr from single string provided by dnf
|
| |
+ Example input: "glib2(x86-64) >= 2.54.0"
|
| |
+ Example output: (glib2, "GE", (0, 2.54.0, 1))
|
| |
+
|
| |
+ Architecture requirements are lost during the conversion. Release is set to 1 if missing.
|
| |
+ """
|
| |
+ sign = ""
|
| |
+ if "<=" in prco_string:
|
| |
+ sign = "LE"
|
| |
+ data = prco_string.split("<=")
|
| |
+ elif "<" in prco_string:
|
| |
+ sign = "LT"
|
| |
+ data = prco_string.split("<")
|
| |
+ elif ">=" in prco_string:
|
| |
+ sign = "GE"
|
| |
+ data = prco_string.split(">=")
|
| |
+ elif ">" in prco_string:
|
| |
+ sign = "GT"
|
| |
+ data = prco_string.split(">")
|
| |
+ elif "=" in prco_string:
|
| |
+ sign = "EQ"
|
| |
+ data = prco_string.split("=")
|
| |
+ else:
|
| |
+ sign = None
|
| |
+ data = [prco_string, None]
|
| |
+
|
| |
+ # We might have got prco without evr
|
| |
+ if not data[1]:
|
| |
+ return (data[0], sign, (None, None, None))
|
| |
+
|
| |
+ # Trim whitespace from start and end
|
| |
+ data[0] = data[0].strip()
|
| |
+ data[1] = data[1].strip()
|
| |
+
|
| |
+ base_string_for_nevra = str(data[0] + "-" + data[1])
|
| |
+ # Hawkey nevra parser doesn't like brackets :/
|
| |
+ # Removes brackets and content inside them
|
| |
+ # eg. "glib2(x86-64) >= 2.54.0" > "glib2 >= 2.54.0"
|
| |
+ base_string_for_nevra = re.sub(r' *\(.*?\)', '', base_string_for_nevra)
|
| |
+
|
| |
+ try:
|
| |
+ string_for_nevra = str(base_string_for_nevra + ".noarch")
|
| |
+ nevra = hawkey.split_nevra(string_for_nevra)
|
| |
+ # We might be missing release
|
| |
+ except hawkey.ValueException:
|
| |
+ string_for_nevra = str(base_string_for_nevra + "-1.noarch")
|
| |
+ nevra = hawkey.split_nevra(string_for_nevra)
|
| |
+
|
| |
+ return (data[0], sign, (nevra.epoch, nevra.version, nevra.release))
|
| |
|
| |
- # XXX we should just be able to use the pkg.inPrcoRange method but I think it's
|
| |
- # broken - it seems to have the rangeCompare arguments backwards.
|
| |
def inPrcoRange(prcotype, p1, p2):
|
| |
- p2_tuple = (p2.name, 'EQ', (p2.epoch, p2.ver, p2.rel))
|
| |
- for obs in p1.returnPrco(prcotype):
|
| |
- if rpmRangeCompare(obs, p2_tuple):
|
| |
+ "Checks if p1 satisfies p2 or vice versa"
|
| |
+ if prcotype:
|
| |
+ p1_data = getattr(p1, prcotype)
|
| |
+ else:
|
| |
+ raise Exception("Unexpected prcotype: %s" % prcotype)
|
| |
+
|
| |
+ #p2 stays the same, no need to assign it again and again below
|
| |
+ p2_tuple = (p2.name, 'EQ', (p2.epoch, p2.version, p2.release))
|
| |
+
|
| |
+ for item in p1_data:
|
| |
+ p1_tuple = prco_to_tuple(str(item))
|
| |
+ if rangeCompare(p1_tuple, p2_tuple):
|
| |
return True
|
| |
- return False
|
| |
|
| |
+ return False
|
| |
|
| |
def check_list_for_prco(prcotype, pkglist):
|
| |
- '''Compare each pair of packages in the pkglist to see if any of them
|
| |
- obsoletes/conflicts/requires/provides the other'''
|
| |
+ """
|
| |
+ Compare each pair of packages in the pkglist to see if any of them
|
| |
+ obsoletes/conflicts/requires/provides the other
|
| |
+ """
|
| |
for p1 in pkglist:
|
| |
for p2 in pkglist:
|
| |
if p1 == p2:
|
| |
continue
|
| |
if inPrcoRange(prcotype, p1, p2) or inPrcoRange(prcotype, p2, p1):
|
| |
- #print '%s %s %s' % (p1, prcotype, p2)
|
| |
+ #print("%s and %s in prco range." % (p1.name, p2.name))
|
| |
return True
|
| |
return False
|
| |
|
| |
|
| |
def obsolete_each_other(pkglist):
|
| |
+ "Checks if packages in pkglist obsolete each other"
|
| |
return check_list_for_prco('obsoletes', pkglist)
|
| |
|
| |
|
| |
def package_conflict(pkglist):
|
| |
+ "Checks if packages in pkglist conflict"
|
| |
return check_list_for_prco('conflicts', pkglist)
|
| |
|
| |
|
| |
- # XXX with sufficiently fast network, the bottleneck is CPU in the checksum
|
| |
- # comparison here. Maybe we should multithread it someday?
|
| |
- def file_conflict(fn, pkglist):
|
| |
- checksum = None
|
| |
-
|
| |
- for pkg in pkglist:
|
| |
- #print "Fetching Headers for %s" % pkg
|
| |
- #download header
|
| |
- # make this into a proper error func check for urlgrabber routines
|
| |
- # so we get the proper retries
|
| |
- if not os.path.exists(pkg.localHdr()):
|
| |
- try:
|
| |
- hdr = pkg.returnLocalHeader()
|
| |
- except yum.Errors.RepoError, e:
|
| |
- pkg.repo.getHeader(pkg)
|
| |
- else:
|
| |
- pkg.repo.getHeader(pkg)
|
| |
- #read in header
|
| |
- try:
|
| |
- hdr = pkg.returnLocalHeader()
|
| |
- except yum.Errors.RepoError, e:
|
| |
- #print "Cannot retrieve header for %s" % pkg
|
| |
- return True
|
| |
+ def file_conflict_is_permitted(left_loc, right_loc, filename):
|
| |
+ """
|
| |
+ Returns True if rpm would allow both the given packages to share
|
| |
+ ownership of the given filename.
|
| |
+ """
|
| |
+ ts = rpm.TransactionSet()
|
| |
+ ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
|
| |
|
| |
- fi = hdr.fiFromHeader()
|
| |
- for fitup in fi:
|
| |
- if fitup[0] == fn:
|
| |
- if checksum is None:
|
| |
- checksum = fitup[-1]
|
| |
- elif checksum != fitup[-1]:
|
| |
- return True
|
| |
+ fd_left = os.open(left_loc, os.O_RDONLY)
|
| |
+ fd_right = os.open(right_loc, os.O_RDONLY)
|
| |
|
| |
- return False
|
| |
+ left_files = rpm.files(ts.hdrFromFdno(fd_left))
|
| |
+ right_files = rpm.files(ts.hdrFromFdno(fd_right))
|
| |
|
| |
+ # Close FDs
|
| |
+ os.close(fd_left)
|
| |
+ os.close(fd_right)
|
| |
|
| |
- (opts, cruft) = parseArgs()
|
| |
+ if left_files[filename].matches(right_files[filename]):
|
| |
+ #print('Conflict on %s between %s and %s permitted because files match' % (filename, left_loc, right_loc))
|
| |
+ return True
|
| |
+ # TODO: Check if the following code is working properly and uncomment it eventually
|
| |
+ #if left_files[filename].color != right_files[filename].color:
|
| |
+ # #print('Conflict on %s between %s and %s permitted because colors differ' % (filename, left_loc, right_loc))
|
| |
+ # return True
|
| |
+ return False
|
| |
|
| |
- my = yum.YumBase()
|
| |
- my.doConfigSetup(fn = opts.config, init_plugins=False)
|
| |
+ def get_package(pkg):
|
| |
+ "Returns location of the package and downloads it if it's not available in local storage"
|
| |
+ if not "file://" in pkg.remote_location():
|
| |
+ pkg.base.download_packages([pkg])
|
| |
+ return pkg.localPkg()
|
| |
|
| |
- temp_cachedir = None
|
| |
- if os.geteuid() != 0 or opts.tempcache:
|
| |
- temp_cachedir = tempfile.mkdtemp(prefix='conflicts')
|
| |
- my.conf.cachedir = temp_cachedir
|
| |
- else:
|
| |
- my.conf.cachedir = yum.misc.getCacheDir()
|
| |
+ def file_conflict(fn, pkglist):
|
| |
+ "Checks if is fn going to cause conflicts between packages from pkglist"
|
| |
+ for pkg_left in pkglist:
|
| |
+ location_left = get_package(pkg_left)
|
| |
+ for pkg_right in pkglist:
|
| |
+ if pkg_left == pkg_right:
|
| |
+ continue
|
| |
+ location_right = get_package(pkg_right)
|
| |
+ if not file_conflict_is_permitted(location_right, location_left, fn):
|
| |
+ return True
|
| |
+ return False
|
| |
|
| |
- my.repos.setCacheDir(my.conf.cachedir)
|
| |
+ if __name__ == "__main__":
|
| |
|
| |
- if opts.repofrompath:
|
| |
- # setup the fake repos
|
| |
- for repo in opts.repofrompath:
|
| |
- repoid, repopath = tuple(repo.split(','))
|
| |
- if repopath[0] == '/':
|
| |
- baseurl = 'file://' + repopath
|
| |
- else:
|
| |
- # This URL may be a redirect, like download.fedoraproject.org. We
|
| |
- # need to resolve this redirect now, otherwise the redirect URL will
|
| |
- # be used for every package header download and that a) will extremely
|
| |
- # slow down download speed b) may cause consistency problems if not
|
| |
- # always the same repo mirror is returned from the redirect
|
| |
- net_obj = urllib.urlopen(repopath)
|
| |
- baseurl = repopath = net_obj.geturl()
|
| |
-
|
| |
- repopath = os.path.normpath(repopath)
|
| |
- newrepo = yum.yumRepo.YumRepository(repoid)
|
| |
- newrepo.name = repoid
|
| |
- newrepo.baseurl = baseurl
|
| |
- newrepo.basecachedir = my.conf.cachedir
|
| |
- newrepo.metadata_expire = 0
|
| |
- newrepo.timestamp_check = False
|
| |
- my.repos.add(newrepo)
|
| |
- my.repos.enableRepo(newrepo.id)
|
| |
- my.logger.info("Added %s repo from %s" % (repoid, repopath))
|
| |
-
|
| |
- if opts.repoid:
|
| |
- for repo in my.repos.repos.values():
|
| |
- if repo.id not in opts.repoid:
|
| |
- repo.disable()
|
| |
+ usage = """
|
| |
+ Check given repos for possible file/package conflicts
|
| |
+
|
| |
+ %s [-c <config file>] [-r <repoid>] [-r <repoid2>]
|
| |
+ """ % sys.argv[0]
|
| |
+ parser = OptionParser(usage=usage)
|
| |
+ parser.add_option("-c", "--config", default='/etc/yum.conf',
|
| |
+ help='config file to use (defaults to /etc/yum.conf)')
|
| |
+ parser.add_option("-r", "--repoid", default=[], action='append',
|
| |
+ help=("specify repo ids to query, can be specified multiple times "
|
| |
+ "(default is all enabled)"))
|
| |
+ parser.add_option("-t", "--tempcache", default=False, action="store_true",
|
| |
+ help="Use a temp dir for storing/accessing yum-cache")
|
| |
+ parser.add_option("--repofrompath", action="append",
|
| |
+ help=("specify repoid & paths of additional repositories "
|
| |
+ "- unique repoid and complete path required, can be specified multiple "
|
| |
+ "times. Example. --repofrompath=myrepo,/path/to/repo"))
|
| |
+ (opts, _) = parser.parse_args()
|
| |
+
|
| |
+ my = dnf.Base()
|
| |
+ my.read_all_repos()
|
| |
+
|
| |
+ temp_cachedir = None
|
| |
+ if os.geteuid() != 0 or opts.tempcache:
|
| |
+ temp_cachedir = tempfile.mkdtemp(prefix='conflicts')
|
| |
+ my.conf.cachedir = temp_cachedir
|
| |
+ else:
|
| |
+ my.conf.cachedir = dnf.yum.misc.getCacheDir()
|
| |
+
|
| |
+ if opts.repofrompath:
|
| |
+ # setup the fake repos
|
| |
+ for repo in opts.repofrompath:
|
| |
+ repoid, repopath = tuple(repo.split(','))
|
| |
+ if repopath[0] == '/':
|
| |
+ baseurl = 'file://' + repopath
|
| |
+ else:
|
| |
+ # This URL may be a redirect, like download.fedoraproject.org. We
|
| |
+ # need to resolve this redirect now, otherwise the redirect URL will
|
| |
+ # be used for every package header download and that a) will extremely
|
| |
+ # slow down download speed b) may cause consistency problems if not
|
| |
+ # always the same repo mirror is returned from the redirect
|
| |
+ with urlopen(repopath) as net_obj:
|
| |
+ baseurl = repopath = net_obj.geturl()
|
| |
+
|
| |
+ repopath = os.path.normpath(repopath)
|
| |
+ newrepo = dnf.repo.Repo(repoid)
|
| |
+ newrepo.name = repoid
|
| |
+ newrepo.baseurl = baseurl
|
| |
+ newrepo.basecachedir = my.conf.cachedir
|
| |
+ newrepo.metadata_expire = 0
|
| |
+ newrepo.timestamp_check = False
|
| |
+ my.repos.add(newrepo)
|
| |
+ print("Added %s repo from %s" % (repoid, repopath))
|
| |
+
|
| |
+ if opts.repoid:
|
| |
+ for repo in my.repos.values():
|
| |
+ if repo.id not in opts.repoid:
|
| |
+ repo.disable()
|
| |
+ else:
|
| |
+ repo.enable()
|
| |
+
|
| |
+ fulldict = defaultdict(list)
|
| |
+
|
| |
+ print('Getting complete filelist for:')
|
| |
+ for repo in my.repos.iter_enabled():
|
| |
+ if repo.metalink:
|
| |
+ print(repo.metalink)
|
| |
else:
|
| |
- repo.enable()
|
| |
-
|
| |
- fulldict = defaultdict(list)
|
| |
- #dirdict = defaultdict(list)
|
| |
-
|
| |
- print 'Getting complete filelist for:'
|
| |
- for repo in my.repos.listEnabled():
|
| |
- print repo.urls[0]
|
| |
-
|
| |
- for pkg in my.pkgSack.returnNewestByNameArch():
|
| |
- for fn in pkg.filelist:
|
| |
- fulldict[fn].append(pkg)
|
| |
-
|
| |
- # for fn in pkg.dirlist:
|
| |
- # dirdict[fn].append(pkg)
|
| |
- print '%u files found.\n' % len(fulldict)
|
| |
-
|
| |
- print 'Looking for duplicated filenames:'
|
| |
- filedict = {}
|
| |
- for fn in fulldict.keys():
|
| |
- if len(fulldict[fn]) >= 2:
|
| |
- filedict[fn] = fulldict[fn]
|
| |
- del fulldict # frees up a little memory
|
| |
- print '%u duplicates found.\n' % len(filedict)
|
| |
-
|
| |
- print 'Doing more advanced checks to see if these are real conflicts:'
|
| |
- count = 0
|
| |
- found = 0
|
| |
- total = len(filedict)
|
| |
- milestone = int(0.05 * total)
|
| |
- start = time.time()
|
| |
- last = start
|
| |
- package_conflicts = set()
|
| |
- confdict = {}
|
| |
- for (fn, pkglist) in filedict.iteritems():
|
| |
- #print '%d checking on %s ...' % (count, fn),
|
| |
- if (pkg_names_match(pkglist) or
|
| |
- sourcerpms_match(pkglist) or
|
| |
- obsolete_each_other(pkglist)):
|
| |
- pass
|
| |
- elif package_conflict(pkglist):
|
| |
- strlist = sorted([str(p) for p in pkglist])
|
| |
- package_conflicts.add('\n'.join(strlist))
|
| |
- elif file_conflict(fn, pkglist):
|
| |
- found += 1
|
| |
- confdict[fn] = pkglist
|
| |
-
|
| |
- # Timing / counting output
|
| |
- count += 1
|
| |
- if count % milestone == 0:
|
| |
- now = time.time()
|
| |
- percent = round(float(count*100) / total)
|
| |
- files_per_sec = float(milestone) / (now - last)
|
| |
- total_files_per_sec = float(count) / (now - start)
|
| |
- eta_sec = float(total - count) / total_files_per_sec
|
| |
- eta = str(datetime.timedelta(seconds=int(eta_sec)))
|
| |
- print "%3u%% complete (%6u/%6u, %5u/sec), %4u found - eta %s" % \
|
| |
- (percent, count, total, files_per_sec, found, eta)
|
| |
- last = now
|
| |
- del filedict
|
| |
- print "%u file conflicts found." % len(confdict)
|
| |
- print "%u package conflicts found." % len(package_conflicts)
|
| |
-
|
| |
- # Reduce the results to a dict like {"pkga,pkgb": [conflicting files], ...}
|
| |
- rbpp = defaultdict(list)# Results By Package-Pair
|
| |
- for (fn, pkglist) in confdict.iteritems():
|
| |
- pkgpair = '\n'.join(sorted([str(p) for p in pkglist]))
|
| |
- rbpp[pkgpair].append(fn)
|
| |
- del confdict
|
| |
-
|
| |
- print '\n== Package conflicts =='
|
| |
- for pkglist in package_conflicts:
|
| |
- print pkglist + "\n"
|
| |
-
|
| |
- print '\n== File conflicts, listed by conflicting packages =='
|
| |
- for (pkgpair, files) in rbpp.iteritems():
|
| |
- print pkgpair
|
| |
- for f in sorted(files):
|
| |
- print ' ' + f
|
| |
- print
|
| |
-
|
| |
- # delete cache
|
| |
- if temp_cachedir:
|
| |
- shutil.rmtree(temp_cachedir)
|
| |
-
|
| |
- if rbpp:
|
| |
- # file conflicts should be considered a failure, thus exit 1
|
| |
- sys.exit(1)
|
| |
+ print(repo.baseurl[0])
|
| |
+
|
| |
+ my.fill_sack()
|
| |
+ q = my.sack.query()
|
| |
+
|
| |
+ for package in q:
|
| |
+ if str(package.reponame) == "@System":
|
| |
+ continue
|
| |
+ #print('{} in repo {}'.format(package, package.reponame))
|
| |
+ for file_name in package.files:
|
| |
+ fulldict[file_name].append(package)
|
| |
+
|
| |
+ print('%u files found.\n' % len(fulldict))
|
| |
+
|
| |
+ print('Looking for duplicated filenames:')
|
| |
+ filedict = {}
|
| |
+ for file_name in fulldict.keys():
|
| |
+ if len(fulldict[file_name]) >= 2:
|
| |
+ filedict[file_name] = fulldict[file_name]
|
| |
+ del fulldict # frees up a little memory
|
| |
+ print('%u duplicates found.\n' % len(filedict))
|
| |
+
|
| |
+ print('Doing more advanced checks to see if these are real conflicts:')
|
| |
+ count = 0
|
| |
+ found = 0
|
| |
+ total = len(filedict)
|
| |
+ milestone = int(0.05 * total)
|
| |
+ start = time.time()
|
| |
+ last = start
|
| |
+ package_conflicts = set()
|
| |
+ confdict = {}
|
| |
+ for (file_name, package_list) in filedict.items():
|
| |
+ # /usr/lib/.build-id seems to be causing whole lot of false positives, so just skip it
|
| |
+ if file_name == "/usr/lib/.build-id":
|
| |
+ continue
|
| |
+ if (pkg_names_match(package_list) or
|
| |
+ sourcerpms_match(package_list) or
|
| |
+ obsolete_each_other(package_list)):
|
| |
+ pass
|
| |
+ elif package_conflict(package_list):
|
| |
+ strlist = sorted([str(p) for p in package_list])
|
| |
+ package_conflicts.add('\n'.join(strlist))
|
| |
+ elif file_conflict(file_name, package_list):
|
| |
+ found += 1
|
| |
+ confdict[file_name] = package_list
|
| |
+
|
| |
+ # Timing / counting output
|
| |
+ count += 1
|
| |
+ if count % milestone == 0:
|
| |
+ now = time.time()
|
| |
+ percent = round(float(count*100) / total)
|
| |
+ files_per_sec = float(milestone) / (now - last)
|
| |
+ total_files_per_sec = float(count) / (now - start)
|
| |
+ eta_sec = float(total - count) / total_files_per_sec
|
| |
+ eta = str(datetime.timedelta(seconds=int(eta_sec)))
|
| |
+ print("%3u%% complete (%6u/%6u, %5u/sec), %4u found - eta %s" % \
|
| |
+ (percent, count, total, files_per_sec, found, eta))
|
| |
+ last = now
|
| |
+ del filedict
|
| |
+ print("%u file conflicts found." % len(confdict))
|
| |
+ print("%u package conflicts found." % len(package_conflicts))
|
| |
+
|
| |
+ # Reduce the results to a dict like {"pkga,pkgb": [conflicting files], ...}
|
| |
+ rbpp = defaultdict(list)# Results By Package-Pair
|
| |
+ for (file_name, package_list) in confdict.items():
|
| |
+ pkgpair = '\n'.join(sorted([str(p) for p in package_list]))
|
| |
+ rbpp[pkgpair].append(file_name)
|
| |
+ del confdict
|
| |
+
|
| |
+ print('\n== Package conflicts ==')
|
| |
+ for package_list in package_conflicts:
|
| |
+ print(package_list + "\n")
|
| |
+
|
| |
+ print('\n== File conflicts, listed by conflicting packages ==')
|
| |
+ for (pkgpair, files) in rbpp.items():
|
| |
+ print(pkgpair)
|
| |
+ for file_name in sorted(files):
|
| |
+ print(' ' + file_name)
|
| |
+ print("")
|
| |
+
|
| |
+ # delete cache
|
| |
+ if temp_cachedir:
|
| |
+ shutil.rmtree(temp_cachedir)
|
| |
+
|
| |
+ if rbpp:
|
| |
+ # file conflicts should be considered a failure, thus exit 1
|
| |
+ sys.exit(1)
|
| |
So, this is heavily WIP kind of stuff. Package conflicts sort of works, file conflicts are skipped completely right now.
What am I struggling with:
Btw, I left code to be in python 2 for now, you can run this version of script on F >=30 after dnf install python2-dnf --releasever=29 . If you prefer to have code in python 3 asap, I can do that too.