1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
#!/usr/bin/env python3
# remove virtual packages from disjunctions with real packages
# the idea to clean up a repository like this came from Helmut Grohne
#
# applying this solution to the problem might create uninstallable packages due
# to conflicts:
#
# Package: foo
# Depends: A | B, C
#
# Package: bar
# Provides: B
#
# Package: A
#
# Package: C
# Depends: D
#
# Package: D
# Conflicts: A
#
# In above case, B would be removed from the disjunction of package "foo",
# leaving only A. But through D, C conflicts with A, making "foo"
# uninstallable
import sys
sys.path.append("/usr/share/botch")
from util import get_fh_out, read_tag_file
import re
def extract_name_ver(pkgrel):
m = re.match(
r"\s*([a-zA-Z0-9][a-zA-Z0-9+.-]*)(?::[a-zA-Z0-9][a-zA-Z0-9-]*)?"
+ r"(?:\s+\(\s*(<<|<=|=|>=|>>|<|>).*?\))?.*",
pkgrel,
)
return (pkgrel, m.group(1), m.group(2))
def cleandisj(disj, real_pkgs, virtual_pkgs):
# do not handle simple disjunctions
if len(disj) == 1:
return disj
res = []
# reduce the disjunction to dependencies on real packages
extdisj = [extract_name_ver(pkgrel) for pkgrel in disj]
for pkgrel, pkg, ver in extdisj:
# versioned dependencies must be provided by real packages
if not ver and pkg not in real_pkgs:
continue
res.append(pkgrel)
if not res:
# if nothing remains return first virtual dependency that is provided
# by something or first versioned dependency
for pkgrel, pkg, ver in extdisj:
if ver or pkg in virtual_pkgs:
return [pkgrel]
# no package satisfying this disjunction was found so return original
return disj
else:
# if one or more real package remains, return only first
return [res[0]]
def cleansrcdisj(disj, real_pkgs, virtual_pkgs):
# for source packages, remove all but the first part of the disjunction as
# done by sbuild in setup_apt_archive in lib/Sbuild/ResolverBase.pm
# do not handle simple disjunctions
if len(disj) == 1:
return disj
extdisj = [extract_name_ver(pkgrel) for pkgrel in disj]
res = [extdisj[0]]
# keep disjunctions like "foo (rel x) | foo (rel y)"
if res[0][2]:
for pkgrel, pkg, ver in extdisj[1:]:
# compare package name with the first
if ver and pkg == res[0][1]:
res.append((pkgrel,))
return [r[0] for r in res]
def modify_field(field, real_pkgs, virtual_pkgs, cleanfunc):
# we do it without regexes so that we can assemble the original
# dependency string later
deps = [[pkgrel for pkgrel in disj.split("|")] for disj in field.split(",")]
deps = [cleanfunc(disj, real_pkgs, virtual_pkgs) for disj in deps]
return ",".join(["|".join(disj) for disj in deps])
def remove_virtual_disjunctions(
inPackages, inSources, outPackages, outSources, remove_nonvirtual, verbose=False
):
real_pkgs = set()
virtual_pkgs = set()
# create a list of real packages
for pkg in inPackages:
real_pkgs.add(pkg["Package"])
if not pkg.get("Provides"):
continue
for pkg in pkg["Provides"].split(","):
virtual_pkgs.add(pkg.strip())
# modify binary dependencies
for pkg in inPackages:
for field in ["Depends", "Pre-Depends"]:
if pkg.get(field):
if remove_nonvirtual:
# if --remove-nonvirtual was given, use source package
# behaviour for binary packages as well
pkg[field] = modify_field(
pkg[field], real_pkgs, virtual_pkgs, cleansrcdisj
)
else:
pkg[field] = modify_field(
pkg[field], real_pkgs, virtual_pkgs, cleandisj
)
# modify build dependencies
for pkg in inSources:
for field in ["Build-Depends", "Build-Depends-Indep", "Build-Depends-Arch"]:
if pkg.get(field):
pkg[field] = modify_field(
pkg[field], real_pkgs, virtual_pkgs, cleansrcdisj
)
with outPackages as outfile:
for pkg in inPackages:
pkg.dump(outfile)
outfile.write(b"\n")
with outSources as outfile:
for pkg in inSources:
pkg.dump(outfile)
outfile.write(b"\n")
return True
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description=(
"remove virtual packages from disjunctions with real " + "packages"
)
)
parser.add_argument("inPackages", type=read_tag_file, help="input Packages files")
parser.add_argument("inSources", type=read_tag_file, help="input Sources files")
parser.add_argument("outPackages", type=get_fh_out, help="output Packages file")
parser.add_argument("outSources", type=get_fh_out, help="output Sources file")
parser.add_argument(
"--remove-nonvirtual",
action="store_true",
help="also remove all but the first alternative from "
"binary packages. This applies the same "
"algorithm to binary packages as is applied to "
"source packages",
)
parser.add_argument("--verbose", action="store_true", help="be verbose")
args = parser.parse_args()
ret = remove_virtual_disjunctions(
args.inPackages,
args.inSources,
args.outPackages,
args.outSources,
args.remove_nonvirtual,
args.verbose,
)
exit(not ret)
|