You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
11 KiB

#! /usr/bin/python
# Copyright (C) 2009, 2010, 2011, 2012 Canonical Ltd.
# 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; version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from collections import defaultdict
import gzip
import optparse
import os
import re
import sys
import tempfile
import apt_pkg
default_base = '/home/ubuntu-archive/mirror/ubuntu'
default_suite = 'disco'
components = ('main', 'restricted', 'universe', 'multiverse')
# Cut-down RE from deb822.PkgRelation.
re_dep = re.compile(r'^\s*([a-zA-Z0-9.+\-]{2,})')
re_kernel_image_di = re.compile(r'^kernel-image-(.+)-di')
# Cheaper version of deb822.PkgRelation.parse_relations.
def parse_relation_packages(raw):
for or_dep in raw.split(','):
for dep in or_dep.split('|'):
match = re_dep.match(dep.strip())
if match:
yield match.group(1)
def primary_arches(suite):
return ('amd64', 'i386')
def ports_arches(suite):
if suite == 'lucid':
return ('armel', 'ia64', 'powerpc', 'sparc')
elif suite == 'precise':
return ('armel', 'armhf', 'powerpc')
elif suite in ('14.09', '14.09-factory'):
return ('armhf',)
elif suite in ('trusty', 'vivid', 'wily'):
return ('arm64', 'armhf', 'powerpc', 'ppc64el')
elif suite in ('xenial', 'yakkety'):
return ('arm64', 'armhf', 'powerpc', 'ppc64el', 's390x')
else:
return ('arm64', 'armhf', 'ppc64el', 's390x')
def read_tag_file(path):
tmp = tempfile.NamedTemporaryFile(prefix='checkrdepends.', delete=False)
try:
compressed = gzip.open(path)
try:
tmp.write(compressed.read())
finally:
compressed.close()
tmp.close()
with open(tmp.name) as uncompressed:
tag_file = apt_pkg.TagFile(uncompressed)
prev_name = None
prev_stanza = None
for stanza in tag_file:
try:
name = stanza['package']
except KeyError:
continue
if name != prev_name and prev_stanza is not None:
yield prev_stanza
prev_name = name
prev_stanza = stanza
if prev_stanza is not None:
yield prev_stanza
finally:
os.unlink(tmp.name)
def read_sources(path):
ret = {
'binary': {},
'source': defaultdict(set),
'build_deps': defaultdict(set),
}
binary = ret['binary']
source = ret['source']
build_deps = ret['build_deps']
for stanza in read_tag_file(path):
if 'binary' not in stanza:
continue
name = stanza['package']
binpkgs = [b.rstrip(',') for b in stanza['binary'].split()]
binary[name] = binpkgs
for binpkg in binpkgs:
source[binpkg].add(stanza['package'])
for field in ('build-depends', 'build-depends-indep'):
if field not in stanza:
continue
for depname in parse_relation_packages(stanza[field]):
build_deps[depname].add(name)
return ret
def read_packages(debs, path, sources, ignores=[], missing_ok=False):
ret = {'deps': defaultdict(dict)}
deps = ret['deps']
try:
for stanza in read_tag_file(path):
name = stanza['package']
for field in ('pre-depends', 'depends', 'recommends'):
if field not in stanza:
continue
for depname in parse_relation_packages(stanza[field]):
if depname not in debs:
continue
# skip dependencies that are built from the same source,
# when we're doing a sourceful removal.
if name in ignores:
continue
deps[depname][name] = (field, stanza['architecture'])
except IOError:
if not missing_ok:
raise
return ret
def read_di(debs, path):
ret = set()
try:
with open(path) as manifest:
for line in manifest:
udeb = line.split()[0]
ret.add(udeb)
match = re_kernel_image_di.match(udeb)
if match:
re_modules = re.compile(r'-modules-%s-di' % match.group(1))
for pkg in debs:
if re_modules.search(pkg):
ret.add(pkg)
except IOError:
pass
return ret
def pockets(opts):
if '-' in opts.suite:
return ('',)
else:
return ('', '-updates', '-security', '-backports')
def render_dep(name, field, arch):
ret = name
if field == "recommends":
ret += " (r)"
if arch == "all":
ret += " [all]"
return ret
def search(opts, pkgs):
for pocket in pockets(opts):
pocket_base = '%s/dists/%s%s' % (opts.archive_base, opts.suite, pocket)
if opts.arches:
arches = opts.arches
else:
arches = list(primary_arches(opts.suite))
if opts.ports:
arches.extend(ports_arches(opts.suite))
packages = defaultdict(dict)
sources = {}
for comp in components:
comp_base = '%s/%s' % (pocket_base, comp)
sources[comp] = read_sources('%s/source/Sources.gz' % comp_base)
if opts.binary:
debs = pkgs
ignores = []
else:
debs = set()
for src in pkgs:
for comp in components:
if src in sources[comp]['binary']:
debs.update(set(sources[comp]['binary'][src]))
ignores = debs = sorted(debs)
# Now we have the source<->binary mapping, we can read Packages
# files but only bother to remember the dependencies we need.
for comp in components:
comp_base = '%s/%s' % (pocket_base, comp)
di_comp = '%s/debian-installer' % comp
di_comp_base = '%s/%s' % (pocket_base, di_comp)
build_deps = sources[comp]['build_deps']
for deb in debs:
if opts.directory is not None:
out = open(os.path.join(opts.directory, deb), 'a')
else:
out = sys.stdout
# build dependencies
if deb in build_deps:
print("-- %s%s/%s build deps on %s:" %
(opts.suite, pocket, comp, deb), file=out)
for pkg in sorted(build_deps[deb]):
print(pkg, file=out)
# binary dependencies
for arch in arches:
if arch not in packages[comp]:
packages[comp][arch] = \
read_packages(debs,
'%s/binary-%s/Packages.gz' %
(comp_base, arch),
sources[comp], ignores)
if arch not in packages[di_comp]:
packages[di_comp][arch] = \
read_packages(debs,
'%s/binary-%s/Packages.gz' %
(di_comp_base, arch),
sources[comp], ignores,
missing_ok=True)
if comp == 'main':
di_images = \
read_di(debs,
'%s/installer-%s/current/images/'
'udeb.list' % (comp_base, arch))
di_deps = packages[di_comp][arch]['deps']
for udeb in di_images:
di_deps[udeb]['debian-installer-images'] = (
'depends', arch)
deps = packages[comp][arch]['deps']
di_deps = packages[di_comp][arch]['deps']
if deb in deps:
print("-- %s%s/%s %s deps on %s:" %
(opts.suite, pocket, comp, arch, deb), file=out)
for pkg, (field, pkgarch) in sorted(deps[deb].items()):
print(render_dep(pkg, field, pkgarch), file=out)
if deb in di_deps:
print("-- %s%s/%s %s deps on %s:" %
(opts.suite, pocket, di_comp, arch, deb),
file=out)
for pkg, (field, pkgarch) in sorted(
di_deps[deb].items()):
print(render_dep(pkg, field, pkgarch), file=out)
if opts.directory is not None:
out.close()
def main():
parser = optparse.OptionParser(usage='%prog [options] pkg [...]')
parser.add_option('-B', '--archive-base', dest='archive_base',
help=('archive base directory (default: %s)' %
default_base),
default=default_base)
parser.add_option('-s', '--suite', dest='suite',
help='suite to check (default: %s)' % default_suite,
default=default_suite)
parser.add_option('-a', '--arch', dest='arches', action='append',
help='check only this architecture '
'(may be given multiple times)')
parser.add_option('-b', '--binary', dest='binary', action='store_true',
help='treat arguments as binary packages, not source')
parser.add_option('--no-ports', dest='ports',
default=True, action='store_false',
help='skip ports architectures')
parser.add_option('-d', '--directory', dest='directory', metavar='DIR',
help='output to directory DIR (one file per package) '
'instead of standard output')
opts, args = parser.parse_args()
if 'CHECKRDEPENDS_PROFILE' in os.environ:
import profile
profile.run('search(opts, args)')
else:
search(opts, args)
if __name__ == '__main__':
main()