#!/usr/bin/python3 # # Copyright (C) 2011, Stefano Rivera # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import optparse import sys from distro_info import DistroDataOutdated from ubuntutools.logger import Logger from ubuntutools.misc import (system_distribution, vendor_to_distroinfo, codename_to_distribution) from ubuntutools.rdepends import query_rdepends, RDependsException DEFAULT_MAX_DEPTH = 10 # We want avoid any infinite loop... def main(): system_distro_info = vendor_to_distroinfo(system_distribution())() try: default_release = system_distro_info.devel() except DistroDataOutdated as e: Logger.warn(e) default_release = 'unstable' parser = optparse.OptionParser( '%prog [options] package', description="List reverse-dependencies of package. " "If the package name is prefixed with src: then the " "reverse-dependencies of all the binary packages that " "the specified source package builds will be listed.") parser.add_option('-r', '--release', metavar='RELEASE', default=default_release, help='Query dependencies in RELEASE. ' 'Default: %s' % default_release) parser.add_option('-R', '--without-recommends', action='store_false', dest='recommends', default=True, help='Only consider Depends relationships, ' 'not Recommends') parser.add_option('-s', '--with-suggests', action='store_true', dest='suggests', default=False, help='Also consider Suggests relationships') parser.add_option('-b', '--build-depends', action='store_const', dest='arch', const='source', help='Query build dependencies (synonym for --arch=source)') parser.add_option('-a', '--arch', metavar='ARCH', default='any', help='Query dependencies in ARCH. ' 'Default: any') parser.add_option('-c', '--component', metavar='COMPONENT', action='append', help='Only consider reverse-dependencies in COMPONENT. ' 'Can be specified multiple times. Default: all') parser.add_option('-l', '--list', action='store_true', default=False, help='Display a simple, machine-readable list') parser.add_option('-u', '--service-url', metavar='URL', dest='server', default=None, help='Reverse Dependencies webservice URL. ' 'Default: UbuntuWire') parser.add_option('-x', '--recursive', action='store_true', dest='recursive', default=False, help='Consider to find reverse dependencies recursively.') parser.add_option('-d', '--recursive-deph', type="int", metavar='RECURSIVE_DEPTH', dest='recursive_depth', default=DEFAULT_MAX_DEPTH, help='If recusive, you can specify the depth.') options, args = parser.parse_args() if len(args) != 1: parser.error("One (and only one) package must be specified") package = args[0] opts = {} if options.server is not None: opts['server'] = options.server # Convert unstable/testing aliases to codenames: distribution = codename_to_distribution(options.release) if not distribution: parser.error('Unknown release codename %s' % options.release) distro_info = vendor_to_distroinfo(distribution)() try: options.release = distro_info.codename(options.release, default=options.release) except DistroDataOutdated: # We already printed a warning pass def query(package): try: return query_rdepends(package, options.release, options.arch, **opts) except RDependsException as e: Logger.error(str(e)) sys.exit(1) def filter_out_fiels(data, fields): for field in list(data.keys()): if field not in fields: del data[field] def filter_out_component(data, component): for field, rdeps in list(data.items()): filtered = [rdep for rdep in rdeps if rdep['Component'] in component] if not filtered: del data[field] else: data[field] = filtered if options.arch == 'source': fields = ['Reverse-Build-Depends', 'Reverse-Build-Depends-Indep'] else: fields = ['Reverse-Depends'] if options.recommends: fields.append('Reverse-Recommends') if options.suggests: fields.append('Reverse-Suggests') def build_results(package, result, fields, component, recursive): data = query(package) if not data: return result[package] = data if fields: filter_out_fiels(result[package], fields) if component: filter_out_component(result[package], component) if recursive > 0: for rdeps in result[package].values(): for rdep in rdeps: build_results( rdep['Package'], result, fields, component, recursive - 1) result = {} build_results( package, result, fields, options.component, options.recursive and options.recursive_depth or 0) if options.list: display_consise(result) else: display_verbose(package, result) def display_verbose(package, values): if not values: print("No reverse dependencies found") return def print_field(field): print(field) print('=' * len(field)) def print_package(values, package, arch, dependency, offset=0): line = ' ' * offset + '* %s' % package if all_archs and set(arch) != all_archs: line += ' [%s]' % ' '.join(sorted(arch)) if dependency: if len(line) < 30: line += ' ' * (30 - len(line)) line += ' (for %s)' % dependency print(line) data = values.get(package) if data: offset = offset + 1 for rdeps in data.values(): for rdep in rdeps: print_package(values, rdep['Package'], rdep.get('Architectures', all_archs), rdep.get('Dependency'), offset) all_archs = set() # This isn't accurate, but we make up for it by displaying what we found for data in values.values(): for rdeps in data.values(): for rdep in rdeps: if 'Architectures' in rdep: all_archs.update(rdep['Architectures']) for field, rdeps in values[package].items(): print_field(field) rdeps.sort(key=lambda x: x['Package']) for rdep in rdeps: print_package(values, rdep['Package'], rdep.get('Architectures', all_archs), rdep.get('Dependency')) print() if all_archs: print("Packages without architectures listed are " "reverse-dependencies in: %s" % ', '.join(sorted(list(all_archs)))) def display_consise(values): result = set() for data in values.values(): for rdeps in data.values(): for rdep in rdeps: result.add(rdep['Package']) print('\n'.join(sorted(list(result)))) if __name__ == '__main__': main()