diff options
author | Maxim Sobolev <sobomax@FreeBSD.org> | 2002-01-13 12:05:07 +0000 |
---|---|---|
committer | Maxim Sobolev <sobomax@FreeBSD.org> | 2002-01-13 12:05:07 +0000 |
commit | 2bdeafee662db373db1bc3d131c6b8b5fe7a890f (patch) | |
tree | d8bcd5641c0087c093f59976caaf7f93d945a316 /Tools | |
parent | 05cbf8f60731ffb92cc0ecbf5110f9acb9cd0c98 (diff) |
Notes
Diffstat (limited to 'Tools')
-rwxr-xr-x | Tools/scripts/chkdepschain.py | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/Tools/scripts/chkdepschain.py b/Tools/scripts/chkdepschain.py new file mode 100755 index 000000000000..446031196538 --- /dev/null +++ b/Tools/scripts/chkdepschain.py @@ -0,0 +1,294 @@ +#!/usr/local/bin/python + +import os, os.path, popen2, types, sys, getopt, pickle + +# Global constants and semi-constants +PKG_DBDIR = '/var/db/pkg' +PORTSDIR = '/usr/ports' +ROOT_PORTMK = '/usr/share/mk/bsd.port.mk' +PLIST_FILE = '+CONTENTS' +ORIGIN_PREF = '@comment ORIGIN:' +MAKEFILE = 'Makefile' +MAKE = 'make' + +# Global variables +# +# PortInfo cache +picache = {} + +# Useful aliases +op_isdir = os.path.isdir +op_join = os.path.join +op_split = os.path.split +op_abspath = os.path.abspath + + +# +# Query origin of specified installed package. +# +def getorigin(pkg): + plist = op_join(PKG_DBDIR, pkg, PLIST_FILE) + for line in open(plist).xreadlines(): + if line.startswith(ORIGIN_PREF): + origin = line[len(ORIGIN_PREF):].strip() + break + else: + raise RuntimeError('%s: no origin recorded' % plist) + + return origin + + +# +# Execute external command and return content of its stdout. +# +def getcmdout(cmdline, filterempty = 1): + pipe = popen2.Popen3(cmdline, 1) + results = pipe.fromchild.readlines() + for stream in (pipe.fromchild, pipe.tochild, pipe.childerr): + stream.close() + + if pipe.wait() != 0: + if type(cmdline) is types.StringType: + cmdline = (cmdline) + raise IOError('%s: external command returned non-zero error code' % \ + cmdline[0]) + + if filterempty != 0: + results = filter(lambda line: len(line.strip()) > 0, results) + + return results + + +# +# For a specified path (either dir or makefile) query requested make(1) +# variables and return them as a tuple in exactly the same order as they +# were specified in function call, i.e. querymakevars('foo', 'A', 'B') will +# return a tuple with a first element being the value of A variable, and +# the second one - the value of B. +# +def querymakevars(path, *vars): + if op_isdir(path): + path = op_join(path, MAKEFILE) + dirname, makefile = op_split(path) + cmdline = [MAKE, '-f', makefile] + savedir = os.getcwd() + os.chdir(dirname) + try: + for var in vars: + cmdline.extend(('-V', var)) + + results = map(lambda line: line.strip(), getcmdout(cmdline, 0)) + finally: + os.chdir(savedir) + + return tuple(results) + + +def parsedeps(depstr): + return tuple(map(lambda dep: dep.split(':'), depstr.split())) + + +# +# For a specified port return either a new instance of the PortInfo class, +# or existing instance from the cache. +# +def getpi(path): + path = op_abspath(path) + if not picache.has_key(path): + picache[path] = PortInfo(path) + return picache[path] + + +# +# Format text string according to requested constrains. Useful when you have +# to display multi-line, variable width message on terminal. +# +def formatmsg(msg, wrapat = 78, seclindent = 0): + words = msg.split() + result = '' + isfirstline = 1 + position = 0 + for word in words: + if position + 1 + len(word) > wrapat: + result += '\n' + ' ' * seclindent + word + position = seclindent + len(word) + isfirstline = 0 + else: + if position != 0: + result += ' ' + position += 1 + result += word + position += len(word) + + return result + + +# +# Class that contain main info about the port +# +class PortInfo: + PKGNAME = None + CATEGORIES = None + MAINTAINER = None + BUILD_DEPENDS = None + LIB_DEPENDS = None + RUN_DEPENDS = None + PKGORIGIN = None + # Cached values, to speed-up things + __deps = None + __bt_deps = None + __rt_deps = None + + def __init__(self, path): + self.PKGNAME, self.CATEGORIES, self.MAINTAINER, self.BUILD_DEPENDS, \ + self.LIB_DEPENDS, self.RUN_DEPENDS, self.PKGORIGIN = \ + querymakevars(path, 'PKGNAME', 'CATEGORIES', 'MAINTAINER', \ + 'BUILD_DEPENDS', 'LIB_DEPENDS', 'RUN_DEPENDS', 'PKGORIGIN') + + def __str__(self): + return 'PKGNAME:\t%s\nCATEGORIES:\t%s\nMAINTAINER:\t%s\n' \ + 'BUILD_DEPENDS:\t%s\nLIB_DEPENDS:\t%s\nRUN_DEPENDS:\t%s\n' \ + 'PKGORIGIN:\t%s' % (self.PKGNAME, self.CATEGORIES, self.MAINTAINER, \ + self.BUILD_DEPENDS, self.LIB_DEPENDS, self.RUN_DEPENDS, \ + self.PKGORIGIN) + + def getdeps(self): + if self.__deps == None: + result = [] + for depstr in self.BUILD_DEPENDS, self.LIB_DEPENDS, \ + self.RUN_DEPENDS: + deps = tuple(map(lambda dep: dep[1], parsedeps(depstr))) + result.append(deps) + self.__deps = tuple(result) + return self.__deps + + def get_bt_deps(self): + if self.__bt_deps == None: + topdeps = self.getdeps() + topdeps = list(topdeps[0] + topdeps[1]) + for dep in topdeps[:]: + botdeps = filter(lambda dep: dep not in topdeps, \ + getpi(dep).get_rt_deps()) + topdeps.extend(botdeps) + self.__bt_deps = tuple(topdeps) + return self.__bt_deps + + def get_rt_deps(self): + if self.__rt_deps == None: + topdeps = self.getdeps() + topdeps = list(topdeps[1] + topdeps[2]) + for dep in topdeps[:]: + botdeps = filter(lambda dep: dep not in topdeps, \ + getpi(dep).get_rt_deps()) + topdeps.extend(botdeps) + self.__rt_deps = tuple(topdeps) + return self.__rt_deps + + +def write_msg(*message): + if type(message) == types.StringType: + message = message, + message = tuple(filter(lambda line: line != None, message)) + sys.stderr.writelines(message) + + +# +# Print optional message and usage information and exit with specified exit +# code. +# +def usage(code, msg = None): + myname = os.path.basename(sys.argv[0]) + if msg != None: + msg = str(msg) + '\n' + write_msg(msg, "Usage: %s [-rb] [-l|L cachefile] [-s cachefile]\n" % \ + myname) + sys.exit(code) + + +def main(): + global picache + + # Parse command line arguments + try: + opts, args = getopt.getopt(sys.argv[1:], 'rbl:L:s:') + except getopt.GetoptError, msg: + usage(2, msg) + + if len(args) > 0 or len(opts) == 0 : + usage(2) + + cachefile = None + chk_bt_deps = 0 + chk_rt_deps = 0 + for o, a in opts: + if o == '-b': + chk_bt_deps = 1 + elif o == '-r': + chk_rt_deps = 1 + elif o in ('-l', '-L'): + # Try to load saved PortInfo cache + try: + picache = pickle.load(open(a)) + except: + picache = {} + try: + if o == '-L': + os.unlink(a) + except: + pass + elif o == '-s': + cachefile = a + + # Load origins of all installed packages + instpkgs = os.listdir(PKG_DBDIR) + instpkgs = filter(lambda pkg: op_isdir(op_join(PKG_DBDIR, pkg)), instpkgs) + origins = {} + for pkg in instpkgs: + origins[pkg] = getorigin(pkg) + + # Resolve dependencies for the current port + info = getpi(os.getcwd()) + deps = [] + if chk_bt_deps != 0: + deps.extend(filter(lambda d: d not in deps, info.get_bt_deps())) + if chk_rt_deps != 0: + deps.extend(filter(lambda d: d not in deps, info.get_rt_deps())) + + # Perform validation + nerrs = 0 + nwarns = 0 + for dep in deps: + pi = getpi(dep) + if pi.PKGORIGIN not in origins.values(): + print formatmsg(seclindent = 7 * 0, msg = \ + 'Error: package %s (%s) belongs to dependency chain, but ' \ + 'isn\'t installed.' % (pi.PKGNAME, pi.PKGORIGIN)) + nerrs += 1 + elif pi.PKGNAME not in origins.keys(): + for instpkg in origins.keys(): + if origins[instpkg] == pi.PKGORIGIN: + break + print formatmsg(seclindent = 9 * 0, msg = \ + 'Warning: package %s (%s) belongs to dependency chain, but ' \ + 'package %s is installed instead. Perhaps it\'s an older ' \ + 'version - update is highly recommended.' % (pi.PKGNAME, \ + pi.PKGORIGIN, instpkg)) + nwarns += 1 + + # Save PortInfo cache if requested + if cachefile != None: + try: + pickle.dump(picache, open(cachefile, 'w')) + except: + pass + + return nerrs + + +PORTSDIR, PKG_DBDIR = querymakevars(ROOT_PORTMK, 'PORTSDIR', 'PKG_DBDIR') + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + pass |