13059 pkgdep generate should analyze python modules using the version of python they use
authorBrock Pytlik <bpytlik@sun.com>
Wed, 20 Jan 2010 16:09:18 -0800
changeset 1674 faf484754465
parent 1673 6897b80bca9e
child 1675 8caa149137be
13059 pkgdep generate should analyze python modules using the version of python they use 13835 api_inst no longer needed by find_package 13843 ModuleInfo __str__ should work 13838 make_paths in dependencies.py needs to cope with rp's which are a string, instead of a list
src/modules/flavor/depthlimitedmf.py
src/modules/flavor/depthlimitedmf24.py
src/modules/flavor/python.py
src/modules/flavor/script.py
src/modules/publish/dependencies.py
src/pkgdefs/Makefile
src/pkgdefs/SUNWipkg/prototype
src/pkgdep.py
src/setup.py
src/tests/api/t_dependencies.py
src/tests/baseline.txt
src/tests/cli/t_pkgdep.py
--- a/src/modules/flavor/depthlimitedmf.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/modules/flavor/depthlimitedmf.py	Wed Jan 20 16:09:18 2010 -0800
@@ -2,7 +2,7 @@
 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Python
 # Software Foundation; All Rights Reserved
 #
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 
 
@@ -10,9 +10,6 @@
 modules and discovers where a module might be loaded instead of determining
 which path contains a module to be loaded."""
 
-import dis
-import imp
-import marshal
 import modulefinder
 import os
 import sys
@@ -59,8 +56,8 @@
                 return ["%s%s" % (self.name, suf) for suf in self.suffixes]
 
         def __str__(self):
-                return "name:%s py:%s pyc:%s pkg:%s dirs:%s" % (self.name,
-                    self.py, self.pyc, self.pkg, len(self.dirs))
+                return "name:%s suffixes:%s dirs:%s" % (self.name,
+                    " ".join(self.suffixes), len(self.dirs))
 
 class DepthLimitedModuleFinder(modulefinder.ModuleFinder):
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/flavor/depthlimitedmf24.py	Wed Jan 20 16:09:18 2010 -0800
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Python
+# Software Foundation; All Rights Reserved
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+
+
+"""A standalone version of ModuleFinder which limits the depth of exploration
+for loaded modules and discovers where a module might be loaded instead of
+determining which path contains a module to be loaded.  It is designed to be run
+by python2.4 or python2.5 against 2.4 or 2.5 modules.  To communicate its
+results to the process which ran it, it prints output to stdout.  The format is
+to start a line with 'DEP ' if it contains information about a dependency, and
+'ERR ' if it contains information about a module it couldn't analyze."""
+
+# This module cannot import other pkg modules because running the 2.4 or 2.5
+# interpreter will overwrite the pyc files for some of the other flavor modules.
+# With 2.6, the -B option can be added to the command line invocation for the
+# subprocess and the interpreter won't overwrite pyc files.
+
+import dis
+import modulefinder
+import os
+import sys
+
+from modulefinder import LOAD_CONST, IMPORT_NAME, STORE_NAME, STORE_GLOBAL, \
+    STORE_OPS
+
+python_path = "PYTHONPATH"
+
+class ModuleInfo(object):
+        """This class contains information about from where a python module
+        might be loaded."""
+
+        def __init__(self, name, dirs, builtin=False):
+                """Build a ModuleInfo object.
+
+                The 'name' parameter is the name of the module.
+
+                The 'dirs' parameter is the list of directories where the module
+                might be found.
+
+                The 'builtin' parameter sets whether the module is a python
+                builtin (like sys)."""
+
+                self.name = name
+                self.builtin = builtin
+                self.suffixes = [".py", ".pyc", ".pyo", "/__init__.py"]
+                self.dirs = sorted(dirs)
+
+        def make_package(self):
+                """Declare that this module is a package."""
+
+                if self.dirs:
+                        self.suffixes = ["/__init__.py"]
+                else:
+                        self.suffixes = []
+
+        def get_package_dirs(self):
+                """Get the directories where this package might be defined."""
+
+                return [os.path.join(p, self.name) for p in self.dirs]
+
+        def get_file_names(self):
+                """Return all the file names under which this module might be
+                found."""
+
+                return ["%s%s" % (self.name, suf) for suf in self.suffixes]
+
+        def __str__(self):
+                return "name:%s suffixes:%s dirs:%s" % (self.name,
+                    " ".join(self.suffixes), len(self.dirs))
+
+class DepthLimitedModuleFinder(modulefinder.ModuleFinder):
+
+        def __init__(self, proto_dir, *args, **kwargs):
+                """Produce a module finder that ignores PYTHONPATH and only
+                reports the direct imports of a module."""
+
+                # Check to see whether a python path has been set.
+                if python_path in os.environ:
+                        py_path = [
+                            os.path.normpath(fp)
+                            for fp in os.environ[python_path].split(os.pathsep)
+                        ]
+                else:
+                        py_path = []
+
+                # Remove any paths that start with the defined python paths.
+                new_path = [
+                    fp
+                    for fp in sys.path
+                    if not self.startswith_path(fp, py_path)
+                ]
+
+                # Map the standard system paths into the proto area.
+                new_path = [
+                    os.path.join(proto_dir, fp.lstrip("/"))
+                    for fp in new_path
+                ]
+
+                modulefinder.ModuleFinder.__init__(self, path=new_path,
+                    *args, **kwargs)
+                self.proto_dir = proto_dir
+
+        @staticmethod
+        def startswith_path(path, lst):
+                for l in lst:
+                        if path.startswith(l):
+                                return True
+                return False
+
+        def run_script(self, pathname):
+                """Find all the modules the module at pathname directly
+                imports."""
+
+                fp = open(pathname, "r")
+                return self.load_module('__main__', fp, pathname)
+
+        def load_module(self, fqname, fp, pathname):
+                """This code has been slightly modified from the function of
+                the parent class. Specifically, it checks the current depth
+                of the loading and halts if it exceeds the depth that was given
+                to run_script."""
+
+                self.msgin(2, "load_module", fqname, fp and "fp", pathname)
+                co = compile(fp.read()+'\n', pathname, 'exec')
+                m = self.add_module(fqname)
+                m.__file__ = pathname
+                res = []
+                if co:
+                        if self.replace_paths:
+                                co = self.replace_paths_in_code(co)
+                        m.__code__ = co
+                        res.extend(self.scan_code(co, m))
+                self.msgout(2, "load_module ->", m)
+                return res
+
+        def scan_code(self, co, m):
+                res = []
+                code = co.co_code
+                n = len(code)
+                i = 0
+                fromlist = None
+                while i < n:
+                        c = code[i]
+                        i = i+1
+                        op = ord(c)
+                        if op >= dis.HAVE_ARGUMENT:
+                                oparg = ord(code[i]) + ord(code[i+1])*256
+                                i = i+2
+                        if op == LOAD_CONST:
+                                # An IMPORT_NAME is always preceded by a
+                                # LOAD_CONST, it's a tuple of "from" names, or
+                                # None for a regular import.  The tuple may
+                                # contain "*" for "from <mod> import *"
+                                fromlist = co.co_consts[oparg]
+                        elif op == IMPORT_NAME:
+                                assert fromlist is None or \
+                                    type(fromlist) is tuple
+                                name = co.co_names[oparg]
+                                have_star = 0
+                                if fromlist is not None:
+                                        if "*" in fromlist:
+                                                have_star = 1
+                                        fromlist = [
+                                            f for f in fromlist if f != "*"
+                                        ]
+                                res.extend(self._safe_import_hook(name, m,
+                                    fromlist))
+                        elif op in STORE_OPS:
+                                # keep track of all global names that are
+                                # assigned to
+                                name = co.co_names[oparg]
+                                m.globalnames[name] = 1
+                for c in co.co_consts:
+                    if isinstance(c, type(co)):
+                        res.extend(self.scan_code(c, m))
+                return res
+
+
+        def _safe_import_hook(self, name, caller, fromlist, level=-1):
+                """Wrapper for self.import_hook() that won't raise ImportError.
+                """
+
+                res = []
+                if name in self.badmodules:
+                        self._add_badmodule(name, caller)
+                        return
+                try:
+                        res.extend(self.import_hook(name, caller, level=level))
+                except ImportError, msg:
+                        self.msg(2, "ImportError:", str(msg))
+                        self._add_badmodule(name, caller)
+                else:
+                        if fromlist:
+                                for sub in fromlist:
+                                        if sub in self.badmodules:
+                                                self._add_badmodule(sub, caller)
+                                                continue
+                                        res.extend(self.import_hook(name,
+                                            caller, [sub], level=level))
+                return res
+
+        def import_hook(self, name, caller=None, fromlist=None, level=-1):
+                """Find all the modules that importing name will import."""
+
+                self.msg(3, "import_hook", name, caller, fromlist, level)
+                parent = self.determine_parent(caller)
+                q, tail = self.find_head_package(parent, name)
+                if not tail:
+                        # If q is a builtin module, don't report it because it
+                        # doesn't live in the normal module space and it's part
+                        # of python itself, which is handled by a different
+                        # kind of dependency.
+                        if q.builtin:
+                                return []
+                        else:
+                                return [q]
+                res = self.load_tail(name, q, tail)
+                q.make_package()
+                res.append(q)
+                return res
+
+        def import_module(self, partname, fqname, parent):
+                """Find where this module lives relative to its parent."""
+
+                parent_dirs = None
+                self.msgin(3, "import_module", partname, fqname, parent)
+                try:
+                        m = self.modules[fqname]
+                except KeyError:
+                        pass
+                else:
+                        self.msgout(3, "import_module ->", m)
+                        return m
+                if fqname in self.badmodules:
+                        self.msgout(3, "import_module -> None")
+                        return None
+                if parent:
+                        if not parent.dirs:
+                                self.msgout(3, "import_module -> None")
+                                return None
+                        else:
+                                parent_dirs = parent.get_package_dirs()
+                try:
+                        mod = self.find_module(partname, parent_dirs, parent)
+                except ImportError:
+                        self.msgout(3, "import_module ->", None)
+                        return None
+                return mod
+
+        def find_module(self, name, path, parent=None):
+                """Calculate the potential paths on the file system where the
+                module could be found."""
+
+                if path is None:
+                    if name in sys.builtin_module_names or name == "__future__":
+                            return ModuleInfo(name, [], builtin=True)
+                    path = self.path
+                return ModuleInfo(name, path)
+
+        def load_tail(self, name, q, tail):
+                """Determine where each component of a multilevel import would
+                be found on the file system."""
+
+                self.msgin(4, "load_tail", q, tail)
+                m = q
+                res = []
+                while tail:
+                        i = tail.find('.')
+                        if i < 0: i = len(tail)
+                        head, tail = tail[:i], tail[i+1:]
+                        new_name = "%s.%s" % (name, head)
+                        r = self.import_module(head, new_name, q)
+                        res.append(r)
+                        name = new_name
+                # All but the last module found must be packages because they
+                # contained other packages.
+                for i in range(0, len(res) - 1):
+                        res[i].make_package()
+                self.msgout(4, "load_tail ->", m)
+                return res
+
+
+if __name__ == "__main__":
+        mf = DepthLimitedModuleFinder(sys.argv[1])
+        loaded_modules = mf.run_script(sys.argv[2])
+        for res in set([
+            (tuple(m.get_file_names()), tuple(m.dirs)) for m in loaded_modules
+        ]):
+                print "DEP %s" % (res,)
+        missing, maybe =  mf.any_missing_maybe()
+        for name in missing:
+                print "ERR %s" % name,
--- a/src/modules/flavor/python.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/modules/flavor/python.py	Wed Jan 20 16:09:18 2010 -0800
@@ -21,10 +21,15 @@
 #
 
 #
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
+import os
+import re
+import subprocess
+import sys
+
 import pkg.flavor.base as base
 import pkg.flavor.depthlimitedmf as modulefinder
 
@@ -44,6 +49,69 @@
                     "in %(localpath)s") % self.__dict__
 
 
+class PythonMismatchedVersion(base.DependencyAnalysisError):
+        """Exception that is raised when a module is installed into a path
+        associated with a known version of python (/usr/lib/python2.4 for
+        example) but has a different version of python specified in its
+        #! line (#!/usr/bin/python2.5 for example)."""
+
+        def __init__(self, installed_version, declared_version, local_file,
+            installed_path):
+                self.inst_v = installed_version
+                self.decl_v = declared_version
+                self.lp = local_file
+                self.ip = installed_path
+
+        def __str__(self):
+                return _("The file to be installed at %(ip)s declares a "
+                    "python version of %(decl_v)s.  However, the path suggests "
+                    "that the version should be %(inst_v)s.  The text of the "
+                    "file can be found at %(lp)s") % self.__dict__
+
+
+class PythonSubprocessError(base.DependencyAnalysisError):
+        """This exception is raised when the subprocess created to analyze the
+        module using a different version of python exits with an error code."""
+
+        def __init__(self, rc, cmd, err):
+                self.rc = rc
+                self.cmd = cmd
+                self.err = err
+
+        def __str__(self):
+                return _("The command %(cmd)s\nexited with return code %(rc)s "
+                    "and this message:\n%(err)s") % self.__dict__
+
+
+class PythonSubprocessBadLine(base.DependencyAnalysisError):
+        """This exception is used when the output from the subprocess does not
+        follow the expected format."""
+
+        def __init__(self, cmd, lines):
+                self.lines = "\n".join(lines)
+                self.cmd = cmd
+
+        def __str__(self):
+                return _("The command %(cmd)s produced the following lines "
+                    "which cannot be understood:\n%(lines)s") % self.__dict__
+
+class PythonUnspecifiedVersion(base.PublishingDependency):
+        """This exception is used when an executable file starts with
+        #!/usr/bin/python and is not installed into a location from which its
+        python version may be inferred."""
+
+        def __init__(self, local_file, installed_path):
+                self.lp = local_file
+                self.ip = installed_path
+
+        def __str__(self):
+                return _("The file to be installed in %(ip)s does not specify "
+                    "a specific version of python either in its installed path "
+                    "nor in its text.  Such a file cannot be analyzed for "
+                    "dependencies since the version of python it will be used "
+                    "with is unknown.  The text of the file is here: %(lp)s.") \
+                    % self.__dict__
+
 class PythonDependency(base.PublishingDependency):
         """Class representing the dependency created by importing a module
         in python."""
@@ -57,26 +125,156 @@
                     self.base_names, self.run_paths, self.pkg_vars)
 
 
-def process_python_dependencies(proto_dir, action, pkg_vars):
-        """Given the path to a python file, the proto area containing that file,
-        the action that produced the dependency, and the variants against which
-        the action's package was published, produce a list of PythonDependency
-        objects."""
+py_bin_re = re.compile(r"^\#\!/usr/bin/python(?P<major>\d+)\.(?P<minor>\d+)")
+py_lib_re = re.compile(r"^usr/lib/python(?P<major>\d+)\.(?P<minor>\d+)/")
+
+def process_python_dependencies(proto_dir, action, pkg_vars, script_path):
+        """Analyze the file delivered by the action for any python dependencies.
+
+        The 'proto_dir' parameter points to the proto area containing the file
+        which 'action' uses.
+
+        The 'action' parameter contain the action which delivers the file.
+
+        The 'pkg_vars' parameter contains the variants against which
+        the action's package was published.
+
+        The 'script_path' parameter is None of the file is not executable, or
+        is the path for the binary which is used to execute the file.
+        """
 
-        local_file = action.attrs[PD_LOCAL_PATH]
+        # There are three conditions which determine whether python dependency
+        # analysis is performed on a file with python in its #! line.
+        # 1) Is the file executable. (Represented in the table below by X)
+        # 2) Is the file installed into a directory which provides information
+        #     about what version of python should be used for it.
+        #     (Represented by D)
+        # 3) Does the first line of the file include a specific version of
+        #     python. (Represented by F)
+        #
+        # Conditions || Perform Analysis
+        #  X  D  F   || Y, if F and D disagree, display a warning in the output
+        #            ||     and use D to analyze the file.
+        #  X  D !F   || Y
+        #  X !D  F   || Y
+        #  X !D !F   || N, and display a warning in the output.
+        # !X  D  F   || Y
+        # !X  D !F   || Y
+        # !X !D  F   || N
+        # !X !D !F   || N
         
-        mf = modulefinder.DepthLimitedModuleFinder(proto_dir)
-        loaded_modules = mf.run_script(local_file)
+        local_file = action.attrs[PD_LOCAL_PATH]
         deps = []
         errs = []
+        path_version = None
 
-        for names, dirs in set([
-            (tuple(m.get_file_names()), tuple(m.dirs)) for m in loaded_modules
-        ]):
-                deps.append(PythonDependency(action, names, dirs, pkg_vars,
-                    proto_dir))
-        missing, maybe = mf.any_missing_maybe()
-        for name in missing:
-                errs.append(PythonModuleMissingPath(name,
-                    action.attrs[PD_LOCAL_PATH]))
+        dir_major = None
+        dir_minor = None
+        file_major = None
+        file_minor = None
+        cur_major = None
+        cur_minor = None
+        executable = bool(script_path)
+
+        # Version of python to use to do the analysis.
+        analysis_major = None
+        analysis_minor = None
+        
+        cur_major, cur_minor = sys.version_info[0:2]
+        install_match = py_lib_re.match(action.attrs["path"])
+        if install_match:
+                dir_major = install_match.group("major")
+                dir_minor = install_match.group("minor")
+
+        script_match = None
+        if script_path:
+                script_match = py_bin_re.match(script_path)
+                if script_match:
+                        file_major = script_match.group("major")
+                        file_minor = script_match.group("minor")
+
+        if executable:
+                # Check whether the version of python declared in the #! line
+                # of the file and the version of python implied by the directory
+                # the file is delivered into match.
+                if install_match and script_match and \
+                    (file_major != dir_major or file_minor != dir_minor):
+                        errs.append(PythonMismatchedVersion(
+                            "%s.%s" % (dir_major, dir_minor),
+                            "%s.%s" % (file_major, file_minor),
+                            local_file, action.attrs["path"]))
+                if install_match:
+                        analysis_major = dir_major
+                        analysis_minor = dir_minor
+                elif script_match:
+                        analysis_major = file_major
+                        analysis_minor = file_minor
+                else:
+                        # An example of this case is an executable file in
+                        # /usr/bin with #!/usr/bin/python as its first line.
+                        errs.append(PythonUnspecifiedVersion(
+                            local_file, action.attrs["path"]))
+        elif install_match:
+                analysis_major = dir_major
+                analysis_minor = dir_minor
+
+        if analysis_major is None or analysis_minor is None:
+                return deps, errs
+
+        analysis_major = int(analysis_major)
+        analysis_minor = int(analysis_minor)
+
+        # If the version implied by the directory hierarchy matches the version
+        # of python running, use the default analyzer and don't fork and exec.
+        if cur_major == analysis_major and cur_minor == analysis_minor:
+                mf = modulefinder.DepthLimitedModuleFinder(proto_dir)
+                loaded_modules = mf.run_script(local_file)
+
+                for names, dirs in set([
+                    (tuple(m.get_file_names()), tuple(m.dirs))
+                    for m in loaded_modules
+                ]):
+                        deps.append(PythonDependency(action, names, dirs,
+                            pkg_vars, proto_dir))
+                missing, maybe = mf.any_missing_maybe()
+                for name in missing:
+                        errs.append(PythonModuleMissingPath(name,
+                            action.attrs[PD_LOCAL_PATH]))
+                return deps, errs
+
+        # If the version implied by the directory hierarchy does not match the
+        # version of python running, it's necessary to fork and run the
+        # appropriate version of python.
+        root_dir = os.path.dirname(__file__)
+        exec_file = os.path.join(root_dir,
+            "depthlimitedmf%s%s.py" % (analysis_major, analysis_minor))
+        cmd = ["python%s.%s" % (analysis_major, analysis_minor), exec_file,
+            proto_dir, local_file]
+        try:
+                sp = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE)
+        except Exception, e:
+                return [], [PythonSubprocessError(None, " ".join(cmd), str(e))]
+        out, err = sp.communicate()
+        if sp.returncode:
+                errs.append(PythonSubprocessError(sp.returncode, " ".join(cmd),
+                    err))
+        bad_lines = []
+        for l in out.splitlines():
+                l = l.strip()
+                if l.startswith("DEP "):
+                        try:
+                                names, dirs = eval(l[4:])
+                        except Exception:
+                                bad_lines.append(l)
+                        else:
+                                deps.append(PythonDependency(action, names,
+                                    dirs, pkg_vars, proto_dir))
+                elif l.startswith("ERR "):
+                        errs.append(PythonModuleMissingPath(l[4:],
+                            action.attrs[PD_LOCAL_PATH]))
+                else:
+                        bad_lines.append(l)
+        if bad_lines:
+                errs.append(PythonSubprocessBadLine(" ".join(cmd), bad_lines))
         return deps, errs
--- a/src/modules/flavor/script.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/modules/flavor/script.py	Wed Jan 20 16:09:18 2010 -0800
@@ -21,11 +21,12 @@
 #
 
 #
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 
 import os
+import stat
 
 import pkg.flavor.base as base
 import pkg.flavor.python as python
@@ -60,22 +61,29 @@
         l = f.readline()
         f.close()
 
+        deps = []
+        elist = []
+        script_path = None
         # add #! dependency
         if l.startswith("#!"):
-                # usedlist omits leading /
-                p = (l[2:].split()[0]) # first part of string is path (removes
-                # options)
-                # we don't handle dependencies through links, so fix up the
-                # common one
-                p = p.strip()
-                if p.startswith("/bin"):
-                        p = os.path.join("/usr", p)
-                deps = [ScriptDependency(action, p, pkg_vars, proto_dir)]
-                elist = []
+                # Determine whether the file will be delivered executable.
+                ex_bit = int(action.attrs.get("mode", "0"), 8) & \
+                    (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+                if ex_bit:
+                        # usedlist omits leading /
+                        p = (l[2:].split()[0])
+                        # first part of string is path (removes options)
+                        # we don't handle dependencies through links, so fix up
+                        # the common one
+                        p = p.strip()
+                        if p.startswith("/bin"):
+                                p = os.path.join("/usr", p)
+                        deps.append(ScriptDependency(action, p, pkg_vars,
+                            proto_dir))
+                        script_path = l
                 if "python" in l:
                         ds, errs = python.process_python_dependencies(proto_dir,
-                            action, pkg_vars)
+                            action, pkg_vars, script_path)
                         elist.extend(errs)
                         deps.extend(ds)
-                return deps, elist
-        return [], []
+        return deps, elist
--- a/src/modules/publish/dependencies.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/modules/publish/dependencies.py	Wed Jan 20 16:09:18 2010 -0800
@@ -352,6 +352,8 @@
         files = file_dep.attrs[files_prefix]
         if isinstance(files, basestring):
                 files = [files]
+        if isinstance(rps, basestring):
+                rps = [rps]
         return [os.path.join(rp, f) for rp in rps for f in files]
 
 def find_package_using_delivered_files(delivered, file_dep, dep_vars,
@@ -432,12 +434,10 @@
         return [a for a, v in res if a not in multiple_path_errs], dep_vars, \
             errs
 
-def find_package(api_inst, delivered, installed, file_dep, pkg_vars):
+def find_package(delivered, installed, file_dep, pkg_vars):
         """Find the packages which resolve the dependency. It returns a list of
         dependency actions with the fmri tag resolved.
 
-        'api_inst' is an ImageInterface which references the current image.
-
         'delivered' is a dictionary mapping paths to a list of fmri, variants
         pairs.
 
@@ -528,7 +528,7 @@
                         pkg_deps[mp] = None
                         continue
                 pkg_res = [
-                    (d, find_package(api_inst, delivered_files, installed_files,
+                    (d, find_package(delivered_files, installed_files,
                         d, pkg_vars))
                     for d in mfst.gen_actions_by_type("depend")
                     if is_file_dependency(d)
--- a/src/pkgdefs/Makefile	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/pkgdefs/Makefile	Wed Jan 20 16:09:18 2010 -0800
@@ -58,8 +58,11 @@
 check: protoproto pkgproto
 	diff protoproto pkgproto | grep "^<" | nawk '{print $$3} END {exit NR != 0}'
 	
+# Find and sort are in the pipline below because when pkgproto encounters two 
+# files hardlinked together, it is not consistent about which is the file and
+# which is the hardlink in its output.
 protoproto: FRC
-	(cd $(ROOT) && pkgproto . | nawk '{print $$1, $$3}' | sort +1) > $@
+	(cd $(ROOT) && find . | sort | pkgproto | nawk '{print $$1, $$3}' | sort +1) > $@
 
 PROTOS = $(SUBDIRS:%=%/prototype)
 
@@ -69,7 +72,7 @@
 # file entries for the subsequent comparison to suceed.
 #
 pkgproto: $(PROTOS)
-	nawk '$$1 ~ /[sdef]/ {print $$1, $$3}' $(PROTOS) | sed 's/^e/f/' | \
+	nawk '$$1 ~ /[lsdef]/ {print $$1, $$3}' $(PROTOS) | sed 's/^e/f/' | \
 	    sort +1 -u > $@
 
 FRC:
--- a/src/pkgdefs/SUNWipkg/prototype	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/pkgdefs/SUNWipkg/prototype	Wed Jan 20 16:09:18 2010 -0800
@@ -151,6 +151,10 @@
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/base.pyc 444 root bin
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf.py 444 root bin
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf.pyc 444 root bin
+f none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf24.py 444 root bin
+f none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf24.pyc 444 root bin
+l none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf25.py=depthlimitedmf24.py
+l none usr/lib/python2.6/vendor-packages/pkg/flavor/depthlimitedmf25.pyc=depthlimitedmf24.pyc
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/elf.py 444 root bin
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/elf.pyc 444 root bin
 f none usr/lib/python2.6/vendor-packages/pkg/flavor/hardlink.py 444 root bin
--- a/src/pkgdep.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/pkgdep.py	Wed Jan 20 16:09:18 2010 -0800
@@ -105,7 +105,6 @@
         kernel_paths = []
         platform_paths = []
         dyn_tok_conv = {}
-        import sys
 
         for opt, arg in opts:
                 if opt == "-D":
--- a/src/setup.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/setup.py	Wed Jan 20 16:09:18 2010 -0800
@@ -172,6 +172,13 @@
 execattrd_dir = 'etc/security/exec_attr.d'
 authattrd_dir = 'etc/security/auth_attr.d'
 
+# A list of source, destination tuples of modules which should be hardlinked
+# together if the os supports it and otherwise copied.
+hardlink_modules = [
+    ("%s/pkg/flavor/depthlimitedmf24" % py_install_dir,
+    "%s/pkg/flavor/depthlimitedmf25" % py_install_dir)
+]
+
 scripts_sunos = {
         scripts_dir: [
                 ['client.py', 'pkg'],
@@ -428,6 +435,21 @@
 
                 _install.run(self)
 
+                for o_src, o_dest in hardlink_modules:
+                        for e in [".py", ".pyc"]:
+                                src = util.change_root(self.root_dir, o_src + e)
+                                dest = util.change_root(
+                                    self.root_dir, o_dest + e)
+                                if ostype == "posix":
+                                        if os.path.exists(dest) and \
+                                            os.stat(src)[stat.ST_INO] != \
+                                            os.stat(dest)[stat.ST_INO]:
+                                                os.remove(dest)
+                                        file_util.copy_file(src, dest,
+                                            link="hard", update=1)
+                                else:
+                                        file_util.copy_file(src, dest, update=1)
+
                 for d, files in scripts[osname].iteritems():
                         for (srcname, dstname) in files:
                                 dst_dir = util.change_root(self.root_dir, d)
--- a/src/tests/api/t_dependencies.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/tests/api/t_dependencies.py	Wed Jan 20 16:09:18 2010 -0800
@@ -41,7 +41,12 @@
 import pkg5unittest
 
 import pkg.catalog as catalog
+import pkg.flavor.base as base
+import pkg.flavor.depthlimitedmf as dlmf
 import pkg.flavor.elf as elf
+import pkg.flavor.hardlink as hl
+import pkg.flavor.python as py
+import pkg.flavor.script as scr
 import pkg.fmri as fmri
 import pkg.portable as portable
 import pkg.publish.dependencies as dependencies
@@ -719,3 +724,19 @@
                     self.int_hardlink_manf_test_symlink)
                 ds, es, ms = dependencies.list_implicit_deps(t_path,
                     self.proto_dir, {}, [])
+
+        def test_str_methods(self):
+                """Test the str methods of objects in the flavor space."""
+
+                str(base.MissingFile("fp"))
+                str(elf.BadElfFile("fp", "ex"))
+                str(elf.UnsupportedDynamicToken("/proto_path", "/install",
+                    "run_path", "tok"))
+                str(py.PythonModuleMissingPath("foo", "bar"))
+                str(py.PythonMismatchedVersion("2.4", "2.6", "foo", "bar"))
+                str(py.PythonSubprocessError(2, "foo", "bar"))
+                str(py.PythonSubprocessBadLine("cmd", ["l1", "l2"]))
+                mi = dlmf.ModuleInfo("name", ["/d1", "/d2"])
+                str(mi)
+                mi.make_package()
+                str(mi)
--- a/src/tests/baseline.txt	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/tests/baseline.txt	Wed Jan 20 16:09:18 2010 -0800
@@ -26,6 +26,7 @@
 api.t_dependencies.py TestDependencyAnalyzer.test_int_elf|pass
 api.t_dependencies.py TestDependencyAnalyzer.test_int_hardlink|pass
 api.t_dependencies.py TestDependencyAnalyzer.test_int_script|pass
+api.t_dependencies.py TestDependencyAnalyzer.test_str_methods|pass
 api.t_dependencies.py TestDependencyAnalyzer.test_symlinks|pass
 api.t_dependencies.py TestDependencyAnalyzer.test_variants_1|pass
 api.t_dependencies.py TestDependencyAnalyzer.test_variants_2|pass
@@ -573,12 +574,13 @@
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_11517|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_11805|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_11829|pass
-cli.t_pkgdep.py TestPkgdepBasics.test_bug_11989|fail
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_12697|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_12816|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_bug_12896|pass
+cli.t_pkgdep.py TestPkgdepBasics.test_bug_13059|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_opts|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_output|pass
+cli.t_pkgdep.py TestPkgdepBasics.test_python_combinations|pass
 cli.t_pkgdep.py TestPkgdepBasics.test_resolve_screen_out|pass
 cli.t_pkgdep_resolve.py TestApiDependencies.test_bug_11518|pass
 cli.t_pkgdep_resolve.py TestApiDependencies.test_bug_12697_and_12896|pass
--- a/src/tests/cli/t_pkgdep.py	Wed Jan 20 15:05:44 2010 +0000
+++ b/src/tests/cli/t_pkgdep.py	Wed Jan 20 16:09:18 2010 -0800
@@ -65,9 +65,9 @@
 """
 
         payload_manf = """\
-hardlink path=usr/baz target=../foo/bar.py
+hardlink path=usr/baz target=lib/python2.6/foo/bar.py
 file usr/lib/python2.6/vendor-packages/pkg/client/indexer.py \
-group=bin mode=0755 owner=root path=foo/bar.py
+group=bin mode=0755 owner=root path=usr/lib/python2.6/foo/bar.py
 """
 
         elf_sub_manf = """\
@@ -189,18 +189,6 @@
 variant.arch:foo
 """
 
-        test_manf_1_resolved = """\
-depend fmri=%(py_pkg_name)s %(pfx)s.file=usr/bin/python %(pfx)s.reason=usr/lib/python2.6/vendor-packages/pkg/client/indexer.py %(pfx)s.type=script type=require
-depend fmri=%(ips_pkg_name)s %(pfx)s.file=usr/lib/python2.6/vendor-packages/pkg/__init__.py %(pfx)s.reason=usr/lib/python2.6/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
-depend fmri=%(ips_pkg_name)s %(pfx)s.file=usr/lib/python2.6/vendor-packages/pkg/indexer.py %(pfx)s.reason=usr/lib/python2.6/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
-depend fmri=%(ips_pkg_name)s %(pfx)s.file=usr/lib/python2.6/vendor-packages/pkg/misc.py %(pfx)s.reason=usr/lib/python2.6/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
-depend fmri=%(ips_pkg_name)s %(pfx)s.file=usr/lib/python2.6/vendor-packages/pkg/search_storage.py %(pfx)s.reason=usr/lib/python2.6/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
-depend fmri=%(resolve_name)s %(pfx)s.file=var/log/authlog %(pfx)s.reason=baz %(pfx)s.type=hardlink type=require
-depend fmri=%(csl_pkg_name)s %(pfx)s.file=libc.so.1 %(pfx)s.path=lib %(pfx)s.path=usr/lib %(pfx)s.reason=usr/xpg4/lib/libcurses.so.1 %(pfx)s.type=elf type=require
-"""
-
-        test_manf_1_full_resolved = test_manf_1_resolved + test_manf_1
-
         def make_full_res_manf_1_mod_proto(self, proto_area):
                 return self.make_full_res_manf_1(proto_area) + \
                     """\
@@ -218,20 +206,20 @@
                     "%(pfx)s.file=misc/__init__.py " +
                     self.__make_paths("pkg",
                         [proto_area + "/usr/bin"] + self.new_path) +
-                    " %(pfx)s.reason=foo/bar.py "
+                    " %(pfx)s.reason=usr/lib/python2.6/foo/bar.py "
                     "%(pfx)s.type=python type=require\n"
 
                     "depend fmri=%(dummy_fmri)s "
                     "%(pfx)s.file=pkg/__init__.py " +
                     self.__make_paths("",
                         [proto_area + "/usr/bin"] + self.new_path) +
-                    " %(pfx)s.reason=foo/bar.py "
+                    " %(pfx)s.reason=usr/lib/python2.6/foo/bar.py "
                     "%(pfx)s.type=python type=require\n"
 
                     "depend fmri=%(dummy_fmri)s "
                     "%(pfx)s.file=python "
                     "%(pfx)s.path=usr/bin "
-                    "%(pfx)s.reason=foo/bar.py "
+                    "%(pfx)s.reason=usr/lib/python2.6/foo/bar.py "
                     "%(pfx)s.type=script type=require\n"
                     "depend fmri=%(dummy_fmri)s "
                     "%(pfx)s.file=search_storage.py "
@@ -240,7 +228,7 @@
                     "%(pfx)s.file=search_storage/__init__.py " +
                     self.__make_paths("pkg",
                         [proto_area + "/usr/bin"] + self.new_path) +
-                    " %(pfx)s.reason=foo/bar.py "
+                    " %(pfx)s.reason=usr/lib/python2.6/foo/bar.py "
                     "%(pfx)s.type=python type=require\n"
 
                     "depend fmri=%(dummy_fmri)s "
@@ -250,20 +238,13 @@
                     "%(pfx)s.file=indexer/__init__.py " +
                     self.__make_paths("pkg",
                         [proto_area + "/usr/bin"] + self.new_path) +
-                    " %(pfx)s.reason=foo/bar.py "
+                    " %(pfx)s.reason=usr/lib/python2.6/foo/bar.py "
                     "%(pfx)s.type=python type=require\n") % {
                         "pfx":
                             base.Dependency.DEPEND_DEBUG_PREFIX,
                         "dummy_fmri":base.Dependency.DUMMY_FMRI
                     }
 
-        res_payload_1_tmp = """\
-depend fmri=__TBD pkg.debug.depend.file=misc.py pkg.debug.depend.file=misc.pyc pkg.debug.depend.file=misc.pyo pkg.debug.depend.file=misc/__init__.py pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/proto/root_i386/usr/bin/pkg pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/src/tests/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-dynload/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-old/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-tk/pkg pkg.debug.depend.path=usr/lib/python2.6/pkg pkg.debug.depend.path=usr/lib/python2.6/plat-sunos5/pkg pkg.debug.depend.path=usr/lib/python2.6/site-packages/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gst-0.10/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gtk-2.0/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/pkg pkg.debug.depend.path=usr/lib/python26.zip/pkg pkg.debug.depend.reason=foo/bar.py pkg.debug.depend.type=python type=require
-depend fmri=__TBD pkg.debug.depend.file=pkg/__init__.py pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/proto/root_i386/usr/bin pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/src/tests pkg.debug.depend.path=usr/lib/python2.6 pkg.debug.depend.path=usr/lib/python2.6/lib-dynload pkg.debug.depend.path=usr/lib/python2.6/lib-old pkg.debug.depend.path=usr/lib/python2.6/lib-tk pkg.debug.depend.path=usr/lib/python2.6/plat-sunos5 pkg.debug.depend.path=usr/lib/python2.6/site-packages pkg.debug.depend.path=usr/lib/python2.6/vendor-packages pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gst-0.10 pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gtk-2.0 pkg.debug.depend.path=usr/lib/python26.zip pkg.debug.depend.reason=foo/bar.py pkg.debug.depend.type=python type=require
-depend fmri=__TBD pkg.debug.depend.file=python pkg.debug.depend.path=usr/bin pkg.debug.depend.reason=foo/bar.py pkg.debug.depend.type=script type=require
-depend fmri=__TBD pkg.debug.depend.file=search_storage.py pkg.debug.depend.file=search_storage.pyc pkg.debug.depend.file=search_storage.pyo pkg.debug.depend.file=search_storage/__init__.py pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/proto/root_i386/usr/bin/pkg pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/src/tests/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-dynload/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-old/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-tk/pkg pkg.debug.depend.path=usr/lib/python2.6/pkg pkg.debug.depend.path=usr/lib/python2.6/plat-sunos5/pkg pkg.debug.depend.path=usr/lib/python2.6/site-packages/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gst-0.10/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gtk-2.0/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/pkg pkg.debug.depend.path=usr/lib/python26.zip/pkg pkg.debug.depend.reason=foo/bar.py pkg.debug.depend.type=python type=require
-depend fmri=__TBD pkg.debug.depend.file=indexer.py pkg.debug.depend.file=indexer.pyc pkg.debug.depend.file=indexer.pyo pkg.debug.depend.file=indexer/__init__.py pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/proto/root_i386/usr/bin/pkg pkg.debug.depend.path=export/home/bpytlik/IPS/dep_tests/src/tests/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-dynload/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-old/pkg pkg.debug.depend.path=usr/lib/python2.6/lib-tk/pkg pkg.debug.depend.path=usr/lib/python2.6/pkg pkg.debug.depend.path=usr/lib/python2.6/plat-sunos5/pkg pkg.debug.depend.path=usr/lib/python2.6/site-packages/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gst-0.10/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/gtk-2.0/pkg pkg.debug.depend.path=usr/lib/python2.6/vendor-packages/pkg pkg.debug.depend.path=usr/lib/python26.zip/pkg pkg.debug.depend.reason=foo/bar.py pkg.debug.depend.type=python type=require
-"""        
         two_variant_deps = """\
 set name=variant.foo value=bar value=baz
 set name=variant.num value=one value=two value=three
@@ -447,19 +428,96 @@
 from pkg.misc import EmptyI
 """
 
-        p24_test_manf_1 = """\
-file NOHASH group=bin mode=0755 owner=root path=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py
+        py_in_usr_bin_manf = """\
+file NOHASH group=bin mode=0755 owner=root path=usr/bin/pkg \
+"""
+
+        py_in_usr_bin_manf_non_ex = """\
+file NOHASH group=bin mode=0644 owner=root path=usr/bin/pkg \
+"""
+
+        pyver_python_text = """\
+#!/usr/bin/python%s
+
+import pkg.indexer as indexer
+import pkg.search_storage as ss
+from pkg.misc import EmptyI
+"""
+
+        pyver_test_manf_1 = """\
+file NOHASH group=bin mode=0755 owner=root path=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py \
 """
 
-        p24_res_full_manf_1 = """\
-file NOHASH group=bin mode=0755 owner=root path=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py
-depend %(pfx)s.file=usr/bin/python fmri=%(dummy_fmri)s type=require %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=script
-depend %(pfx)s.file=usr/lib/python2.4/vendor-packages/pkg/__init__.py fmri=%(dummy_fmri)s type=require %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python
-depend %(pfx)s.file=usr/lib/python2.4/vendor-packages/pkg/indexer.py fmri=%(dummy_fmri)s type=require %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python
-depend %(pfx)s.file=usr/lib/python2.4/vendor-packages/pkg/misc.py fmri=%(dummy_fmri)s type=require %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python
-depend %(pfx)s.file=usr/lib/python2.4/vendor-packages/pkg/search_storage.py fmri=%(dummy_fmri)s type=require %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python
+        pyver_test_manf_1_non_ex = """\
+file NOHASH group=bin mode=0644 owner=root path=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py \
+"""
+        pyver_24_python_res = """
+depend fmri=%(dummy_fmri)s %(pfx)s.file=indexer.py %(pfx)s.file=indexer.pyc %(pfx)s.file=indexer.pyo %(pfx)s.file=indexer/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.4/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.4/lib-tk/pkg %(pfx)s.path=usr/lib/python2.4/pkg %(pfx)s.path=usr/lib/python2.4/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.4/site-packages/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gst-0.10/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gtk-2.0/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/pkg %(pfx)s.path=usr/lib/python24.zip/pkg %(pfx)s.reason=%(reason)s %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=misc.py %(pfx)s.file=misc.pyc %(pfx)s.file=misc.pyo %(pfx)s.file=misc/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.4/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.4/lib-tk/pkg %(pfx)s.path=usr/lib/python2.4/pkg %(pfx)s.path=usr/lib/python2.4/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.4/site-packages/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gst-0.10/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gtk-2.0/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/pkg %(pfx)s.path=usr/lib/python24.zip/pkg %(pfx)s.reason=%(reason)s %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=pkg/__init__.py %(pfx)s.path=%(cwd)s %(pfx)s.path=usr/lib/python2.4 %(pfx)s.path=usr/lib/python2.4/lib-dynload %(pfx)s.path=usr/lib/python2.4/lib-tk %(pfx)s.path=usr/lib/python2.4/plat-sunos5 %(pfx)s.path=usr/lib/python2.4/site-packages %(pfx)s.path=usr/lib/python2.4/vendor-packages %(pfx)s.path=usr/lib/python2.4/vendor-packages/gst-0.10 %(pfx)s.path=usr/lib/python2.4/vendor-packages/gtk-2.0 %(pfx)s.path=usr/lib/python24.zip %(pfx)s.reason=%(reason)s %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=search_storage.py %(pfx)s.file=search_storage.pyc %(pfx)s.file=search_storage.pyo %(pfx)s.file=search_storage/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.4/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.4/lib-tk/pkg %(pfx)s.path=usr/lib/python2.4/pkg %(pfx)s.path=usr/lib/python2.4/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.4/site-packages/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gst-0.10/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/gtk-2.0/pkg %(pfx)s.path=usr/lib/python2.4/vendor-packages/pkg %(pfx)s.path=usr/lib/python24.zip/pkg %(pfx)s.reason=%(reason)s %(pfx)s.type=python type=require
+""" % {
+    "pfx": base.Dependency.DEPEND_DEBUG_PREFIX,
+    "dummy_fmri": base.Dependency.DUMMY_FMRI,
+    "cwd": os.getcwd().lstrip("/"),
+    "reason": "%(reason)s"
+}
+
+        pyver_24_script_full_manf_1 = """
+file NOHASH group=bin mode=0755 owner=root path=%(reason)s
+depend fmri=%(dummy_fmri)s %(pfx)s.file=python%(bin_ver)s %(pfx)s.path=usr/bin %(pfx)s.reason=%(reason)s %(pfx)s.type=script type=require\
+""" % {
+    "pfx": base.Dependency.DEPEND_DEBUG_PREFIX,
+    "dummy_fmri": base.Dependency.DUMMY_FMRI,
+    "cwd": os.getcwd().lstrip("/"),
+    "reason": "%(reason)s",
+    "bin_ver": "%(bin_ver)s"
+}
+        
+        pyver_res_full_manf_1 = {}
+        pyver_res_full_manf_1["2.4"] = pyver_24_script_full_manf_1 + \
+            pyver_24_python_res
+
+        # 2.5 needs a separate entry from 2.4 because the default module search
+        # paths changed between the versions.
+        pyver_res_full_manf_1["2.5"] = """
+file NOHASH group=bin mode=0755 owner=root path=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py
+depend fmri=%(dummy_fmri)s %(pfx)s.file=indexer.py %(pfx)s.file=indexer.pyc %(pfx)s.file=indexer.pyo %(pfx)s.file=indexer/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.5/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.5/lib-tk/pkg %(pfx)s.path=usr/lib/python2.5/pkg %(pfx)s.path=usr/lib/python2.5/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.5/site-packages/pkg %(pfx)s.path=usr/lib/python2.5/vendor-packages/pkg %(pfx)s.path=usr/lib/python25.zip/pkg %(pfx)s.reason=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=misc.py %(pfx)s.file=misc.pyc %(pfx)s.file=misc.pyo %(pfx)s.file=misc/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.5/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.5/lib-tk/pkg %(pfx)s.path=usr/lib/python2.5/pkg %(pfx)s.path=usr/lib/python2.5/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.5/site-packages/pkg %(pfx)s.path=usr/lib/python2.5/vendor-packages/pkg %(pfx)s.path=usr/lib/python25.zip/pkg %(pfx)s.reason=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=pkg/__init__.py %(pfx)s.path=%(cwd)s %(pfx)s.path=usr/lib/python2.5 %(pfx)s.path=usr/lib/python2.5/lib-dynload %(pfx)s.path=usr/lib/python2.5/lib-tk %(pfx)s.path=usr/lib/python2.5/plat-sunos5 %(pfx)s.path=usr/lib/python2.5/site-packages %(pfx)s.path=usr/lib/python2.5/vendor-packages %(pfx)s.path=usr/lib/python25.zip %(pfx)s.reason=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=python %(pfx)s.path=usr/bin %(pfx)s.reason=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py %(pfx)s.type=script type=require
+depend fmri=%(dummy_fmri)s %(pfx)s.file=search_storage.py %(pfx)s.file=search_storage.pyc %(pfx)s.file=search_storage.pyo %(pfx)s.file=search_storage/__init__.py %(pfx)s.path=%(cwd)s/pkg %(pfx)s.path=usr/lib/python2.5/lib-dynload/pkg %(pfx)s.path=usr/lib/python2.5/lib-tk/pkg %(pfx)s.path=usr/lib/python2.5/pkg %(pfx)s.path=usr/lib/python2.5/plat-sunos5/pkg %(pfx)s.path=usr/lib/python2.5/site-packages/pkg %(pfx)s.path=usr/lib/python2.5/vendor-packages/pkg %(pfx)s.path=usr/lib/python25.zip/pkg %(pfx)s.reason=usr/lib/python2.5/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+""" % {"pfx":base.Dependency.DEPEND_DEBUG_PREFIX, "dummy_fmri":base.Dependency.DUMMY_FMRI, "cwd":os.getcwd().lstrip("/")}
+
+        # pyver_resolve_dep_manf = {}
+        pyver_resolve_dep_manf = """
+file NOHASH group=bin mode=0444 owner=root path=usr/lib/python%(py_ver)s/vendor-packages/pkg/indexer.py
+file NOHASH group=bin mode=0444 owner=root path=usr/lib/python%(py_ver)s/vendor-packages/pkg/__init__.py
+file NOHASH group=bin mode=0444 owner=root path=usr/lib/python%(py_ver)s/lib-tk/pkg/search_storage.py
+file NOHASH group=bin mode=0444 owner=root path=usr/lib/python%(py_ver)s/vendor-packages/pkg/misc.py
+file NOHASH group=bin mode=0755 owner=root path=usr/bin/python
+"""
+
+        pyver_resolve_results = """
+depend fmri=%(res_manf)s %(pfx)s.file=usr/lib/python%(py_ver)s/vendor-packages/pkg/indexer.py %(pfx)s.reason=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(res_manf)s %(pfx)s.file=usr/lib/python%(py_ver)s/vendor-packages/pkg/misc.py %(pfx)s.reason=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(res_manf)s %(pfx)s.file=usr/lib/python%(py_ver)s/vendor-packages/pkg/__init__.py %(pfx)s.reason=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+depend fmri=%(res_manf)s %(pfx)s.file=usr/bin/python %(pfx)s.reason=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py %(pfx)s.type=script type=require
+depend fmri=%(res_manf)s %(pfx)s.file=usr/lib/python%(py_ver)s/lib-tk/pkg/search_storage.py %(pfx)s.reason=usr/lib/python%(py_ver)s/vendor-packages/pkg/client/indexer.py %(pfx)s.type=python type=require
+"""
+
+        pyver_mismatch_results = """
+depend fmri=%(dummy_fmri)s %(pfx)s.file=python2.6 %(pfx)s.path=usr/bin %(pfx)s.reason=usr/lib/python2.4/vendor-packages/pkg/client/indexer.py %(pfx)s.type=script type=require \
 """ % {"pfx":base.Dependency.DEPEND_DEBUG_PREFIX, "dummy_fmri":base.Dependency.DUMMY_FMRI}
 
+        pyver_mismatch_errs = """
+The file to be installed at usr/lib/python2.4/vendor-packages/pkg/client/indexer.py declares a python version of 2.6.  However, the path suggests that the version should be 2.4.  The text of the file can be found at %s/usr/lib/python2.4/vendor-packages/pkg/client/indexer.py
+"""
+
+        pyver_unspecified_ver_err = """
+The file to be installed in usr/bin/pkg does not specify a specific version of python either in its installed path nor in its text.  Such a file cannot be analyzed for dependencies since the version of python it will be used with is unknown.  The text of the file is here: %s/usr/bin/pkg.
+"""
+
         def setUp(self):
                 testutils.SingleDepotTestCase.setUp(self)
                 self.image_create(self.dc.get_depot_url())
@@ -623,107 +681,171 @@
 
                 portable.remove(tp)
 
-        def test_bug_11989(self):
-                """These tests fail because they're resolved using a 2.6 based
-                interpreter, instead of a 2.4 one."""
+        def test_python_combinations(self):
+                """Test that each line in the following table is accounted for
+                by a test case.
 
-                tp = self.make_manifest(self.p24_test_manf_1)
-                self.make_text_file("usr/lib/python2.4/vendor-packages/pkg/"
-                    "client/indexer.py", self.python_text)
-                self.make_elf([], "usr/xpg4/lib/libcurses.so.1")
+                There are three conditions which determine whether python
+                dependency analysis is performed on a file with python in its
+                #! line.
+                1) Is the file executable.
+                    (Represented in the table below by X)
+                2) Is the file installed into a directory which provides
+                    information about what version of python should be used
+                    for it.
+                    (Represented by D)
+                3) Does the first line of the file include a specific version
+                    of python.
+                    (Represented by F)
                 
+                Conditions || Perform Analysis
+                 X  D  F   || Y, if F and D disagree, display a warning in the
+                           ||     output and use D to analyze the file.
+                 X  D !F   || Y
+                 X !D  F   || Y
+                 X !D !F   || N, and display a warning in the output.
+                !X  D  F   || Y
+                !X  D !F   || Y
+                !X !D  F   || N
+                !X !D !F   || N
+                """
+
+                # The test for line 1 with matching versions is done by
+                # test_bug_13059.
+
+                # Test line 1 (X D F) with mismatched versions.
+                tp = self.make_manifest(self.pyver_test_manf_1 %
+                    {"py_ver":"2.4"})
+                fp = "usr/lib/python2.4/vendor-packages/pkg/client/indexer.py"
+                self.make_text_file(fp, self.pyver_python_text % "2.6")
+                self.pkgdepend("generate %s" % tp, proto=self.proto_dir, exit=1)
+                self.check_res(self.pyver_mismatch_results + \
+                    self.pyver_24_python_res % {"reason": fp, "bin_ver": "2.6"},
+                    self.output)
+                self.check_res(self.pyver_mismatch_errs % self.proto_dir,
+                    self.errout)
+
+                # Test line 2 (X D !F)
+                tp = self.make_manifest(self.pyver_test_manf_1 %
+                    {"py_ver":"2.4"})
+                fp = "usr/lib/python2.4/vendor-packages/pkg/client/indexer.py"
+                self.make_text_file(fp, self.pyver_python_text % "")
                 self.pkgdepend("generate -m %s" % tp, proto=self.proto_dir)
-                self.check_res(self.p24_res_full_manf_1, self.output)
-                self.check_res("ensure failure", self.errout)
-
-                dependency_mp = self.make_manifest(self.output)
-                provider_mp = self.make_manifest(self.resolve_dep_manf)
-
-                self.pkgdepend("resolve %s %s" % (dependency_mp, provider_mp),
-                    use_proto=False)
-                self.check_res("", self.output)
+                self.check_res(
+                    self.pyver_res_full_manf_1["2.4"] %
+                        {"reason": fp, "bin_ver": ""},
+                    self.output)
                 self.check_res("", self.errout)
-                dependency_res_p = dependency_mp + ".res"
-                provider_res_p = provider_mp + ".res"
-                lines = self.__read_file(dependency_res_p)
-                self.check_res(self.test_manf_1_resolved % {
-                        "resolve_name": os.path.basename(provider_mp),
-                        "pfx":
-                            base.Dependency.DEPEND_DEBUG_PREFIX,
-                        "dummy_fmri": base.Dependency.DUMMY_FMRI
-                    }, lines)
-                lines = self.__read_file(provider_res_p)
-                self.check_res("", lines)
+
+                # Test line 3 (X !D F)
+                tp = self.make_manifest(self.py_in_usr_bin_manf)
+                fp = "usr/bin/pkg"
+                self.make_text_file(fp, self.pyver_python_text % "2.4")
+                self.pkgdepend("generate -m %s" % tp, proto=self.proto_dir)
+                self.check_res(
+                    self.pyver_res_full_manf_1["2.4"] %
+                        {"reason": fp, "bin_ver": "2.4"},
+                    self.output)
+                self.check_res("", self.errout)
+
+                # Test line 4 (X !D !F)
+                tp = self.make_manifest(self.py_in_usr_bin_manf)
+                fp = "usr/bin/pkg"
+                self.make_text_file(fp, self.pyver_python_text % "")
+                self.pkgdepend("generate -m %s" % tp, proto=self.proto_dir,
+                    exit=1)
+                self.check_res(
+                    self.pyver_24_script_full_manf_1 %
+                        {"reason": fp, "bin_ver": ""},
+                    self.output)
+                self.check_res(self.pyver_unspecified_ver_err % self.proto_dir,
+                    self.errout)
 
-                portable.remove(dependency_res_p)
-                portable.remove(provider_res_p)
+                # Test line 5 (!X D F)
+                tp = self.make_manifest(self.pyver_test_manf_1_non_ex %
+                    {"py_ver":"2.4"})
+                fp = "usr/lib/python2.4/vendor-packages/pkg/client/indexer.py"
+                self.make_text_file(fp, self.pyver_python_text % "2.6")
+                self.pkgdepend("generate %s" % tp, proto=self.proto_dir)
+                self.check_res(self.pyver_24_python_res % {"reason": fp},
+                    self.output)
+                self.check_res("", self.errout)
 
-                tmp_d = tempfile.mkdtemp()
-                
-                self.pkgdepend("resolve -m -d %s %s %s" %
-                    (tmp_d, dependency_mp, provider_mp), use_proto=False)
+                # Test line 6 (!X D !F)
+                tp = self.make_manifest(self.pyver_test_manf_1_non_ex %
+                    {"py_ver":"2.4"})
+                fp = "usr/lib/python2.4/vendor-packages/pkg/client/indexer.py"
+                self.make_text_file(fp, self.pyver_python_text % "")
+                self.pkgdepend("generate %s" % tp, proto=self.proto_dir)
+                self.check_res(self.pyver_24_python_res % {"reason": fp},
+                    self.output)
+                self.check_res("", self.errout)
+
+                # Test line 7 (!X !D F)
+                tp = self.make_manifest(self.py_in_usr_bin_manf_non_ex)
+                fp = "usr/bin/pkg"
+                self.make_text_file(fp, self.pyver_python_text % "2.4")
+                self.pkgdepend("generate %s" % tp, proto=self.proto_dir)
                 self.check_res("", self.output)
                 self.check_res("", self.errout)
-                dependency_res_p = os.path.join(tmp_d,
-                    os.path.basename(dependency_mp))
-                provider_res_p = os.path.join(tmp_d,
-                    os.path.basename(provider_mp))
-                lines = self.__read_file(dependency_res_p)
-                self.check_res(self.test_manf_1_full_resolved % {
-                        "resolve_name": os.path.basename(provider_mp),
-                        "pfx":
-                            base.Dependency.DEPEND_DEBUG_PREFIX,
-                        "dummy_fmri":base.Dependency.DUMMY_FMRI
-                    }, lines)
-                lines = self.__read_file(provider_res_p)
-                self.check_res(self.resolve_dep_manf, lines)
 
-                portable.remove(dependency_res_p)
-                portable.remove(provider_res_p)
-
-                self.pkgdepend("resolve -s foo -d %s %s %s" %
-                    (tmp_d, dependency_mp, provider_mp), use_proto=False)
+                # Test line 8 (!X !D !F)
+                tp = self.make_manifest(self.py_in_usr_bin_manf_non_ex)
+                fp = "usr/bin/pkg"
+                self.make_text_file(fp, self.pyver_python_text % "")
+                self.pkgdepend("generate %s" % tp, proto=self.proto_dir)
                 self.check_res("", self.output)
                 self.check_res("", self.errout)
-                dependency_res_p = os.path.join(tmp_d,
-                    os.path.basename(dependency_mp)) + ".foo"
-                provider_res_p = os.path.join(tmp_d,
-                    os.path.basename(provider_mp)) + ".foo"
-                lines = self.__read_file(dependency_res_p)
-                self.check_res(self.test_manf_1_resolved % {
-                        "resolve_name": os.path.basename(provider_mp),
-                        "pfx":
-                            base.Dependency.DEPEND_DEBUG_PREFIX,
-                        "dummy_fmri":base.Dependency.DUMMY_FMRI
-                    }, lines)
-                lines = self.__read_file(provider_res_p)
-                self.check_res("", lines)
+
+        def test_bug_13059(self):
+                """Test that python modules written for a version of python
+                other than the current system version are analyzed correctly."""
+
+                for py_ver in ["2.4", "2.5"]:
 
-                portable.remove(dependency_res_p)
-                portable.remove(provider_res_p)
+                        # Set up the files for generate.
+                        tp = self.make_manifest(
+                            self.pyver_test_manf_1 % {"py_ver":py_ver})
+                        fp = "usr/lib/python%s/vendor-packages/pkg/" \
+                            "client/indexer.py" % py_ver 
+                        self.make_text_file(fp, self.python_text)
+
+                        # Run generate and check the output.
+                        self.pkgdepend("generate -m %s" % tp,
+                            proto=self.proto_dir)
+                        self.check_res(self.pyver_res_full_manf_1[py_ver] %
+                            {"bin_ver": "", "reason":fp},
+                            self.output)
+                        self.check_res("", self.errout)
 
-                self.pkgdepend("resolve -s .foo %s %s" %
-                    (dependency_mp, provider_mp), use_proto=False)
-                self.check_res("", self.output)
-                self.check_res("", self.errout)
-                dependency_res_p = dependency_mp + ".foo"
-                provider_res_p = provider_mp + ".foo"
-                lines = self.__read_file(dependency_res_p)
-                self.check_res(self.test_manf_1_resolved % {
-                        "resolve_name": os.path.basename(provider_mp),
-                        "pfx":
-                            base.Dependency.DEPEND_DEBUG_PREFIX,
-                        "dummy_fmri":base.Dependency.DUMMY_FMRI
-                    }, lines)
-                lines = self.__read_file(provider_res_p)
-                self.check_res("", lines)
+                        # Take the output from the run and make it a file
+                        # for the resolver to use.
+                        dependency_mp = self.make_manifest(self.output)
+                        provider_mp = self.make_manifest(
+                            self.pyver_resolve_dep_manf % {"py_ver":py_ver})
 
-                portable.remove(dependency_res_p)
-                portable.remove(provider_res_p)
-                
-                os.rmdir(tmp_d)
-                portable.remove(dependency_mp)
-                portable.remove(provider_mp)                
+                        # Run resolver and check the output.
+                        self.pkgdepend(
+                            "resolve %s %s" % (dependency_mp, provider_mp),
+                            use_proto=False)
+                        self.check_res("", self.output)
+                        self.check_res("", self.errout)
+                        dependency_res_p = dependency_mp + ".res"
+                        provider_res_p = provider_mp + ".res"
+                        lines = self.__read_file(dependency_res_p)
+                        self.check_res(self.pyver_resolve_results % {
+                                "res_manf": os.path.basename(provider_mp),
+                                "pfx":
+                                    base.Dependency.DEPEND_DEBUG_PREFIX,
+                                "py_ver": py_ver,
+                                "reason": fp
+                            }, lines)
+                        lines = self.__read_file(provider_res_p)
+                        self.check_res("", lines)
+
+                        # Clean up
+                        portable.remove(dependency_res_p)
+                        portable.remove(provider_res_p)
 
         def test_resolve_screen_out(self):
                 """Check that the results printed to screen are what is