aboutsummaryrefslogtreecommitdiff
path: root/Tools
diff options
context:
space:
mode:
authorMaxim Sobolev <sobomax@FreeBSD.org>2002-01-13 12:05:07 +0000
committerMaxim Sobolev <sobomax@FreeBSD.org>2002-01-13 12:05:07 +0000
commit2bdeafee662db373db1bc3d131c6b8b5fe7a890f (patch)
treed8bcd5641c0087c093f59976caaf7f93d945a316 /Tools
parent05cbf8f60731ffb92cc0ecbf5110f9acb9cd0c98 (diff)
Notes
Diffstat (limited to 'Tools')
-rwxr-xr-xTools/scripts/chkdepschain.py294
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