ubuntu-dev-tools/reverse-depends

253 lines
7.8 KiB
Python
Executable File

#!/usr/bin/python3
#
# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
#
# 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()