Skip to content

Instantly share code, notes, and snippets.

@tomschr
Last active July 3, 2018 07:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomschr/23e3038d4678404072e75e4a12b81455 to your computer and use it in GitHub Desktop.
Save tomschr/23e3038d4678404072e75e4a12b81455 to your computer and use it in GitHub Desktop.
Returns a dependency graph for a given, installed RPM package
#!/usr/bin/env python3
"""Returns a dependency graph for a given, installed RPM package
Usage:
rpmdep [-h | --help]
rpmdep [-v ...] [options] RPM
Required Arguments.
RPM The installed RPM package
Options:
-h, --help Shows this help
-v Raise verbosity level
--version Prints the version
Examples:
# Returns the dependency tree of rpm
$ rpmdep rpm
coreutils
info
diffutils
fillup
grep
"""
__version__ = "0.2.0"
__author__ = "Thomas Schraitle <toms AT suse DOT de>"
from docopt import docopt, DocoptExit
import logging
from logging import (# BASIC_FORMAT,
CRITICAL,
DEBUG,
FATAL,
ERROR,
INFO,
NOTSET,
WARN,
WARNING,
)
from logging.config import dictConfig
import re
import subprocess
import sys
#: Map verbosity to log levels
LOGLEVELS = {None: WARNING, # 0
0: WARNING,
1: INFO,
2: DEBUG,
}
#: Default logging dict for :class:`logging.config.dictConfig`:
DEFAULT_LOGGING_DICT = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
# See https://docs.python.org/3.5/library/logging.html#logrecord-attributes
'format': '[%(levelname)s] %(name)s::%(funcName)s: %(message)s'
},
},
'handlers': {
'default': {
'level': 'NOTSET',
'formatter': 'standard',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stderr',
},
},
'loggers': {
'rpmdep': {
'handlers': ['default'],
'level': 'INFO',
'propagate': True
}
}
}
log = logging.getLogger("rpmdep")
def parsecli(cliargs=None):
"""Parse CLI arguments with docopt
:param list cliargs: List of commandline arguments
:return: dictionary from docopt
:rtype: dict
"""
version = "%s %s" % ("rpmdep", __version__)
args = docopt(__doc__, argv=cliargs, version=version)
# This is just needed to introduce some ignore lists later
# TODO: Remove this if-clause after ignore list works
if not args.get('--ignore'):
args['--ignore'] = None
args['--ignore'] = re.split("[;,]", args['--ignore']) if args['--ignore'] is not None else None
dictConfig(DEFAULT_LOGGING_DICT)
# logging.basicConfig(handler=logging.StreamHandler,)
log.setLevel(LOGLEVELS.get(args['-v'], logging.DEBUG))
log.debug("CLI result: %s", args)
return args
def checkargs(args):
"""Check arguments
:param args: dictionary from docopt parsing
"""
log.debug("Checking arguments")
if not args['RPM']:
raise DocoptExit("ERROR: Need a RPM name")
def main(cliargs=None):
"""Entry point for the application script
:param list cliargs: Arguments to parse or None (=use sys.argv)
:return: return codes from ``ERROR_CODES``
"""
try:
args = parsecli(cliargs)
checkargs(args)
result = process(args)
log.info("Done.")
return result
except KeyboardInterrupt:
return 10
def filterdeps(deps):
"""Filter dependency list
"""
ignore = ['bash']
for d in deps:
# Handle 'bash >= 4' and return 'bash'
d = d.split(" ")[0]
if d in ignore or d.startswith("/") or "(" in d:
continue
yield d
def rpmrequires(name, ignores=None, ignoreerror=True):
"""Return a list of dependencies for a given name
:param name: the RPM name
"""
ignores = ignores if ignores is not None else []
if name in ignores:
log.info("Ignoring %s", name)
return []
log.info("Investigating %s...", name)
try:
result = subprocess.check_output(['rpm', '-q', '--requires', name],
universal_newlines=True)
except subprocess.CalledProcessError:
if not ignoreerror:
raise RuntimeError("Cannot find %r RPM" % name)
return []
possibledeps = result.rstrip().split("\n")
return possibledeps
SEENRPMS = set(['distribution-release', 'atk', 'glib2', 'gtk2', 'libglade2',
'pango', 'awk', 'insserv', 'xsltproc', 'bash',
'ghostscript-library',
# general names:
'jaxp_parser_impl', 'jre', 'jpackage-utils', 'xml-commons-apis',
'xml-commons-resolver', 'libxml2', 'libxslt', 'java',
'jakarta-commons-io', 'jakarta-commons-logging'
]
)
def handlerpms(possibledeps, indent="", ignores=None):
"""Handle the RPM and sub dependencies
:param possibledeps: a list of possible RPM names
:param indent: indentation level (spaces)
"""
for d in filterdeps(possibledeps):
# Avoid recursion
if d in SEENRPMS:
continue
print(indent, d)
SEENRPMS.add(d)
subrpms = rpmrequires(d, ignores=ignores)
handlerpms(subrpms, indent=" ", ignores=ignores)
def process(args):
"""Process RPM
"""
log.info("Process dependencies...")
log.info("Ignored packages %s", args['--ignore'])
try:
possibledeps = rpmrequires(args['RPM'], args['--ignore'])
handlerpms(possibledeps, ignores=args['--ignore'])
except RuntimeError as error:
log.error(error)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment