diff --git a/bin/spack b/bin/spack
index ac9cd5738f77790dbbf280179600bc358cf72cbe..60a2ef4c3ad742d403738566aaa099cf247eea1c 100755
--- a/bin/spack
+++ b/bin/spack
@@ -74,9 +74,10 @@ args = parser.parse_args()
 spack.verbose = args.verbose
 spack.debug = args.debug
 if args.mock:
-    from spack.util.filesystem import new_path
-    mock_path = new_path(spack.module_path, 'test', 'mock_packages')
-    spack.packages_path = mock_path
+    from llnl.util.filesystem import join_path
+    from spack.packages import PackageDB
+    mock_path = join_path(spack.module_path, 'test', 'mock_packages')
+    spack.db = PackageDB(mock_path)
 
 # If the user asked for it, don't check ssl certs.
 if args.insecure:
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index 4867d1b520e02c9c253ff1b7eb8377209b033a5a..97ce91386b03fa449a7df40d520e3409a64e0fdf 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -34,7 +34,6 @@
 
 import spack
 import spack.cmd
-import spack.packages as packages
 import spack.util.crypto
 from spack.stage import Stage, FailedDownloadError
 from spack.version import *
@@ -81,7 +80,7 @@ def get_checksums(versions, urls, **kwargs):
 
 def checksum(parser, args):
     # get the package we're going to generate checksums for
-    pkg = packages.get(args.package)
+    pkg = spack.db.get(args.package)
 
     # If the user asked for specific versions, use those.
     versions = [ver(v) for v in args.versions]
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
index d54b529d3c2057a44b323ea231279bb0b3e3faf3..6091cae6c8e1aa05c654f5c02683ab6b2c831228 100644
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -26,8 +26,8 @@
 
 import llnl.util.tty as tty
 
+import spack
 import spack.cmd
-import spack.packages as packages
 import spack.stage as stage
 
 description = "Remove staged files for packages"
@@ -49,7 +49,7 @@ def clean(parser, args):
 
     specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
-        package = packages.get(spec)
+        package = spack.db.get(spec)
         if args.dist:
             package.do_clean_dist()
         elif args.work:
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index 236485f9a192e1661066f6c6bab2057ddc1d1377..e8fbb46d7a2efada04b10058a3415ae35147cad1 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -33,7 +33,6 @@
 import spack
 import spack.cmd
 import spack.package
-import spack.packages as packages
 import spack.url
 import spack.util.crypto as crypto
 import spack.cmd.checksum
@@ -131,7 +130,7 @@ def create(parser, args):
         tty.msg("Couldn't guess a name for this package.")
         while not name:
             new_name = raw_input("Name: ")
-            if packages.valid_name(name):
+            if spack.db.valid_name(name):
                 name = new_name
             else:
                 print "Package name can only contain A-Z, a-z, 0-9, '_' and '-'"
@@ -141,11 +140,11 @@ def create(parser, args):
 
     tty.msg("Creating template for package %s" % name)
 
-    pkg_path = packages.filename_for_package_name(name)
+    pkg_path = spack.db.filename_for_package_name(name)
     if os.path.exists(pkg_path) and not args.force:
         tty.die("%s already exists." % pkg_path)
 
-    class_name = packages.class_name_for_package_name(name)
+    class_name = spack.db.class_name_for_package_name(name)
     versions = list(reversed(spack.package.find_versions_of_archive(url)))
 
     archives_to_fetch = 1
diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py
index b6373872c02a39703a4ae89b3ef0a5aa65b807a3..8a2256709987e97b9954f79b52067f887dc2c70f 100644
--- a/lib/spack/spack/cmd/edit.py
+++ b/lib/spack/spack/cmd/edit.py
@@ -29,7 +29,6 @@
 import llnl.util.tty as tty
 
 import spack
-import spack.packages as packages
 
 description = "Open package files in $EDITOR"
 
@@ -67,7 +66,7 @@ def edit(parser, args):
     if not name:
         path = spack.packages_path
     else:
-        path = packages.filename_for_package_name(name)
+        path = spack.db.filename_for_package_name(name)
 
         if os.path.exists(path):
             if not os.path.isfile(path):
@@ -78,7 +77,7 @@ def edit(parser, args):
             tty.die("No package '%s'.  Use spack create, or supply -f/--force "
                     "to edit a new file." % name)
         else:
-            class_name = packages.class_name_for_package_name(name)
+            class_name = spack.db.class_name_for_package_name(name)
 
             with closing(open(path, "w")) as pkg_file:
                 pkg_file.write(
diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py
index 86bc9cdef375ed206557255d4bdc9353c9cd7987..1dd8703daf23a004c513bad91b41cadb6578612d 100644
--- a/lib/spack/spack/cmd/fetch.py
+++ b/lib/spack/spack/cmd/fetch.py
@@ -24,8 +24,8 @@
 ##############################################################################
 import argparse
 
+import spack
 import spack.cmd
-import spack.packages as packages
 
 description = "Fetch archives for packages"
 
@@ -46,5 +46,5 @@ def fetch(parser, args):
 
     specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
-        package = packages.get(spec)
+        package = spack.db.get(spec)
         package.do_fetch()
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index 3db0608d6b1d681fdfb47c9428cb401aff8aad7c..9e8bf190a8440f2ab159f15b0ed2c2ca7902813f 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -31,7 +31,7 @@
 
 import spack
 import spack.spec
-import spack.packages as packages
+import spack
 
 
 description ="Find installed spack packages"
@@ -83,7 +83,7 @@ def hasher():
 
     # Make a dict with specs keyed by architecture and compiler.
     index = hasher()
-    for spec in packages.installed_package_specs():
+    for spec in spack.db.installed_package_specs():
         if query_specs and not any(spec.satisfies(q) for q in query_specs):
             continue
 
diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py
index 07fe67ca4bf9726c857adafc3e4fb08ef1ac0999..39dbfbb1508062d776d4b32d4866b73c7e88c16e 100644
--- a/lib/spack/spack/cmd/graph.py
+++ b/lib/spack/spack/cmd/graph.py
@@ -23,9 +23,8 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import spack
-import spack.packages as packages
 
 description = "Write out inter-package dependencies in dot graph format"
 
 def graph(parser, args):
-    packages.graph_dependencies()
+    spack.db.graph_dependencies()
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 3a17b2ce3c4d49b22f56802f82bc9800c8eff66b..648dbf905a128525fe8bf301e8a4a6729e8626ce 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -26,7 +26,6 @@
 import textwrap
 from llnl.util.tty.colify import colify
 import spack
-import spack.packages as packages
 
 description = "Get detailed information on a particular package"
 
@@ -35,7 +34,7 @@ def setup_parser(subparser):
 
 
 def info(parser, args):
-    package = packages.get(args.name)
+    package = spack.db.get(args.name)
     print "Package:   ", package.name
     print "Homepage:  ", package.homepage
     print "Download:  ", package.url
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index f80baf0ad641e7ffc742232c9a72ea0d3f431e1b..02194c1b3fa47a48773ddbdc9fd9182f3536374b 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -26,7 +26,6 @@
 import argparse
 
 import spack
-import spack.packages as packages
 import spack.cmd
 
 description = "Build and install packages"
@@ -56,6 +55,6 @@ def install(parser, args):
     specs = spack.cmd.parse_specs(args.packages, concretize=True)
 
     for spec in specs:
-        package = packages.get(spec)
+        package = spack.db.get(spec)
         package.dirty = args.dirty
         package.do_install()
diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py
index 15e15818686d95a7e504351e98c7b18f4f3a1734..2b996371ea9a07c0ccc8175fe46c24a8bf5a0331 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -22,8 +22,8 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import spack.packages as packages
 from llnl.util.tty.colify import colify
+import spack
 
 description ="List available spack packages"
 
@@ -33,4 +33,4 @@ def setup_parser(subparser):
 
 def list(parser, args):
     # Print all the package names in columns
-    colify(packages.all_package_names())
+    colify(spack.db.all_package_names())
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
index 91fe38fe677cc1e15398d76143145e852d34d25b..94e40557fff865cd70494603c71bd6a6117c506b 100644
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -29,9 +29,8 @@
 import llnl.util.tty as tty
 from llnl.util.filesystem import mkdirp, join_path
 
-import spack.packages as packages
+import spack
 import spack.cmd
-
 from spack.stage import Stage
 
 
@@ -46,7 +45,7 @@ def setup_parser(subparser):
 
 def mirror(parser, args):
     if not args.packages:
-        args.packages = [p for p in packages.all_package_names()]
+        args.packages = [p for p in spack.db.all_package_names()]
 
     if os.path.isfile(args.directory):
         tty.error("%s already exists and is a file." % args.directory)
@@ -59,7 +58,7 @@ def mirror(parser, args):
 
     # Iterate through packages and download all the safe tarballs for each of them
     for pkg_name in args.packages:
-        pkg = packages.get(pkg_name)
+        pkg = spack.db.get(pkg_name)
 
         # Skip any package that has no checksummed versions.
         if not pkg.versions:
diff --git a/lib/spack/spack/cmd/patch.py b/lib/spack/spack/cmd/patch.py
index cc790df56e172c6d183197b1c6338ea579181fe0..2356583b07466f681223f7ae3b38e6988d00e1c5 100644
--- a/lib/spack/spack/cmd/patch.py
+++ b/lib/spack/spack/cmd/patch.py
@@ -25,7 +25,7 @@
 import argparse
 
 import spack.cmd
-import spack.packages as packages
+import spack
 
 
 description="Patch expanded archive sources in preparation for install"
@@ -47,5 +47,5 @@ def patch(parser, args):
 
     specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
-        package = packages.get(spec)
+        package = spack.db.get(spec)
         package.do_patch()
diff --git a/lib/spack/spack/cmd/providers.py b/lib/spack/spack/cmd/providers.py
index b59950aeb3a64bf87db61fcfe65586eb419c2527..1a652c82d173f016e3bb353100623f53562f0838 100644
--- a/lib/spack/spack/cmd/providers.py
+++ b/lib/spack/spack/cmd/providers.py
@@ -27,8 +27,8 @@
 
 from llnl.util.tty.colify import colify
 
+import spack
 import spack.cmd
-import spack.packages
 
 description ="List packages that provide a particular virtual package"
 
@@ -39,4 +39,4 @@ def setup_parser(subparser):
 
 def providers(parser, args):
     for spec in spack.cmd.parse_specs(args.vpkg_spec):
-        colify(sorted(spack.packages.providers_for(spec)), indent=4)
+        colify(sorted(spack.db.providers_for(spec)), indent=4)
diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py
index 2ce3d66bcbebd9fab1ed8c4ebd8e54b35de5d71b..1bf1f93c2fbd20a397ec1498d6a44d6c910db513 100644
--- a/lib/spack/spack/cmd/stage.py
+++ b/lib/spack/spack/cmd/stage.py
@@ -24,9 +24,8 @@
 ##############################################################################
 import argparse
 
+import spack
 import spack.cmd
-import spack.packages as packages
-
 
 description="Expand downloaded archive in preparation for install"
 
@@ -47,5 +46,5 @@ def stage(parser, args):
 
     specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
-        package = packages.get(spec)
+        package = spack.db.get(spec)
         package.do_stage()
diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py
index 16d693aa21de282ed423cd5b1405d4e88c163fe4..b1418ac2f1b72190eb62f476e59024457a427200 100644
--- a/lib/spack/spack/cmd/test.py
+++ b/lib/spack/spack/cmd/test.py
@@ -28,7 +28,6 @@
 from llnl.util.lang import list_modules
 
 import spack
-import spack.packages as packages
 import spack.test
 
 description ="Run unit tests"
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index e47b4d7f7a23c3e1befecc8e84e6bb6b14cae208..7982892d81db0fce55285b36d338a82055d95179 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -26,8 +26,8 @@
 
 import llnl.util.tty as tty
 
+import spack
 import spack.cmd
-import spack.packages as packages
 
 description="Remove an installed package"
 
@@ -49,7 +49,7 @@ def uninstall(parser, args):
     # Fail and ask user to be unambiguous if it doesn't
     pkgs = []
     for spec in specs:
-        matching_specs = packages.get_installed(spec)
+        matching_specs = spack.db.get_installed(spec)
         if len(matching_specs) > 1:
             tty.die("%s matches multiple packages.  Which one did you mean?"
                     % spec, *matching_specs)
@@ -58,7 +58,7 @@ def uninstall(parser, args):
             tty.die("%s does not match any installed packages." % spec)
 
         installed_spec = matching_specs[0]
-        pkgs.append(packages.get(installed_spec))
+        pkgs.append(spack.db.get(installed_spec))
 
     # Sort packages to be uninstalled by the number of installed dependents
     # This ensures we do things in the right order
diff --git a/lib/spack/spack/cmd/versions.py b/lib/spack/spack/cmd/versions.py
index 1f0da9fbd82970a46a1413964101724207dc64e4..c54503527956af1fd4d7ae90952098431e232685 100644
--- a/lib/spack/spack/cmd/versions.py
+++ b/lib/spack/spack/cmd/versions.py
@@ -24,7 +24,7 @@
 ##############################################################################
 import os
 from llnl.util.tty.colify import colify
-import spack.packages as packages
+import spack
 
 description ="List available versions of a package"
 
@@ -33,5 +33,5 @@ def setup_parser(subparser):
 
 
 def versions(parser, args):
-    pkg = packages.get(args.package)
+    pkg = spack.db.get(args.package)
     colify(reversed(pkg.fetch_available_versions()))
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index 790d67cac835ec1087339ead88d95cbd6a72d4a0..fc360d59ba9dc643368119935997987f8e7252ea 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -35,7 +35,6 @@
 """
 import spack.architecture
 import spack.compilers
-import spack.packages
 import spack.spec
 from spack.version import *
 
diff --git a/lib/spack/spack/globals.py b/lib/spack/spack/globals.py
index 2cf2761362105d7f9e91a021f13cc51a16d71084..20801447fa81718144750074f79b88a8df58c43f 100644
--- a/lib/spack/spack/globals.py
+++ b/lib/spack/spack/globals.py
@@ -30,6 +30,7 @@
 from spack.util.executable import *
 from spack.directory_layout import SpecHashDirectoryLayout
 from spack.concretize import DefaultConcretizer
+from spack.packages import PackageDB
 
 # This lives in $prefix/lib/spac/spack/__file__
 prefix = ancestor(__file__, 4)
@@ -41,7 +42,6 @@
 lib_path       = join_path(prefix, "lib", "spack")
 env_path       = join_path(lib_path, "env")
 module_path    = join_path(lib_path, "spack")
-packages_path  = join_path(module_path, "packages")
 compilers_path = join_path(module_path, "compilers")
 test_path      = join_path(module_path, "test")
 
@@ -50,6 +50,12 @@
 
 install_path   = join_path(prefix, "opt")
 
+#
+# Set up the packages database.
+#
+db = PackageDB(join_path(module_path, "packages"))
+
+
 #
 # This controls how spack lays out install prefixes and
 # stage directories.
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 68e4e30b3b42278f646581c8a88b321a93883bdb..ff020fb44c2e23c2ed831e83b26cf584644a2efb 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -44,19 +44,19 @@
 
 import llnl.util.tty as tty
 from llnl.util.tty.color import cwrite
-from llnl.util.filesystem import touch
+from llnl.util.filesystem import *
 from llnl.util.lang import *
 
-from spack import *
+import spack
 import spack.spec
 import spack.error
-import spack.packages as packages
 import spack.url as url
 import spack.util.crypto as crypto
 from spack.version import *
 from spack.stage import Stage
 from spack.util.web import get_pages
 from spack.util.environment import *
+from spack.util.executable import Executable, which
 from spack.util.compression import allowed_archive
 
 """Allowed URL schemes for spack packages."""
@@ -480,7 +480,7 @@ def preorder_traversal(self, visited=None, **kwargs):
                     yield spec
                 continue
 
-            for pkg in packages.get(name).preorder_traversal(visited, **kwargs):
+            for pkg in spack.db.get(name).preorder_traversal(visited, **kwargs):
                 yield pkg
 
 
@@ -539,7 +539,7 @@ def installed_dependents(self):
         """Return a list of the specs of all installed packages that depend
            on this one."""
         dependents = []
-        for spec in packages.installed_package_specs():
+        for spec in spack.db.installed_package_specs():
             if self.name in spec.dependencies:
                 dep_spec = spec.dependencies[self.name]
                 if self.spec == dep_spec:
@@ -594,7 +594,7 @@ def do_fetch(self):
 
         self.stage.fetch()
 
-        if self.version in self.versions:
+        if spack.do_checksum and self.version in self.versions:
             digest = self.versions[self.version]
             checker = crypto.Checker(digest)
             if checker.check(self.stage.archive_file):
@@ -720,28 +720,28 @@ def setup_install_environment(self):
 
         # Add spack environment at front of path and pass the
         # lib location along so the compiler script can find spack
-        os.environ[SPACK_LIB] = lib_path
+        os.environ[spack.SPACK_LIB] = spack.lib_path
 
         # Fix for case-insensitive file systems.  Conflicting links are
         # in directories called "case*" within the env directory.
-        env_paths = [env_path]
-        for file in os.listdir(env_path):
-            path = join_path(env_path, file)
+        env_paths = [spack.env_path]
+        for file in os.listdir(spack.env_path):
+            path = join_path(spack.env_path, file)
             if file.startswith("case") and os.path.isdir(path):
                 env_paths.append(path)
         path_put_first("PATH", env_paths)
-        path_set(SPACK_ENV_PATH, env_paths)
+        path_set(spack.SPACK_ENV_PATH, env_paths)
 
         # Pass along prefixes of dependencies here
         path_set(
-            SPACK_DEPENDENCIES,
+            spack.SPACK_DEPENDENCIES,
             [dep.package.prefix for dep in self.spec.dependencies.values()])
 
         # Install location
-        os.environ[SPACK_PREFIX] = self.prefix
+        os.environ[spack.SPACK_PREFIX] = self.prefix
 
         # Build root for logging.
-        os.environ[SPACK_BUILD_ROOT] = self.stage.expanded_archive_path
+        os.environ[spack.SPACK_BUILD_ROOT] = self.stage.expanded_archive_path
 
 
     def do_install_dependencies(self):
@@ -887,7 +887,7 @@ def __init__(self, name, parallel):
 
     def __call__(self, *args, **kwargs):
         parallel = kwargs.get('parallel', self.parallel)
-        disable_parallel = env_flag(SPACK_NO_PARALLEL_MAKE)
+        disable_parallel = env_flag(spack.SPACK_NO_PARALLEL_MAKE)
 
         if parallel and not disable_parallel:
             jobs = "-j%d" % multiprocessing.cpu_count()
diff --git a/lib/spack/spack/packages/__init__.py b/lib/spack/spack/packages/__init__.py
index 5731fb0d99e5a35c278baa1d19f73b482db949b1..beb8ecf6abc093aeb992137564ae5d7c17175183 100644
--- a/lib/spack/spack/packages/__init__.py
+++ b/lib/spack/spack/packages/__init__.py
@@ -28,14 +28,22 @@
 import string
 import inspect
 import glob
+import imp
 
 import llnl.util.tty as tty
 from llnl.util.filesystem import join_path
-from llnl.util.lang import list_modules
+from llnl.util.lang import memoized
 
 import spack
 import spack.error
 import spack.spec
+from spack.virtual import ProviderIndex
+
+# Name of module under which packages are imported
+_imported_packages_module = 'spack.packages'
+
+# Name of the package file inside a package directory
+_package_file_name = 'package.py'
 
 # Valid package names can contain '-' but can't start with it.
 valid_package_re = r'^\w[\w-]*$'
@@ -43,323 +51,216 @@
 # Don't allow consecutive [_-] in package names
 invalid_package_re = r'[_-][_-]+'
 
-instances = {}
-
-
-def _autospec(function):
-    """Decorator that automatically converts the argument of a single-arg
-       function to a Spec."""
-    def converter(arg):
-        if not isinstance(arg, spack.spec.Spec):
-            arg = spack.spec.Spec(arg)
-        return function(arg)
-    return converter
-
-
-class ProviderIndex(object):
-    """This is a dict of dicts used for finding providers of particular
-       virtual dependencies. The dict of dicts looks like:
-
-       { vpkg name :
-           { full vpkg spec : package providing spec } }
-
-       Callers can use this to first find which packages provide a vpkg,
-       then find a matching full spec.  e.g., in this scenario:
-
-       { 'mpi' :
-           { mpi@:1.1 : mpich,
-             mpi@:2.3 : mpich2@1.9: } }
-
-       Calling providers_for(spec) will find specs that provide a
-       matching implementation of MPI.
-    """
-    def __init__(self, specs, **kwargs):
-        # TODO: come up with another name for this.  This "restricts" values to
-        # the verbatim impu specs (i.e., it doesn't pre-apply package's constraints, and
-        # keeps things as broad as possible, so it's really the wrong name)
-        self.restrict = kwargs.setdefault('restrict', False)
-
-        self.providers = {}
-
-        for spec in specs:
-            if not isinstance(spec, spack.spec.Spec):
-                spec = spack.spec.Spec(spec)
-
-            if spec.virtual:
-                continue
-
-            self.update(spec)
-
-
-    def update(self, spec):
-        if type(spec) != spack.spec.Spec:
-            spec = spack.spec.Spec(spec)
-
-        assert(not spec.virtual)
-
-        pkg = spec.package
-        for provided_spec, provider_spec in pkg.provided.iteritems():
-            if provider_spec.satisfies(spec, deps=False):
-                provided_name = provided_spec.name
-                if provided_name not in self.providers:
-                    self.providers[provided_name] = {}
-
-                if self.restrict:
-                    self.providers[provided_name][provided_spec] = spec
-
-                else:
-                    # Before putting the spec in the map, constrain it so that
-                    # it provides what was asked for.
-                    constrained = spec.copy()
-                    constrained.constrain(provider_spec)
-                    self.providers[provided_name][provided_spec] = constrained
-
-
-    def providers_for(self, *vpkg_specs):
-        """Gives specs of all packages that provide virtual packages
-           with the supplied specs."""
-        providers = set()
-        for vspec in vpkg_specs:
-            # Allow string names to be passed as input, as well as specs
-            if type(vspec) == str:
-                vspec = spack.spec.Spec(vspec)
-
-            # Add all the providers that satisfy the vpkg spec.
-            if vspec.name in self.providers:
-                for provider_spec, spec in self.providers[vspec.name].items():
-                    if provider_spec.satisfies(vspec, deps=False):
-                        providers.add(spec)
-
-        # Return providers in order
-        return sorted(providers)
-
-
-    # TODO: this is pretty darned nasty, and inefficient.
-    def _cross_provider_maps(self, lmap, rmap):
-        result = {}
-        for lspec in lmap:
-            for rspec in rmap:
-                try:
-                    constrained = lspec.copy().constrain(rspec)
-                    if lmap[lspec].name != rmap[rspec].name:
-                        continue
-                    result[constrained] = lmap[lspec].copy().constrain(
-                        rmap[rspec], deps=False)
-                except spack.spec.UnsatisfiableSpecError:
-                    continue
-        return result
-
-
-    def __contains__(self, name):
-        """Whether a particular vpkg name is in the index."""
-        return name in self.providers
-
-
-    def satisfies(self, other):
-        """Check that providers of virtual specs are compatible."""
-        common = set(self.providers) & set(other.providers)
-        if not common:
-            return True
-
-        result = {}
-        for name in common:
-            crossed = self._cross_provider_maps(self.providers[name],
-                                                other.providers[name])
-            if crossed:
-                result[name] = crossed
-
-        return bool(result)
-
-
-
-@_autospec
-def get(spec):
-    if spec.virtual:
-        raise UnknownPackageError(spec.name)
-
-    if not spec in instances:
-        package_class = get_class_for_package_name(spec.name)
-        instances[spec.name] = package_class(spec)
-
-    return instances[spec.name]
-
-
-@_autospec
-def get_installed(spec):
-    return [s for s in installed_package_specs() if s.satisfies(spec)]
-
-
-@_autospec
-def providers_for(vpkg_spec):
-    if not hasattr(providers_for, 'index'):
-        providers_for.index = ProviderIndex(all_package_names())
-
-    providers = providers_for.index.providers_for(vpkg_spec)
-    if not providers:
-        raise UnknownPackageError("No such virtual package: %s" % vpkg_spec)
-    return providers
-
 
 def valid_package_name(pkg_name):
+    """Return whether the pkg_name is valid for use in Spack."""
     return (re.match(valid_package_re, pkg_name) and
             not re.search(invalid_package_re, pkg_name))
 
 
 def validate_package_name(pkg_name):
+    """Raise an exception if pkg_name is not valid."""
     if not valid_package_name(pkg_name):
         raise InvalidPackageNameError(pkg_name)
 
 
-def dirname_for_package_name(pkg_name):
-    """Get the directory name for a particular package would use, even if it's a
-       foo.py package and not a directory with a foo/__init__.py file."""
-    return join_path(spack.packages_path, pkg_name)
+def class_name_for_package_name(pkg_name):
+    """Get a name for the class the package file should contain.  Note that
+       conflicts don't matter because the classes are in different modules.
+    """
+    validate_package_name(pkg_name)
 
+    class_name = pkg_name.replace('_', '-')
+    class_name = string.capwords(class_name, '-')
+    class_name = class_name.replace('-', '')
 
-def filename_for_package_name(pkg_name):
-    """Get the filename for the module we should load for a particular package.
-       The package can be either in a standalone .py file, or it can be in
-       a directory with an __init__.py file.
+    # If a class starts with a number, prefix it with Number_ to make it a valid
+    # Python class name.
+    if re.match(r'^[0-9]', class_name):
+        class_name = "Num_%s" % class_name
 
-       Package "foo" in standalone .py file:
-         packages/foo.py
+    return class_name
 
-       Package "foo" in directory:
-         packages/foo/__init__.py
 
-       The second form is used when there are files (like patches) that need
-       to be stored along with the package.
+def _autospec(function):
+    """Decorator that automatically converts the argument of a single-arg
+       function to a Spec."""
+    def converter(self, spec_like):
+        if not isinstance(spec_like, spack.spec.Spec):
+            spec_like = spack.spec.Spec(spec_like)
+        return function(self, spec_like)
+    return converter
 
-       If the package doesn't exist yet, this will just return the name
-       of the standalone .py file.
-    """
-    validate_package_name(pkg_name)
-    pkg_dir = dirname_for_package_name(pkg_name)
 
-    if os.path.isdir(pkg_dir):
-        init_file = join_path(pkg_dir, '__init__.py')
-        return init_file
-    else:
-        pkg_file  = "%s.py" % pkg_dir
-        return pkg_file
+class PackageDB(object):
+    def __init__(self, root):
+        """Construct a new package database from a root directory."""
+        self.root = root
+        self.instances = {}
+        self.provider_index = None
 
 
-def installed_package_specs():
-    return spack.install_layout.all_specs()
+    @_autospec
+    def get(self, spec):
+        if spec.virtual:
+            raise UnknownPackageError(spec.name)
 
+        if not spec in self.instances:
+            package_class = self.get_class_for_package_name(spec.name)
+            self.instances[spec.name] = package_class(spec)
 
-def all_package_names():
-    """Generator function for all packages."""
-    for module in list_modules(spack.packages_path):
-        yield module
+        return self.instances[spec.name]
 
 
-def all_packages():
-    for name in all_package_names():
-        yield get(name)
+    @_autospec
+    def get_installed(self, spec):
+        return [s for s in self.installed_package_specs() if s.satisfies(spec)]
 
 
-def class_name_for_package_name(pkg_name):
-    """Get a name for the class the package file should contain.  Note that
-       conflicts don't matter because the classes are in different modules.
-    """
-    validate_package_name(pkg_name)
+    @_autospec
+    def providers_for(self, vpkg_spec):
+        if self.provider_index is None:
+            self.provider_index = ProviderIndex(self.all_package_names())
 
-    class_name = pkg_name.replace('_', '-')
-    class_name = string.capwords(class_name, '-')
-    class_name = class_name.replace('-', '')
+        providers = self.provider_index.providers_for(vpkg_spec)
+        if not providers:
+            raise UnknownPackageError("No such virtual package: %s" % vpkg_spec)
+        return providers
 
-    # If a class starts with a number, prefix it with Number_ to make it a valid
-    # Python class name.
-    if re.match(r'^[0-9]', class_name):
-        class_name = "Num_%s" % class_name
 
-    return class_name
+    def dirname_for_package_name(self, pkg_name):
+        """Get the directory name for a particular package.  This is the
+           directory that contains its package.py file."""
+        return join_path(self.root, pkg_name)
 
 
-def exists(pkg_name):
-    """Whether a package with the supplied name exists ."""
-    return os.path.exists(filename_for_package_name(pkg_name))
+    def filename_for_package_name(self, pkg_name):
+        """Get the filename for the module we should load for a particular
+           package.  Packages for a pacakge DB live in
+           ``$root/<package_name>/package.py``
 
+           This will return a proper package.py path even if the
+           package doesn't exist yet, so callers will need to ensure
+           the package exists before importing.
+        """
+        validate_package_name(pkg_name)
+        pkg_dir = self.dirname_for_package_name(pkg_name)
+        return join_path(pkg_dir, _package_file_name)
 
-def packages_module():
-    # TODO: replace this with a proper package DB class, instead of this hackiness.
-    packages_path = re.sub(spack.module_path + '\/+', 'spack.', spack.packages_path)
-    packages_module = re.sub(r'/', '.', packages_path)
-    return packages_module
 
+    def installed_package_specs(self):
+        """Read installed package names straight from the install directory
+           layout.
+        """
+        return spack.install_layout.all_specs()
 
-def get_class_for_package_name(pkg_name):
-    file_name = filename_for_package_name(pkg_name)
 
-    if os.path.exists(file_name):
-        if not os.path.isfile(file_name):
-            tty.die("Something's wrong. '%s' is not a file!" % file_name)
-        if not os.access(file_name, os.R_OK):
-            tty.die("Cannot read '%s'!" % file_name)
-    else:
-        raise UnknownPackageError(pkg_name)
+    @memoized
+    def all_package_names(self):
+        """Generator function for all packages.  This looks for
+           ``<pkg_name>/package.py`` files within the root direcotry"""
+        all_package_names = []
+        for pkg_name in os.listdir(self.root):
+            pkg_dir  = join_path(self.root, pkg_name)
+            pkg_file = join_path(pkg_dir, _package_file_name)
+            if os.path.isfile(pkg_file):
+                all_package_names.append(pkg_name)
+            all_package_names.sort()
+        return all_package_names
 
-    # Figure out pacakges module from spack.packages_path
-    # This allows us to change the module path.
-    if not re.match(r'%s' % spack.module_path, spack.packages_path):
-        raise RuntimeError("Packages path is not a submodule of spack.")
 
-    class_name = class_name_for_package_name(pkg_name)
-    try:
-        module_name = "%s.%s" % (packages_module(), pkg_name)
-        module = __import__(module_name, fromlist=[class_name])
-    except ImportError, e:
-        tty.die("Error while importing %s.%s:\n%s" % (pkg_name, class_name, e.message))
+    def all_packages(self):
+        for name in self.all_package_names():
+            yield get(name)
 
-    cls = getattr(module, class_name)
-    if not inspect.isclass(cls):
-        tty.die("%s.%s is not a class" % (pkg_name, class_name))
 
-    return cls
+    def exists(self, pkg_name):
+        """Whether a package with the supplied name exists ."""
+        return os.path.exists(self.filename_for_package_name(pkg_name))
 
 
-def compute_dependents():
-    """Reads in all package files and sets dependence information on
-       Package objects in memory.
-    """
-    if not hasattr(compute_dependents, index):
-        compute_dependents.index = {}
-
-    for pkg in all_packages():
-        if pkg._dependents is None:
-            pkg._dependents = []
-
-        for name, dep in pkg.dependencies.iteritems():
-            dpkg = get(name)
-            if dpkg._dependents is None:
-                dpkg._dependents = []
-            dpkg._dependents.append(pkg.name)
-
-
-def graph_dependencies(out=sys.stdout):
-    """Print out a graph of all the dependencies between package.
-       Graph is in dot format."""
-    out.write('digraph G {\n')
-    out.write('  label = "Spack Dependencies"\n')
-    out.write('  labelloc = "b"\n')
-    out.write('  rankdir = "LR"\n')
-    out.write('  ranksep = "5"\n')
-    out.write('\n')
-
-    def quote(string):
-        return '"%s"' % string
-
-    deps = []
-    for pkg in all_packages():
-        out.write('  %-30s [label="%s"]\n' % (quote(pkg.name), pkg.name))
-        for dep_name, dep in pkg.dependencies.iteritems():
-            deps.append((pkg.name, dep_name))
-    out.write('\n')
-
-    for pair in deps:
-        out.write('  "%s" -> "%s"\n' % pair)
-    out.write('}\n')
+    @memoized
+    def get_class_for_package_name(self, pkg_name):
+        """Get an instance of the class for a particular package.
+
+           This method uses Python's ``imp`` package to load python
+           source from a Spack package's ``package.py`` file.  A
+           normal python import would only load each package once, but
+           because we do this dynamically, the method needs to be
+           memoized to ensure there is only ONE package class
+           instance, per package, per database.
+        """
+        file_path = self.filename_for_package_name(pkg_name)
+
+        if os.path.exists(file_path):
+            if not os.path.isfile(file_path):
+                tty.die("Something's wrong. '%s' is not a file!" % file_path)
+            if not os.access(file_path, os.R_OK):
+                tty.die("Cannot read '%s'!" % file_path)
+        else:
+            raise UnknownPackageError(pkg_name)
+
+        # Figure out pacakges module based on self.root
+        if not re.match(r'%s' % spack.module_path, self.root):
+            raise RuntimeError("Packages path is not a submodule of spack.")
+
+        class_name = class_name_for_package_name(pkg_name)
+        try:
+            module_name = _imported_packages_module + '.' + pkg_name
+            module = imp.load_source(module_name, file_path)
+
+        except ImportError, e:
+            tty.die("Error while importing %s from %s:\n%s" % (
+                pkg_name, file_path, e.message))
+
+        cls = getattr(module, class_name)
+        if not inspect.isclass(cls):
+            tty.die("%s.%s is not a class" % (pkg_name, class_name))
+
+        return cls
+
+
+    def compute_dependents(self):
+        """Reads in all package files and sets dependence information on
+           Package objects in memory.
+        """
+        if not hasattr(compute_dependents, index):
+            compute_dependents.index = {}
+
+        for pkg in all_packages():
+            if pkg._dependents is None:
+                pkg._dependents = []
+
+            for name, dep in pkg.dependencies.iteritems():
+                dpkg = get(name)
+                if dpkg._dependents is None:
+                    dpkg._dependents = []
+                dpkg._dependents.append(pkg.name)
+
+
+    def graph_dependencies(self, out=sys.stdout):
+        """Print out a graph of all the dependencies between package.
+           Graph is in dot format."""
+        out.write('digraph G {\n')
+        out.write('  label = "Spack Dependencies"\n')
+        out.write('  labelloc = "b"\n')
+        out.write('  rankdir = "LR"\n')
+        out.write('  ranksep = "5"\n')
+        out.write('\n')
+
+        def quote(string):
+            return '"%s"' % string
+
+        deps = []
+        for pkg in all_packages():
+            out.write('  %-30s [label="%s"]\n' % (quote(pkg.name), pkg.name))
+            for dep_name, dep in pkg.dependencies.iteritems():
+                deps.append((pkg.name, dep_name))
+        out.write('\n')
+
+        for pair in deps:
+            out.write('  "%s" -> "%s"\n' % pair)
+        out.write('}\n')
 
 
 class InvalidPackageNameError(spack.error.SpackError):
diff --git a/lib/spack/spack/packages/callpath.py b/lib/spack/spack/packages/callpath/package.py
similarity index 100%
rename from lib/spack/spack/packages/callpath.py
rename to lib/spack/spack/packages/callpath/package.py
diff --git a/lib/spack/spack/packages/cmake.py b/lib/spack/spack/packages/cmake/package.py
similarity index 100%
rename from lib/spack/spack/packages/cmake.py
rename to lib/spack/spack/packages/cmake/package.py
diff --git a/lib/spack/spack/packages/dyninst.py b/lib/spack/spack/packages/dyninst/package.py
similarity index 100%
rename from lib/spack/spack/packages/dyninst.py
rename to lib/spack/spack/packages/dyninst/package.py
diff --git a/lib/spack/spack/packages/graphlib.py b/lib/spack/spack/packages/graphlib/package.py
similarity index 100%
rename from lib/spack/spack/packages/graphlib.py
rename to lib/spack/spack/packages/graphlib/package.py
diff --git a/lib/spack/spack/packages/launchmon/__init__.py b/lib/spack/spack/packages/launchmon/package.py
similarity index 100%
rename from lib/spack/spack/packages/launchmon/__init__.py
rename to lib/spack/spack/packages/launchmon/package.py
diff --git a/lib/spack/spack/packages/libdwarf.py b/lib/spack/spack/packages/libdwarf/package.py
similarity index 100%
rename from lib/spack/spack/packages/libdwarf.py
rename to lib/spack/spack/packages/libdwarf/package.py
diff --git a/lib/spack/spack/packages/libelf.py b/lib/spack/spack/packages/libelf/package.py
similarity index 100%
rename from lib/spack/spack/packages/libelf.py
rename to lib/spack/spack/packages/libelf/package.py
diff --git a/lib/spack/spack/packages/libunwind.py b/lib/spack/spack/packages/libunwind/package.py
similarity index 100%
rename from lib/spack/spack/packages/libunwind.py
rename to lib/spack/spack/packages/libunwind/package.py
diff --git a/lib/spack/spack/packages/mpich.py b/lib/spack/spack/packages/mpich/package.py
similarity index 100%
rename from lib/spack/spack/packages/mpich.py
rename to lib/spack/spack/packages/mpich/package.py
diff --git a/lib/spack/spack/packages/mpileaks.py b/lib/spack/spack/packages/mpileaks/package.py
similarity index 100%
rename from lib/spack/spack/packages/mpileaks.py
rename to lib/spack/spack/packages/mpileaks/package.py
diff --git a/lib/spack/spack/packages/mrnet.py b/lib/spack/spack/packages/mrnet/package.py
similarity index 100%
rename from lib/spack/spack/packages/mrnet.py
rename to lib/spack/spack/packages/mrnet/package.py
diff --git a/lib/spack/spack/packages/mvapich2/__init__.py b/lib/spack/spack/packages/mvapich2/package.py
similarity index 100%
rename from lib/spack/spack/packages/mvapich2/__init__.py
rename to lib/spack/spack/packages/mvapich2/package.py
diff --git a/lib/spack/spack/packages/openmpi/__init__.py b/lib/spack/spack/packages/openmpi/package.py
similarity index 100%
rename from lib/spack/spack/packages/openmpi/__init__.py
rename to lib/spack/spack/packages/openmpi/package.py
diff --git a/lib/spack/spack/packages/pmgr_collective.py b/lib/spack/spack/packages/pmgr_collective/package.py
similarity index 100%
rename from lib/spack/spack/packages/pmgr_collective.py
rename to lib/spack/spack/packages/pmgr_collective/package.py
diff --git a/lib/spack/spack/packages/scr.py b/lib/spack/spack/packages/scr/package.py
similarity index 100%
rename from lib/spack/spack/packages/scr.py
rename to lib/spack/spack/packages/scr/package.py
diff --git a/lib/spack/spack/packages/spindle.py b/lib/spack/spack/packages/spindle/package.py
similarity index 100%
rename from lib/spack/spack/packages/spindle.py
rename to lib/spack/spack/packages/spindle/package.py
diff --git a/lib/spack/spack/packages/stat.py b/lib/spack/spack/packages/stat/package.py
similarity index 100%
rename from lib/spack/spack/packages/stat.py
rename to lib/spack/spack/packages/stat/package.py
diff --git a/lib/spack/spack/patch.py b/lib/spack/spack/patch.py
index 2638000b27a518e416e288e7afd838b5d395aa50..42d49b15e5337949054003f08504c3b031a7b503 100644
--- a/lib/spack/spack/patch.py
+++ b/lib/spack/spack/patch.py
@@ -30,7 +30,6 @@
 import spack
 import spack.stage
 import spack.error
-import spack.packages as packages
 
 from spack.util.executable import which
 
@@ -55,7 +54,7 @@ def __init__(self, pkg_name, path_or_url, level):
         if '://' in path_or_url:
             self.url = path_or_url
         else:
-            pkg_dir = packages.dirname_for_package_name(pkg_name)
+            pkg_dir = spack.db.dirname_for_package_name(pkg_name)
             self.path = join_path(pkg_dir, path_or_url)
             if not os.path.isfile(self.path):
                 raise NoSuchPatchFileError(pkg_name, self.path)
diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py
index 3950bd1a20d3e2d68cea7ed1bfa4b10d2163c7b5..f46b7dfc84da8cd000417b5b8831bea4194e57c9 100644
--- a/lib/spack/spack/relations.py
+++ b/lib/spack/spack/relations.py
@@ -80,7 +80,6 @@ class Mpileaks(Package):
 
 from spack.patch import Patch
 from spack.spec import Spec, parse_anonymous_spec
-from spack.packages import packages_module
 
 
 """Adds a dependencies local variable in the locals of
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index a660e840d003aded27f2efa27f7fec430a29756e..a4f585b28a97db0763e75823e5de961452dfbbc0 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -99,16 +99,16 @@
 from llnl.util.lang import *
 from llnl.util.tty.color import *
 
+import spack
 import spack.parse
 import spack.error
 import spack.compilers
 import spack.compilers.gcc
-import spack.packages as packages
 
 from spack.version import *
 from spack.util.string import *
 from spack.util.prefix import Prefix
-
+from spack.virtual import ProviderIndex
 
 # Convenient names for color formats so that other things can use them
 compiler_color         = '@g'
@@ -379,7 +379,7 @@ def root(self):
 
     @property
     def package(self):
-        return packages.get(self)
+        return spack.db.get(self)
 
 
     @property
@@ -391,7 +391,7 @@ def virtual(self):
            Possible idea: just use conventin and make virtual deps all
            caps, e.g., MPI vs mpi.
         """
-        return not packages.exists(self.name)
+        return not spack.db.exists(self.name)
 
 
     @property
@@ -532,7 +532,7 @@ def _expand_virtual_packages(self):
                 return
 
             for spec in virtuals:
-                providers = packages.providers_for(spec)
+                providers = spack.db.providers_for(spec)
                 concrete = spack.concretizer.choose_provider(spec, providers)
                 concrete = concrete.copy()
                 spec._replace_with(concrete)
@@ -624,7 +624,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
 
         # Combine constraints from package dependencies with
         # constraints on the spec's dependencies.
-        pkg = packages.get(self.name)
+        pkg = spack.db.get(self.name)
         for name, pkg_dep in self.package.dependencies.items():
             # If it's a virtual dependency, try to find a provider
             if pkg_dep.virtual:
@@ -653,7 +653,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
             else:
                 # if it's a real dependency, check whether it provides something
                 # already required in the spec.
-                index = packages.ProviderIndex([pkg_dep], restrict=True)
+                index = ProviderIndex([pkg_dep], restrict=True)
                 for vspec in (v for v in spec_deps.values() if v.virtual):
                     if index.providers_for(vspec):
                         vspec._replace_with(pkg_dep)
@@ -718,7 +718,7 @@ def normalize(self):
         # Remove virtual deps that are already provided by something in the spec
         spec_packages = [d.package for d in spec_deps.values() if not d.virtual]
 
-        index = packages.ProviderIndex(spec_deps.values(), restrict=True)
+        index = ProviderIndex(spec_deps.values(), restrict=True)
 
         visited = set()
         self._normalize_helper(visited, spec_deps, index)
@@ -754,7 +754,7 @@ def validate_names(self):
         for spec in self.preorder_traversal():
             # Don't get a package for a virtual name.
             if not spec.virtual:
-                packages.get(spec.name)
+                spack.db.get(spec.name)
 
             # validate compiler name in addition to the package name.
             if spec.compiler:
@@ -888,10 +888,8 @@ def satisfies_dependencies(self, other):
                 return False
 
         # For virtual dependencies, we need to dig a little deeper.
-        self_index = packages.ProviderIndex(
-            self.preorder_traversal(), restrict=True)
-        other_index = packages.ProviderIndex(
-            other.preorder_traversal(), restrict=True)
+        self_index = ProviderIndex(self.preorder_traversal(), restrict=True)
+        other_index = ProviderIndex(other.preorder_traversal(), restrict=True)
 
         # This handles cases where there are already providers for both vpkgs
         if not self_index.satisfies(other_index):
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index 2aadf40382764c53fe06311dd6379a4844ec346c..271b91547901b30b0af4f3248f67e39dbef0947c 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -30,6 +30,8 @@
 
 import spack
 
+import spack.test.install
+
 
 """Names of tests to be included in Spack's test suite"""
 test_names = ['versions',
@@ -40,7 +42,8 @@
               'spec_semantics',
               'spec_dag',
               'concretize',
-              'multimethod']
+              'multimethod',
+              'install']
 
 
 def list_tests():
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 538c13f2cdedb4ee79855bf675f90049f917dd31..62e2732749cf7fe3f43782b63b12432512816ce2 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -24,7 +24,7 @@
 ##############################################################################
 import unittest
 
-import spack.packages as packages
+import spack
 from spack.spec import Spec
 from spack.test.mock_packages_test import *
 
@@ -113,22 +113,22 @@ def test_concretize_with_provides_when(self):
            we ask for some advanced version.
         """
         self.assertTrue(not any(spec.satisfies('mpich2@:1.0')
-                                for spec in packages.providers_for('mpi@2.1')))
+                                for spec in spack.db.providers_for('mpi@2.1')))
 
         self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
-                                for spec in packages.providers_for('mpi@2.2')))
+                                for spec in spack.db.providers_for('mpi@2.2')))
 
         self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
-                                for spec in packages.providers_for('mpi@2.2')))
+                                for spec in spack.db.providers_for('mpi@2.2')))
 
         self.assertTrue(not any(spec.satisfies('mpich@:1')
-                                for spec in packages.providers_for('mpi@2')))
+                                for spec in spack.db.providers_for('mpi@2')))
 
         self.assertTrue(not any(spec.satisfies('mpich@:1')
-                                for spec in packages.providers_for('mpi@3')))
+                                for spec in spack.db.providers_for('mpi@3')))
 
         self.assertTrue(not any(spec.satisfies('mpich2')
-                                for spec in packages.providers_for('mpi@3')))
+                                for spec in spack.db.providers_for('mpi@3')))
 
 
     def test_virtual_is_fully_expanded_for_callpath(self):
@@ -162,8 +162,4 @@ def test_virtual_is_fully_expanded_for_mpileaks(self):
     def test_my_dep_depends_on_provider_of_my_virtual_dep(self):
         spec = Spec('indirect_mpich')
         spec.normalize()
-
-        print
-        print spec.tree(color=True)
-
         spec.concretize()
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
new file mode 100644
index 0000000000000000000000000000000000000000..e567f6f9b5c9ba91627ae54d926a90b989b6c524
--- /dev/null
+++ b/lib/spack/spack/test/install.py
@@ -0,0 +1,98 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import unittest
+import shutil
+from contextlib import closing
+
+from llnl.util.filesystem import *
+
+import spack
+from spack.stage import Stage
+from spack.util.executable import which
+from spack.test.mock_packages_test import *
+
+dir_name = 'trivial-1.0'
+archive_name = 'trivial-1.0.tar.gz'
+install_test_package = 'trivial_install_test_package'
+
+class InstallTest(MockPackagesTest):
+    """Tests install and uninstall on a trivial package."""
+
+    def setUp(self):
+        super(InstallTest, self).setUp()
+
+        self.stage = Stage('not_a_real_url')
+        archive_dir = join_path(self.stage.path, dir_name)
+        dummy_configure = join_path(archive_dir, 'configure')
+
+        mkdirp(archive_dir)
+        with closing(open(dummy_configure, 'w')) as configure:
+            configure.write(
+                "#!/bin/sh\n"
+                "prefix=$(echo $1 | sed 's/--prefix=//')\n"
+                "cat > Makefile <<EOF\n"
+                "all:\n"
+                "\techo Building...\n\n"
+                "install:\n"
+                "\tmkdir -p $prefix\n"
+                "\ttouch $prefix/dummy_file\n"
+                "EOF\n")
+        os.chmod(dummy_configure, 0755)
+
+        with working_dir(self.stage.path):
+            tar = which('tar')
+            tar('-czf', archive_name, dir_name)
+
+        # We use a fake pacakge, so skip the checksum.
+        spack.do_checksum = False
+
+    def tearDown(self):
+        super(InstallTest, self).tearDown()
+
+        if self.stage is not None:
+            self.stage.destroy()
+
+        # Turn checksumming back on
+        spack.do_checksum = True
+
+
+    def test_install_and_uninstall(self):
+        # Get a basic concrete spec for the trivial install package.
+        spec = Spec(install_test_package)
+        spec.concretize()
+
+        # Get the package
+        pkg = spack.db.get(spec)
+
+        # Fake some values
+        archive_path = join_path(self.stage.path, archive_name)
+        pkg.url = 'file://' + archive_path
+
+        try:
+            pkg.do_install()
+            pkg.do_uninstall()
+        except:
+            if pkg: pkg.remove_prefix()
+            raise
diff --git a/lib/spack/spack/test/mock_packages/__init__.py b/lib/spack/spack/test/mock_packages/__init__.py
deleted file mode 100644
index 1c388f31c5bc14a1107d793de718006d634d24ef..0000000000000000000000000000000000000000
--- a/lib/spack/spack/test/mock_packages/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-##############################################################################
-# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://scalability-llnl.github.io/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License (as published by
-# the Free Software Foundation) version 2.1 dated February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
diff --git a/lib/spack/spack/test/mock_packages/callpath.py b/lib/spack/spack/test/mock_packages/callpath/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/callpath.py
rename to lib/spack/spack/test/mock_packages/callpath/package.py
diff --git a/lib/spack/spack/test/mock_packages/direct_mpich.py b/lib/spack/spack/test/mock_packages/direct_mpich/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/direct_mpich.py
rename to lib/spack/spack/test/mock_packages/direct_mpich/package.py
diff --git a/lib/spack/spack/test/mock_packages/dyninst.py b/lib/spack/spack/test/mock_packages/dyninst/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/dyninst.py
rename to lib/spack/spack/test/mock_packages/dyninst/package.py
diff --git a/lib/spack/spack/test/mock_packages/fake.py b/lib/spack/spack/test/mock_packages/fake/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/fake.py
rename to lib/spack/spack/test/mock_packages/fake/package.py
diff --git a/lib/spack/spack/test/mock_packages/indirect_mpich.py b/lib/spack/spack/test/mock_packages/indirect_mpich/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/indirect_mpich.py
rename to lib/spack/spack/test/mock_packages/indirect_mpich/package.py
diff --git a/lib/spack/spack/test/mock_packages/libdwarf.py b/lib/spack/spack/test/mock_packages/libdwarf/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/libdwarf.py
rename to lib/spack/spack/test/mock_packages/libdwarf/package.py
diff --git a/lib/spack/spack/test/mock_packages/libelf.py b/lib/spack/spack/test/mock_packages/libelf/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/libelf.py
rename to lib/spack/spack/test/mock_packages/libelf/package.py
diff --git a/lib/spack/spack/test/mock_packages/mpich.py b/lib/spack/spack/test/mock_packages/mpich/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/mpich.py
rename to lib/spack/spack/test/mock_packages/mpich/package.py
diff --git a/lib/spack/spack/test/mock_packages/mpich2.py b/lib/spack/spack/test/mock_packages/mpich2/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/mpich2.py
rename to lib/spack/spack/test/mock_packages/mpich2/package.py
diff --git a/lib/spack/spack/test/mock_packages/mpileaks.py b/lib/spack/spack/test/mock_packages/mpileaks/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/mpileaks.py
rename to lib/spack/spack/test/mock_packages/mpileaks/package.py
diff --git a/lib/spack/spack/test/mock_packages/multimethod.py b/lib/spack/spack/test/mock_packages/multimethod/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/multimethod.py
rename to lib/spack/spack/test/mock_packages/multimethod/package.py
diff --git a/lib/spack/spack/test/mock_packages/directory-pkg/__init__.py b/lib/spack/spack/test/mock_packages/trivial_install_test_package/package.py
similarity index 72%
rename from lib/spack/spack/test/mock_packages/directory-pkg/__init__.py
rename to lib/spack/spack/test/mock_packages/trivial_install_test_package/package.py
index aa53bb1f36e48f95ccee24ba3a8678cc52f205f4..b665825b32d6644ef64c047ba58f69fa0f6620b3 100644
--- a/lib/spack/spack/test/mock_packages/directory-pkg/__init__.py
+++ b/lib/spack/spack/test/mock_packages/trivial_install_test_package/package.py
@@ -24,16 +24,15 @@
 ##############################################################################
 from spack import *
 
-class DirectoryPkg(Package):
-    """This is a fake package that tests spack's ability to load packages in
-       directories with __init__.py files.
-    """
-    homepage = "http://www.example.com"
-    url      = "http://www.example.com/directory-pkg-1.0.tar.gz"
+class TrivialInstallTestPackage(Package):
+    """This package is a stub with a trivial install method.  It allows us
+       to test the install and uninstall logic of spack."""
+    homepage = "http://www.example.com/trivial_install"
+    url      = "http://www.unit-test-should-replace-this-url/trivial_install-1.0.tar.gz"
 
-    versions = { '1.0' : '0123456789abcdef0123456789abcdef' }
+    versions = { '1.0' : 'foobarbaz' }
 
-    this_is_a_directory_pkg = True
-
-    def install(self):
-        pass
+    def install(self, spec, prefix):
+        configure('--prefix=%s' % prefix)
+        make()
+        make('install')
diff --git a/lib/spack/spack/test/mock_packages/zmpi.py b/lib/spack/spack/test/mock_packages/zmpi/package.py
similarity index 100%
rename from lib/spack/spack/test/mock_packages/zmpi.py
rename to lib/spack/spack/test/mock_packages/zmpi/package.py
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
index 69bd120c58dc5ce656aa3cfd95befe319d99cbfe..f300995d7e6ea20bb54bc82247b155603f07e5c8 100644
--- a/lib/spack/spack/test/mock_packages_test.py
+++ b/lib/spack/spack/test/mock_packages_test.py
@@ -28,54 +28,31 @@
 from llnl.util.filesystem import join_path
 
 import spack
-import spack.packages as packages
+from spack.packages import PackageDB
 from spack.spec import Spec
 
 mock_packages_path = join_path(spack.module_path, 'test', 'mock_packages')
-original_deps = None
-
 
 def set_pkg_dep(pkg, spec):
     """Alters dependence information for a pacakge.
        Use this to mock up constraints.
     """
     spec = Spec(spec)
-    packages.get(pkg).dependencies[spec.name] = spec
-
-
-def restore_dependencies():
-    # each time through restore original dependencies & constraints
-    global original_deps
-    for pkg_name, deps in original_deps.iteritems():
-        packages.get(pkg_name).dependencies.clear()
-        for dep in deps:
-            set_pkg_dep(pkg_name, dep)
+    spack.db.get(pkg).dependencies[spec.name] = spec
 
 
 class MockPackagesTest(unittest.TestCase):
     @classmethod
-    def setUpClass(cls):
-        # Use a different packages directory for these tests.  We want to use
-        # mocked up packages that don't interfere with the real ones.
-        cls.real_packages_path = spack.packages_path
-        spack.packages_path = mock_packages_path
-
-        # First time through, record original relationships bt/w packages
-        global original_deps
-        original_deps = {}
-        for name in list_modules(mock_packages_path):
-            pkg = packages.get(name)
-            original_deps[name] = [
-                spec for spec in pkg.dependencies.values()]
+    def setUp(self):
+        # Use the mock packages database for these tests.  This allows
+        # us to set up contrived packages that don't interfere with
+        # real ones.
+        self.real_db = spack.db
+        spack.db = PackageDB(mock_packages_path)
 
 
     @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         """Restore the real packages path after any test."""
-        restore_dependencies()
-        spack.packages_path = cls.real_packages_path
-
-
-    def setUp(self):
-        """Before each test, restore deps between packages to original state."""
-        restore_dependencies()
+        #restore_dependencies()
+        spack.db = self.real_db
diff --git a/lib/spack/spack/test/multimethod.py b/lib/spack/spack/test/multimethod.py
index 79b55bf73efc95d43808f6345e6d65d2d552f030..d7731134260285b2cdde0fc307b17d2a4c884f11 100644
--- a/lib/spack/spack/test/multimethod.py
+++ b/lib/spack/spack/test/multimethod.py
@@ -27,7 +27,7 @@
 """
 import unittest
 
-import spack.packages as packages
+import spack
 from spack.multimethod import *
 from spack.version import *
 from spack.spec import Spec
@@ -38,37 +38,37 @@
 class MultiMethodTest(MockPackagesTest):
 
     def test_no_version_match(self):
-        pkg = packages.get('multimethod@2.0')
+        pkg = spack.db.get('multimethod@2.0')
         self.assertRaises(NoSuchMethodError, pkg.no_version_2)
 
 
     def test_one_version_match(self):
-        pkg = packages.get('multimethod@1.0')
+        pkg = spack.db.get('multimethod@1.0')
         self.assertEqual(pkg.no_version_2(), 1)
 
-        pkg = packages.get('multimethod@3.0')
+        pkg = spack.db.get('multimethod@3.0')
         self.assertEqual(pkg.no_version_2(), 3)
 
-        pkg = packages.get('multimethod@4.0')
+        pkg = spack.db.get('multimethod@4.0')
         self.assertEqual(pkg.no_version_2(), 4)
 
 
     def test_version_overlap(self):
-        pkg = packages.get('multimethod@2.0')
+        pkg = spack.db.get('multimethod@2.0')
         self.assertEqual(pkg.version_overlap(), 1)
 
-        pkg = packages.get('multimethod@5.0')
+        pkg = spack.db.get('multimethod@5.0')
         self.assertEqual(pkg.version_overlap(), 2)
 
 
     def test_mpi_version(self):
-        pkg = packages.get('multimethod^mpich@3.0.4')
+        pkg = spack.db.get('multimethod^mpich@3.0.4')
         self.assertEqual(pkg.mpi_version(), 3)
 
-        pkg = packages.get('multimethod^mpich2@1.2')
+        pkg = spack.db.get('multimethod^mpich2@1.2')
         self.assertEqual(pkg.mpi_version(), 2)
 
-        pkg = packages.get('multimethod^mpich@1.0')
+        pkg = spack.db.get('multimethod^mpich@1.0')
         self.assertEqual(pkg.mpi_version(), 1)
 
 
@@ -76,54 +76,54 @@ def test_undefined_mpi_version(self):
         # This currently fails because provides() doesn't do
         # the right thing undefined version ranges.
         # TODO: fix this.
-        pkg = packages.get('multimethod^mpich@0.4')
+        pkg = spack.db.get('multimethod^mpich@0.4')
         self.assertEqual(pkg.mpi_version(), 0)
 
 
     def test_default_works(self):
-        pkg = packages.get('multimethod%gcc')
+        pkg = spack.db.get('multimethod%gcc')
         self.assertEqual(pkg.has_a_default(), 'gcc')
 
-        pkg = packages.get('multimethod%intel')
+        pkg = spack.db.get('multimethod%intel')
         self.assertEqual(pkg.has_a_default(), 'intel')
 
-        pkg = packages.get('multimethod%pgi')
+        pkg = spack.db.get('multimethod%pgi')
         self.assertEqual(pkg.has_a_default(), 'default')
 
 
     def test_architecture_match(self):
-        pkg = packages.get('multimethod=x86_64')
+        pkg = spack.db.get('multimethod=x86_64')
         self.assertEqual(pkg.different_by_architecture(), 'x86_64')
 
-        pkg = packages.get('multimethod=ppc64')
+        pkg = spack.db.get('multimethod=ppc64')
         self.assertEqual(pkg.different_by_architecture(), 'ppc64')
 
-        pkg = packages.get('multimethod=ppc32')
+        pkg = spack.db.get('multimethod=ppc32')
         self.assertEqual(pkg.different_by_architecture(), 'ppc32')
 
-        pkg = packages.get('multimethod=arm64')
+        pkg = spack.db.get('multimethod=arm64')
         self.assertEqual(pkg.different_by_architecture(), 'arm64')
 
-        pkg = packages.get('multimethod=macos')
+        pkg = spack.db.get('multimethod=macos')
         self.assertRaises(NoSuchMethodError, pkg.different_by_architecture)
 
 
     def test_dependency_match(self):
-        pkg = packages.get('multimethod^zmpi')
+        pkg = spack.db.get('multimethod^zmpi')
         self.assertEqual(pkg.different_by_dep(), 'zmpi')
 
-        pkg = packages.get('multimethod^mpich')
+        pkg = spack.db.get('multimethod^mpich')
         self.assertEqual(pkg.different_by_dep(), 'mpich')
 
         # If we try to switch on some entirely different dep, it's ambiguous,
         # but should take the first option
-        pkg = packages.get('multimethod^foobar')
+        pkg = spack.db.get('multimethod^foobar')
         self.assertEqual(pkg.different_by_dep(), 'mpich')
 
 
     def test_virtual_dep_match(self):
-        pkg = packages.get('multimethod^mpich2')
+        pkg = spack.db.get('multimethod^mpich2')
         self.assertEqual(pkg.different_by_virtual_dep(), 2)
 
-        pkg = packages.get('multimethod^mpich@1.0')
+        pkg = spack.db.get('multimethod^mpich@1.0')
         self.assertEqual(pkg.different_by_virtual_dep(), 1)
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index 1a1f9c9bf4c7403ec2071fad3b5d0f51462b2643..e23df237231b3b541b20f151b6d6f404a35a3c0d 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -25,45 +25,29 @@
 import unittest
 
 from spack.test.mock_packages_test import *
-import spack.packages as packages
+import spack
 
 class PackagesTest(MockPackagesTest):
 
-    def test_load_regular_package(self):
-        pkg = packages.get('mpich')
+    def test_load_package(self):
+        pkg = spack.db.get('mpich')
 
 
-    def test_regular_package_name(self):
-        pkg = packages.get('mpich')
+    def test_package_name(self):
+        pkg = spack.db.get('mpich')
         self.assertEqual(pkg.name, 'mpich')
 
 
-    def test_regular_package_filename(self):
-        filename = packages.filename_for_package_name('mpich')
-        self.assertEqual(filename, join_path(mock_packages_path, 'mpich.py'))
+    def test_package_filename(self):
+        filename = spack.db.filename_for_package_name('mpich')
+        self.assertEqual(filename, join_path(mock_packages_path, 'mpich', 'package.py'))
 
 
-    def test_regular_package_name(self):
-        pkg = packages.get('mpich')
+    def test_package_name(self):
+        pkg = spack.db.get('mpich')
         self.assertEqual(pkg.name, 'mpich')
 
 
-    def test_load_directory_package(self):
-        pkg = packages.get('directory-pkg')
-        self.assertTrue(hasattr(pkg, 'this_is_a_directory_pkg'))
-        self.assertTrue(pkg.this_is_a_directory_pkg)
-
-
-    def test_directory_package_name(self):
-        pkg = packages.get('directory-pkg')
-        self.assertEqual(pkg.name, 'directory-pkg')
-
-
-    def test_directory_package_filename(self):
-        filename = packages.filename_for_package_name('directory-pkg')
-        self.assertEqual(filename, join_path(mock_packages_path, 'directory-pkg/__init__.py'))
-
-
     def test_nonexisting_package_filename(self):
-        filename = packages.filename_for_package_name('some-nonexisting-package')
-        self.assertEqual(filename, join_path(mock_packages_path, 'some-nonexisting-package.py'))
+        filename = spack.db.filename_for_package_name('some-nonexisting-package')
+        self.assertEqual(filename, join_path(mock_packages_path, 'some-nonexisting-package', 'package.py'))
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index d03d146310916a53c53e5d60ce0222b52ea3c2f7..0c0b214ab718e3a921c768c9ed3d861055d8ce01 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -30,7 +30,6 @@
 """
 import spack
 import spack.package
-import spack.packages as packages
 
 from llnl.util.lang import list_modules
 
diff --git a/lib/spack/spack/virtual.py b/lib/spack/spack/virtual.py
new file mode 100644
index 0000000000000000000000000000000000000000..960212eba96a339c8dc329dcf48e65fee73f8003
--- /dev/null
+++ b/lib/spack/spack/virtual.py
@@ -0,0 +1,142 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""
+The ``virtual`` module contains utility classes for virtual dependencies.
+"""
+import spack.spec
+
+class ProviderIndex(object):
+    """This is a dict of dicts used for finding providers of particular
+       virtual dependencies. The dict of dicts looks like:
+
+       { vpkg name :
+           { full vpkg spec : package providing spec } }
+
+       Callers can use this to first find which packages provide a vpkg,
+       then find a matching full spec.  e.g., in this scenario:
+
+       { 'mpi' :
+           { mpi@:1.1 : mpich,
+             mpi@:2.3 : mpich2@1.9: } }
+
+       Calling providers_for(spec) will find specs that provide a
+       matching implementation of MPI.
+    """
+    def __init__(self, specs, **kwargs):
+        # TODO: come up with another name for this.  This "restricts" values to
+        # the verbatim impu specs (i.e., it doesn't pre-apply package's constraints, and
+        # keeps things as broad as possible, so it's really the wrong name)
+        self.restrict = kwargs.setdefault('restrict', False)
+
+        self.providers = {}
+
+        for spec in specs:
+            if not isinstance(spec, spack.spec.Spec):
+                spec = spack.spec.Spec(spec)
+
+            if spec.virtual:
+                continue
+
+            self.update(spec)
+
+
+    def update(self, spec):
+        if type(spec) != spack.spec.Spec:
+            spec = spack.spec.Spec(spec)
+
+        assert(not spec.virtual)
+
+        pkg = spec.package
+        for provided_spec, provider_spec in pkg.provided.iteritems():
+            if provider_spec.satisfies(spec, deps=False):
+                provided_name = provided_spec.name
+                if provided_name not in self.providers:
+                    self.providers[provided_name] = {}
+
+                if self.restrict:
+                    self.providers[provided_name][provided_spec] = spec
+
+                else:
+                    # Before putting the spec in the map, constrain it so that
+                    # it provides what was asked for.
+                    constrained = spec.copy()
+                    constrained.constrain(provider_spec)
+                    self.providers[provided_name][provided_spec] = constrained
+
+
+    def providers_for(self, *vpkg_specs):
+        """Gives specs of all packages that provide virtual packages
+           with the supplied specs."""
+        providers = set()
+        for vspec in vpkg_specs:
+            # Allow string names to be passed as input, as well as specs
+            if type(vspec) == str:
+                vspec = spack.spec.Spec(vspec)
+
+            # Add all the providers that satisfy the vpkg spec.
+            if vspec.name in self.providers:
+                for provider_spec, spec in self.providers[vspec.name].items():
+                    if provider_spec.satisfies(vspec, deps=False):
+                        providers.add(spec)
+
+        # Return providers in order
+        return sorted(providers)
+
+
+    # TODO: this is pretty darned nasty, and inefficient.
+    def _cross_provider_maps(self, lmap, rmap):
+        result = {}
+        for lspec in lmap:
+            for rspec in rmap:
+                try:
+                    constrained = lspec.copy().constrain(rspec)
+                    if lmap[lspec].name != rmap[rspec].name:
+                        continue
+                    result[constrained] = lmap[lspec].copy().constrain(
+                        rmap[rspec], deps=False)
+                except spack.spec.UnsatisfiableSpecError:
+                    continue
+        return result
+
+
+    def __contains__(self, name):
+        """Whether a particular vpkg name is in the index."""
+        return name in self.providers
+
+
+    def satisfies(self, other):
+        """Check that providers of virtual specs are compatible."""
+        common = set(self.providers) & set(other.providers)
+        if not common:
+            return True
+
+        result = {}
+        for name in common:
+            crossed = self._cross_provider_maps(self.providers[name],
+                                                other.providers[name])
+            if crossed:
+                result[name] = crossed
+
+        return bool(result)