#!/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. # pylint: disable=invalid-name # pylint: enable=invalid-name import argparse import sys from distro_info import DistroDataOutdated from ubuntutools import getLogger from ubuntutools.misc import codename_to_distribution, system_distribution, vendor_to_distroinfo from ubuntutools.rdepends import RDependsException, query_rdepends Logger = getLogger() 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.warning(e) default_release = "unstable" 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 = argparse.ArgumentParser(description=description) parser.add_argument( "-r", "--release", default=default_release, help="Query dependencies in RELEASE. Default: %(default)s", ) parser.add_argument( "-R", "--without-recommends", action="store_false", dest="recommends", help="Only consider Depends relationships, not Recommends", ) parser.add_argument( "-s", "--with-suggests", action="store_true", help="Also consider Suggests relationships" ) parser.add_argument( "-b", "--build-depends", action="store_true", help="Query build dependencies (synonym for --arch=source)", ) parser.add_argument( "-a", "--arch", default="any", help="Query dependencies in ARCH. Default: any" ) parser.add_argument( "-c", "--component", action="append", help="Only consider reverse-dependencies in COMPONENT. " "Can be specified multiple times. Default: all", ) parser.add_argument( "-l", "--list", action="store_true", help="Display a simple, machine-readable list" ) parser.add_argument( "-u", "--service-url", metavar="URL", dest="server", default=None, help="Reverse Dependencies webservice URL. Default: UbuntuWire", ) parser.add_argument( "-x", "--recursive", action="store_true", help="Consider to find reverse dependencies recursively.", ) parser.add_argument( "-d", "--recursive-depth", type=int, default=DEFAULT_MAX_DEPTH, help="If recusive, you can specify the depth.", ) parser.add_argument("package") options = parser.parse_args() 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(f"Unknown release codename {options.release}") distro_info = vendor_to_distroinfo(distribution)() try: options.release = distro_info.codename(options.release, default=options.release) except DistroDataOutdated: # We already logged a warning pass if options.build_depends: options.arch = "source" if options.arch == "source": fields = [ "Reverse-Build-Depends", "Reverse-Build-Depends-Indep", "Reverse-Build-Depends-Arch", "Reverse-Testsuite-Triggers", ] else: fields = ["Reverse-Depends"] if options.recommends: fields.append("Reverse-Recommends") if options.with_suggests: fields.append("Reverse-Suggests") def build_results(package, result, fields, component, recursive): try: data = query_rdepends(package, options.release, options.arch, **opts) except RDependsException as e: Logger.error(str(e)) sys.exit(1) if not data: return if fields: data = {k: v for k, v in data.items() if k in fields} if component: data = { k: [rdep for rdep in v if rdep["Component"] in component] for k, v in data.items() } data = {k: v for k, v in data.items() if v} result[package] = data 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( options.package, result, fields, options.component, options.recursive and options.recursive_depth or 0, ) if options.list: display_consise(result) else: display_verbose(options.package, result) def display_verbose(package, values): if not values: Logger.info("No reverse dependencies found") return def log_package(values, package, arch, dependency, visited, offset=0): line = f"{' ' * offset}* {package}" if all_archs and set(arch) != all_archs: line += f" [{' '.join(sorted(arch))}]" if dependency: if len(line) < 30: line += " " * (30 - len(line)) line += f" (for {dependency})" Logger.info(line) if package in visited: return visited = visited.copy().add(package) data = values.get(package) if data: offset = offset + 1 for rdeps in data.values(): for rdep in rdeps: log_package( values, rdep["Package"], rdep.get("Architectures", all_archs), rdep.get("Dependency"), visited, 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(): Logger.info("%s", field) Logger.info("%s", "=" * len(field)) rdeps.sort(key=lambda x: x["Package"]) for rdep in rdeps: log_package( values, rdep["Package"], rdep.get("Architectures", all_archs), rdep.get("Dependency"), {package}, ) Logger.info("") if all_archs: Logger.info( "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"]) Logger.info("\n".join(sorted(list(result)))) if __name__ == "__main__": main()