diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index d1d5529cf0a1c8c0abf606b7c322606f90350c43..20386847882e13094f8e408b07ef5380218dfb8d 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -7,6 +7,9 @@
 import spack.tty as tty
 from spack.util.lang import attr_setdefault
 
+# cmd has a submodule called "list" so preserve the python list module
+python_list = list
+
 # Patterns to ignore in the commands directory when looking for commands.
 ignore_files = r'^\.|^__init__.py$|^#'
 
@@ -50,15 +53,25 @@ def get_command(name):
     return getattr(get_module(name), get_cmd_function_name(name))
 
 
-def parse_specs(args):
+def parse_specs(args, **kwargs):
     """Convenience function for parsing arguments from specs.  Handles common
        exceptions and dies if there are errors.
     """
-    if type(args) == list:
+    concretize = kwargs.get('concretize', False)
+    normalize = kwargs.get('normalize', False)
+
+    if isinstance(args, (python_list, tuple)):
         args = " ".join(args)
 
     try:
-        return spack.spec.parse(" ".join(args))
+        specs = spack.spec.parse(args)
+        for spec in specs:
+            if concretize:
+                spec.concretize() # implies normalize
+            elif normalize:
+                spec.normalize()
+
+        return specs
 
     except spack.parse.ParseError, e:
         tty.error(e.message, e.string, e.pos * " " + "^")
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index f50e2311042423f878fccd1490bbccf79f74e09a..30c2f8b32f9e57b24019b13a98367a64c74483b6 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -1,6 +1,7 @@
 import os
 import re
 import argparse
+import hashlib
 from pprint import pprint
 from subprocess import CalledProcessError
 
@@ -8,17 +9,20 @@
 import spack.packages as packages
 from spack.stage import Stage
 from spack.colify import colify
-from spack.util.crypto import md5
+from spack.util.crypto import checksum
 from spack.version import *
 
 group='foo'
 description ="Checksum available versions of a package, print out checksums for addition to a package file."
 
 def setup_parser(subparser):
-    subparser.add_argument('package', metavar='PACKAGE', help='Package to list versions for')
-    subparser.add_argument('versions', nargs=argparse.REMAINDER, help='Versions to generate checksums for')
-    subparser.add_argument('-n', '--number', dest='number', type=int,
-                           default=10, help='Number of versions to list')
+    subparser.add_argument(
+        'package', metavar='PACKAGE', help='Package to list versions for')
+    subparser.add_argument(
+        'versions', nargs=argparse.REMAINDER, help='Versions to generate checksums for')
+    subparser.add_argument(
+        '-n', '--number', dest='number', type=int,
+        default=10, help='Number of versions to list')
 
 
 def checksum(parser, args):
@@ -50,7 +54,7 @@ def checksum(parser, args):
         stage = Stage(url)
         try:
             stage.fetch()
-            hashes.append(md5(stage.archive_file))
+            hashes.append(checksum(hashlib.md5, stage.archive_file))
         finally:
             stage.destroy()
 
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
index ff5ae4ba2c645deba3312ba176a5eb0d661eb7e4..c184c35a8495682f345c8432a0837cd92feb361c 100644
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -22,8 +22,9 @@ def clean(parser, args):
     if not args.packages:
         tty.die("spack clean requires at least one package argument")
 
-    specs = spack.cmd.parse_specs(args.packages)
+    specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
+        tty.message("Cleaning for spec:", spec)
         package = packages.get(spec.name)
         if args.dist:
             package.do_clean_dist()
diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py
index 2c8098b6732e4be94e696f6ab2f0f8597246231c..9aaad1f2fb21f6c1cea063d401280f51cc7c91dd 100644
--- a/lib/spack/spack/cmd/fetch.py
+++ b/lib/spack/spack/cmd/fetch.py
@@ -1,18 +1,26 @@
 import argparse
+
 import spack.cmd
 import spack.packages as packages
 
 description = "Fetch archives for packages"
 
 def setup_parser(subparser):
-    subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to fetch")
+    subparser.add_argument(
+        '-n', '--no-checksum', action='store_true', dest='no_checksum',
+        help="Do not check packages against checksum")
+    subparser.add_argument(
+        'packages', nargs=argparse.REMAINDER, help="specs of packages to fetch")
 
 
 def fetch(parser, args):
     if not args.packages:
         tty.die("fetch requires at least one package argument")
 
-    specs = spack.cmd.parse_specs(args.packages)
+    if args.no_checksum:
+        spack.do_checksum = False
+
+    specs = spack.cmd.parse_specs(args.packages, concretize=True)
     for spec in specs:
-        package = packages.get(spec.name)
+        package = packages.get(spec)
         package.do_fetch()
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
new file mode 100644
index 0000000000000000000000000000000000000000..b565d9aa77e221d7b8f5271cd1a0d672b9998bac
--- /dev/null
+++ b/lib/spack/spack/cmd/find.py
@@ -0,0 +1,87 @@
+import collections
+import argparse
+
+import spack
+import spack.packages as packages
+import spack.colify
+from spack.colify import colify
+
+description ="Find installed spack packages"
+
+def setup_parser(subparser):
+    subparser.add_argument(
+        '-p', '--paths', action='store_true', dest='paths',
+        help='Show paths to package install directories')
+    subparser.add_argument(
+        '-l', '--long', action='store_true', dest='full_specs',
+        help='Show full-length specs of installed packages')
+    subparser.add_argument(
+        'query_specs', nargs=argparse.REMAINDER,
+        help='optional specs to filter results')
+
+
+# TODO: move this and colify to tty.
+def hline(label, char):
+    max_width = 64
+    cols, rows = spack.colify.get_terminal_size()
+    if not cols:
+        cols = max_width
+    else:
+        cols -= 2
+    cols = min(max_width, cols)
+
+    label = str(label)
+    out =  char * 2 + " " + label + " "
+    out += (cols - len(out)) * char
+    return out
+
+
+def find(parser, args):
+    def hasher():
+        return collections.defaultdict(hasher)
+
+    query_specs = []
+    if args.query_specs:
+        query_specs = spack.cmd.parse_specs(args.query_specs, normalize=True)
+
+    # Make a dict with specs keyed by architecture and compiler.
+    index = hasher()
+    for spec in packages.installed_package_specs():
+        if query_specs and not any(spec.satisfies(q) for q in query_specs):
+            continue
+
+        if spec.compiler not in index[spec.architecture]:
+            index[spec.architecture][spec.compiler] = []
+        index[spec.architecture][spec.compiler].append(spec)
+
+    # Traverse the index and print out each package
+    for architecture in index:
+        print hline(architecture, "=")
+        for compiler in index[architecture]:
+            print hline(compiler, "-")
+
+            specs = index[architecture][compiler]
+            specs.sort()
+
+            abbreviated = []
+            for s in specs:
+                abbrv = "%s@%s%s" % (s.name, s.version, s.variants)
+                if s.dependencies:
+                    abbrv += '-' + s.dependencies.sha1()[:6]
+                abbreviated.append(abbrv)
+
+            if args.paths:
+                # Print one spec per line along with prefix path
+                width = max(len(s) for s in abbreviated)
+                width += 2
+                format = "    %-{}s%s".format(width)
+
+                for abbrv, spec in zip(abbreviated, specs):
+                    print format % (abbrv, spec.package.prefix)
+
+            elif args.full_specs:
+                for spec in specs:
+                    print spec.tree(indent=4),
+            else:
+                for abbrv in abbreviated:
+                    print "    %s" % abbrv
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 0940220ea292e15ff0a7a7c0fc665bf6f81daa8c..973fe862680577f4d3d253860aa2b5653e9e283d 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -5,7 +5,7 @@
 import spack.packages as packages
 from spack.colify import colify
 
-description = "Build and install packages"
+description = "Get detailed information on a particular package"
 
 def setup_parser(subparser):
     subparser.add_argument('name', metavar="PACKAGE", help="name of packages to get info on")
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index b3ad46398850cf92b9ab555c7aa8347babbbd901..e766757d51b5bad611796d27fddbe2917582cadb 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -8,21 +8,30 @@
 description = "Build and install packages"
 
 def setup_parser(subparser):
-    subparser.add_argument('-i', '--ignore-dependencies',
-                           action='store_true', dest='ignore_dependencies',
-                           help="Do not try to install dependencies of requested packages.")
-    subparser.add_argument('-d', '--dirty', action='store_true', dest='dirty',
-                           help="Don't clean up staging area when install completes.")
-    subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to install")
+    subparser.add_argument(
+        '-i', '--ignore-dependencies', action='store_true', dest='ignore_dependencies',
+        help="Do not try to install dependencies of requested packages.")
+    subparser.add_argument(
+        '-d', '--dirty', action='store_true', dest='dirty',
+        help="Don't clean up staging area when install completes.")
+    subparser.add_argument(
+        '-n', '--no-checksum', action='store_true', dest='no_checksum',
+        help="Do not check packages against checksum")
+    subparser.add_argument(
+        'packages', nargs=argparse.REMAINDER, help="specs of packages to install")
 
 
 def install(parser, args):
     if not args.packages:
         tty.die("install requires at least one package argument")
 
+    if args.no_checksum:
+        spack.do_checksum = False
+
     spack.ignore_dependencies = args.ignore_dependencies
-    specs = spack.cmd.parse_specs(args.packages)
+    specs = spack.cmd.parse_specs(args.packages, concretize=True)
+
     for spec in specs:
-        package = packages.get(spec.name)
+        package = packages.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 89ea5d82d8869e70dba1b06e078a993c58de9bfe..bc60646c5246e97e153a312e6cd8c64debcee0fc 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -1,23 +1,13 @@
-import os
-import re
-from subprocess import CalledProcessError
-
-import spack
 import spack.packages as packages
-from spack.version import ver
 from spack.colify import colify
-import spack.url as url
-import spack.tty as tty
 
-description ="List spack packages"
+description ="List available spack packages"
 
 def setup_parser(subparser):
-    subparser.add_argument('-i', '--installed', action='store_true', dest='installed',
-                           help='List installed packages for each platform along with versions.')
+    pass
 
 
 def list(parser, args):
-    if args.installed:
-        colify(str(pkg) for pkg in packages.installed_packages())
-    else:
-        colify(packages.all_package_names())
+    # Print all the package names in columns
+    colify(packages.all_package_names())
+
diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py
index ad12955b41d94e324643c6f740fe9df7513e9044..9af55c83468ec072bc156dc1ad063e94143e3d29 100644
--- a/lib/spack/spack/cmd/stage.py
+++ b/lib/spack/spack/cmd/stage.py
@@ -1,10 +1,24 @@
+import argparse
 import spack.packages as packages
 
 description="Expand downloaded archive in preparation for install"
 
 def setup_parser(subparser):
-    subparser.add_argument('name', help="name of package to stage")
+    subparser.add_argument(
+        '-n', '--no-checksum', action='store_true', dest='no_checksum',
+        help="Do not check packages against checksum")
+    subparser.add_argument(
+        'packages', nargs=argparse.REMAINDER, help="specs of packages to stage")
+
 
 def stage(parser, args):
-    package = packages.get(args.name)
-    package.do_stage()
+    if not args.packages:
+        tty.die("stage requires at least one package argument")
+
+    if args.no_checksum:
+        spack.do_checksum = False
+
+    specs = spack.cmd.parse_specs(args.packages, concretize=True)
+    for spec in specs:
+        package = packages.get(spec)
+        package.do_stage()
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index e76d91f123b3840e76affc92a3f8716de950a7a9..8965bd5a1456a5136a9c957728894be3b3d950a7 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -1,13 +1,18 @@
+import argparse
+
 import spack.cmd
+import spack.tty as tty
 import spack.packages as packages
-import argparse
 
 description="Remove an installed package"
 
 def setup_parser(subparser):
-    subparser.add_argument('-f', '--force', action='store_true', dest='force',
-                           help="Ignore installed packages that depend on this one and remove it anyway.")
-    subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall")
+    subparser.add_argument(
+        '-f', '--force', action='store_true', dest='force',
+        help="Remove regardless of whether other packages depend on this one.")
+    subparser.add_argument(
+        'packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall")
+
 
 def uninstall(parser, args):
     if not args.packages:
@@ -15,8 +20,20 @@ def uninstall(parser, args):
 
     specs = spack.cmd.parse_specs(args.packages)
 
-    # get packages to uninstall as a list.
-    pkgs = [packages.get(spec.name) for spec in specs]
+    # For each spec provided, make sure it refers to only one package.
+    # Fail and ask user to be unambiguous if it doesn't
+    pkgs = []
+    for spec in specs:
+        matching_specs = packages.get_installed(spec)
+        if len(matching_specs) > 1:
+            tty.die("%s matches multiple packages.  Which one did you mean?"
+                    % spec, *matching_specs)
+
+        elif len(matching_specs) == 0:
+            tty.die("%s does not match any installed packages." % spec)
+
+        installed_spec = matching_specs[0]
+        pkgs.append(packages.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/concretize.py b/lib/spack/spack/concretize.py
index 029b6d0d0380432006d23e6bf3d94b54a0a04193..82e9c2b24746cdddff550e25b26b99ae025d2f34 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -39,7 +39,7 @@ def concretize_version(self, spec):
         if valid_versions:
             spec.versions = ver([valid_versions[-1]])
         else:
-            spec.versions = ver([pkg.version])
+            spec.versions = ver([pkg.default_version])
 
 
     def concretize_architecture(self, spec):
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 2fdc55dc3f066deb1a4b8518b6298fa208818359..86dd22b58f2f1129420cb408391166170e43c001 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -1,9 +1,11 @@
-import exceptions
 import re
 import os
+import os.path
+import exceptions
+import hashlib
 
-import spack.spec as spec
-from spack.util import *
+from spack.spec import Spec
+from spack.util.filesystem import *
 from spack.error import SpackError
 
 
@@ -30,11 +32,14 @@ def relative_path_for_spec(self, spec):
         raise NotImplementedError()
 
 
+    def make_path_for_spec(self, spec):
+        """Creates the installation directory for a spec."""
+        raise NotImplementedError()
+
+
     def path_for_spec(self, spec):
         """Return an absolute path from the root to a directory for the spec."""
-        if not spec.concrete:
-            raise ValueError("path_for_spec requires a concrete spec.")
-
+        assert(spec.concrete)
         path = self.relative_path_for_spec(spec)
         assert(not path.startswith(self.root))
         return os.path.join(self.root, path)
@@ -70,22 +75,90 @@ def traverse_dirs_at_depth(root, depth, path_tuple=(), curdepth=0):
                     yield tup
 
 
-class DefaultDirectoryLayout(DirectoryLayout):
-    def __init__(self, root):
-        super(DefaultDirectoryLayout, self).__init__(root)
+class SpecHashDirectoryLayout(DirectoryLayout):
+    """Lays out installation directories like this::
+           <install_root>/
+               <architecture>/
+                   <compiler>/
+                       name@version+variant-<dependency_hash>
+
+       Where dependency_hash is a SHA-1 hash prefix for the full package spec.
+       This accounts for dependencies.
+
+       If there is ever a hash collision, you won't be able to install a new
+       package unless you use a larger prefix.  However, the full spec is stored
+       in a file called .spec in each directory, so you can migrate an entire
+       install directory to a new hash size pretty easily.
+
+       TODO: make a tool to migrate install directories to different hash sizes.
+    """
+    def __init__(self, root, **kwargs):
+        """Prefix size is number of characters in the SHA-1 prefix to use
+           to make each hash unique.
+        """
+        prefix_size = kwargs.get('prefix_size', 8)
+        spec_file   = kwargs.get('spec_file', '.spec')
+
+        super(SpecHashDirectoryLayout, self).__init__(root)
+        self.prefix_size = prefix_size
+        self.spec_file = spec_file
 
 
     def relative_path_for_spec(self, spec):
-        if not spec.concrete:
-            raise ValueError("relative_path_for_spec requires a concrete spec.")
+        assert(spec.concrete)
 
-        return new_path(
+        path = new_path(
             spec.architecture,
             spec.compiler,
-            "%s@%s%s%s" % (spec.name,
-                           spec.version,
-                           spec.variants,
-                           spec.dependencies))
+            "%s@%s%s" % (spec.name, spec.version, spec.variants))
+
+        if spec.dependencies:
+            path += "-"
+            sha1 = spec.dependencies.sha1()
+            path += sha1[:self.prefix_size]
+
+        return path
+
+
+    def write_spec(self, spec, path):
+        """Write a spec out to a file."""
+        with closing(open(path, 'w')) as spec_file:
+            spec_file.write(spec.tree(ids=False, cover='nodes'))
+
+
+    def read_spec(self, path):
+        """Read the contents of a file and parse them as a spec"""
+        with closing(open(path)) as spec_file:
+            string = spec_file.read().replace('\n', '')
+            return Spec(string)
+
+
+    def make_path_for_spec(self, spec):
+        assert(spec.concrete)
+
+        path = self.path_for_spec(spec)
+        spec_file_path = new_path(path, self.spec_file)
+
+        if os.path.isdir(path):
+            if not os.path.isfile(spec_file_path):
+                raise InconsistentInstallDirectoryError(
+                    'No spec file found at path %s' % spec_file_path)
+
+            installed_spec = self.read_spec(spec_file_path)
+            if installed_spec == self.spec:
+                raise InstallDirectoryAlreadyExistsError(path)
+
+            spec_hash = self.hash_spec(spec)
+            installed_hash = self.hash_spec(installed_spec)
+            if installed_spec == spec_hash:
+                raise SpecHashCollisionError(installed_hash, spec_hash)
+            else:
+                raise InconsistentInstallDirectoryError(
+                    'Spec file in %s does not match SHA-1 hash!'
+                    % (installed_spec, spec_file_path))
+
+        mkdirp(path)
+        self.write_spec(spec, spec_file_path)
 
 
     def all_specs(self):
@@ -94,5 +167,37 @@ def all_specs(self):
 
         for path in traverse_dirs_at_depth(self.root, 3):
             arch, compiler, last_dir = path
-            spec_str = "%s%%%s=%s" % (last_dir, compiler, arch)
-            yield spec.parse(spec_str)
+            spec_file_path = new_path(
+                self.root, arch, compiler, last_dir, self.spec_file)
+            if os.path.exists(spec_file_path):
+                spec = self.read_spec(spec_file_path)
+                yield spec
+
+
+class DirectoryLayoutError(SpackError):
+    """Superclass for directory layout errors."""
+    def __init__(self, message):
+        super(DirectoryLayoutError, self).__init__(message)
+
+
+class SpecHashCollisionError(DirectoryLayoutError):
+    """Raised when there is a hash collision in an SpecHashDirectoryLayout."""
+    def __init__(self, installed_spec, new_spec, prefix_size):
+        super(SpecHashDirectoryLayout, self).__init__(
+            'Specs %s and %s have the same %d character SHA-1 prefix!'
+            % prefix_size, installed_spec, new_spec)
+
+
+class InconsistentInstallDirectoryError(DirectoryLayoutError):
+    """Raised when a package seems to be installed to the wrong place."""
+    def __init__(self, message):
+        super(InconsistentInstallDirectoryError, self).__init__(message)
+
+
+class InstallDirectoryAlreadyExistsError(DirectoryLayoutError):
+    """Raised when make_path_for_sec is called unnecessarily."""
+    def __init__(self, path):
+        super(InstallDirectoryAlreadyExistsError, self).__init__(
+            "Install path %s already exists!")
+
+
diff --git a/lib/spack/spack/globals.py b/lib/spack/spack/globals.py
index 166c463cdf187366d33531b5dc4b4dac426c2f7b..1439af7913cee4318c326bc161721e1cc2879e63 100644
--- a/lib/spack/spack/globals.py
+++ b/lib/spack/spack/globals.py
@@ -3,7 +3,7 @@
 from spack.version import Version
 from spack.util.filesystem import *
 from spack.util.executable import *
-from spack.directory_layout import DefaultDirectoryLayout
+from spack.directory_layout import SpecHashDirectoryLayout
 from spack.concretize import DefaultConcretizer
 
 # This lives in $prefix/lib/spac/spack/__file__
@@ -29,7 +29,7 @@
 # This controls how spack lays out install prefixes and
 # stage directories.
 #
-install_layout = DefaultDirectoryLayout(install_path)
+install_layout = SpecHashDirectoryLayout(install_path, prefix_size=6)
 
 #
 # This controls how things are concretized in spack.
@@ -39,7 +39,7 @@
 concretizer = DefaultConcretizer()
 
 # Version information
-spack_version = Version("0.5")
+spack_version = Version("1.0")
 
 # User's editor from the environment
 editor = Executable(os.environ.get("EDITOR", ""))
@@ -60,6 +60,11 @@
             '/var/tmp/%u/spcak-stage',
             '/tmp/%u/spack-stage']
 
+# Whether spack should allow installation of unsafe versions of
+# software.  "Unsafe" versions are ones it doesn't have a checksum
+# for.
+do_checksum = True
+
 #
 # SYS_TYPE to use for the spack installation.
 # Value of this determines what platform spack thinks it is by
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 69392efdd52198e804a4adbba93a0bb7a0a4d7ce..d372f6b297f3bfe7e2a5205fa18465414a053486 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -22,14 +22,16 @@
 import packages
 import tty
 import validate
+import multiprocessing
 import url
 
 from spack.multi_function import platform
+import spack.util.crypto as crypto
 from spack.version import *
 from spack.stage import Stage
 from spack.util.lang import *
-from spack.util.crypto import md5
 from spack.util.web import get_pages
+from spack.util.environment import *
 
 
 class Package(object):
@@ -256,7 +258,7 @@ class SomePackage(Package):
 
        p.do_clean()              # runs make clean
        p.do_clean_work()         # removes the build directory and
-                                  # re-expands the archive.
+                                 # re-expands the archive.
        p.do_clean_dist()         # removes the stage directory entirely
 
     The convention used here is that a do_* function is intended to be called
@@ -297,9 +299,8 @@ class SomePackage(Package):
 
     def __init__(self, spec):
         # These attributes are required for all packages.
-        attr_required(self, 'homepage')
-        attr_required(self, 'url')
-        attr_required(self, 'md5')
+        attr_required(self.__class__, 'homepage')
+        attr_required(self.__class__, 'url')
 
         # this determines how the package should be built.
         self.spec = spec
@@ -307,39 +308,34 @@ def __init__(self, spec):
         # Name of package is the name of its module (the file that contains it)
         self.name = inspect.getmodulename(self.module.__file__)
 
-        # Don't allow the default homepage.
-        if re.search(r'example.com', self.homepage):
-            tty.die("Bad homepage in %s: %s" % (self.name, self.homepage))
-
         # Make sure URL is an allowed type
         validate.url(self.url)
 
-        # Set up version
-        # TODO: get rid of version attr and use spec
-        # TODO: roll this into available_versions
-        if not hasattr(self, 'version'):
-            try:
-                self.version = url.parse_version(self.url)
-            except UndetectableVersionError:
-                tty.die("Couldn't extract a default version from %s. You " +
-                        "must specify it explicitly in the package." % self.url)
-        elif not isinstance(self.version, Version):
-            self.version = Version(self.version)
+        # patch up the URL with a new version if the spec version is concrete
+        if self.spec.versions.concrete:
+            self.url = self.url_for_version(self.spec.version)
 
         # This is set by scraping a web page.
         self._available_versions = None
 
-        # This list overrides available_versions if set by the user.
-        attr_setdefault(self, 'versions', None)
-        if self.versions and not isinstance(self.versions, VersionList):
-            self.versions = VersionList(self.versions)
+        # versions should be a dict from version to checksum, for safe versions
+        # of this package.  If it's not present, make it an empty dict.
+        if not hasattr(self, 'versions'):
+            self.versions = {}
 
-        # Empty at first; only compute dependent packages if necessary
-        self._dependents = None
+        if not isinstance(self.versions, dict):
+            raise ValueError("versions attribute of package %s must be a dict!"
+                             % self.name)
+
+        # Version-ize the keys in versions dict
+        try:
+            self.versions = { Version(v):h for v,h in self.versions.items() }
+        except ValueError:
+            raise ValueError("Keys of versions dict in package %s must be versions!"
+                             % self.name)
 
         # stage used to build this package.
-        # TODO: hash the concrete spec and use that as the stage name.
-        self.stage = Stage(self.url, "%s-%s" % (self.name, self.version))
+        self._stage = None
 
         # Set a default list URL (place to find available versions)
         if not hasattr(self, 'list_url'):
@@ -349,6 +345,34 @@ def __init__(self, spec):
             self.list_depth = 1
 
 
+    @property
+    def default_version(self):
+        """Get the version in the default URL for this package,
+           or fails."""
+        try:
+            return url.parse_version(self.__class__.url)
+        except UndetectableVersionError:
+            tty.die("Couldn't extract a default version from %s. You " +
+                    "must specify it explicitly in the package." % self.url)
+
+
+    @property
+    def version(self):
+        if not self.spec.concrete:
+            raise ValueError("Can only get version of concrete package.")
+        return self.spec.versions[0]
+
+
+    @property
+    def stage(self):
+        if not self.spec.concrete:
+            raise ValueError("Can only get a stage for a concrete package.")
+
+        if self._stage is None:
+            self._stage = Stage(self.url, str(self.spec))
+        return self._stage
+
+
     def add_commands_to_module(self):
         """Populate the module scope of install() with some useful functions.
            This makes things easier for package writers.
@@ -406,13 +430,6 @@ def add_commands_to_module(self):
         m.man7    = new_path(m.man, 'man7')
         m.man8    = new_path(m.man, 'man8')
 
-    @property
-    def dependents(self):
-        """List of names of packages that depend on this one."""
-        if self._dependents is None:
-            packages.compute_dependents()
-        return tuple(self._dependents)
-
 
     def preorder_traversal(self, visited=None, **kwargs):
         """This does a preorder traversal of the package's dependence DAG."""
@@ -499,20 +516,15 @@ def installed(self):
 
     @property
     def installed_dependents(self):
-        installed = [d for d in self.dependents if packages.get(d).installed]
-        all_deps = []
-        for d in installed:
-            all_deps.append(d)
-            all_deps.extend(packages.get(d).installed_dependents)
-        return tuple(all_deps)
-
-
-    @property
-    def all_dependents(self):
-        all_deps = list(self.dependents)
-        for pkg in self.dependents:
-            all_deps.extend(packages.get(pkg).all_dependents)
-        return tuple(all_deps)
+        """Return a list of the specs of all installed packages that depend
+           on this one."""
+        dependents = []
+        for spec in packages.installed_package_specs():
+            if self.name in spec.dependencies:
+                dep_spec = spec.dependencies[self.name]
+                if self.spec == dep_spec:
+                    dependents.append(dep_spec)
+        return dependents
 
 
     @property
@@ -533,7 +545,7 @@ def url_version(self, version):
 
     def url_for_version(self, version):
         """Gives a URL that you can download a new version of this package from."""
-        return url.substitute_version(self.url, self.url_version(version))
+        return url.substitute_version(self.__class__.url, self.url_version(version))
 
 
     def remove_prefix(self):
@@ -547,28 +559,38 @@ def do_fetch(self):
         """Creates a stage directory and downloads the taball for this package.
            Working directory will be set to the stage directory.
         """
-        stage = self.stage
-        stage.setup()
-        stage.fetch()
+        self.stage.setup()
 
-        archive_md5 = md5(stage.archive_file)
-        if archive_md5 != self.md5:
-            tty.die("MD5 Checksum failed for %s.  Expected %s but got %s."
-                    % (self.name, self.md5, archive_md5))
+        if spack.do_checksum and not self.version in self.versions:
+            tty.die("Cannot fetch %s@%s safely; there is no checksum on file for this "
+                    "version." % (self.name, self.version),
+                    "Add a checksum to the package file, or use --no-checksum to "
+                    "skip this check.")
+
+        self.stage.fetch()
+
+        if self.version in self.versions:
+            digest = self.versions[self.version]
+            checker = crypto.Checker(digest)
+            if checker.check(self.stage.archive_file):
+                tty.msg("Checksum passed for %s" % self.name)
+            else:
+                tty.die("%s checksum failed for %s.  Expected %s but got %s."
+                        % (checker.hash_name, self.name, digest, checker.sum))
 
 
     def do_stage(self):
-        """Unpacks the fetched tarball, then changes into the expanded tarball directory."""
+        """Unpacks the fetched tarball, then changes into the expanded tarball
+           directory."""
         self.do_fetch()
-        stage = self.stage
 
-        archive_dir = stage.expanded_archive_path
+        archive_dir = self.stage.expanded_archive_path
         if not archive_dir:
-            tty.msg("Staging archive: %s" % stage.archive_file)
-            stage.expand_archive()
+            tty.msg("Staging archive: %s" % self.stage.archive_file)
+            self.stage.expand_archive()
         else:
             tty.msg("Already staged %s" % self.name)
-        stage.chdir_to_archive()
+        self.stage.chdir_to_archive()
 
 
     def do_install(self):
@@ -595,18 +617,14 @@ def do_install(self):
 
         tty.msg("Building %s." % self.name)
         try:
+            # create the install directory (allow the layout to handle this in
+            # case it needs to add extra files)
+            spack.install_layout.make_path_for_spec(self.spec)
+
             self.install(self.prefix)
             if not os.path.isdir(self.prefix):
                 tty.die("Install failed for %s.  No install dir created." % self.name)
 
-        except subprocess.CalledProcessError, e:
-            self.remove_prefix()
-            tty.die("Install failed for %s" % self.name, e.message)
-
-        except KeyboardInterrupt, e:
-            self.remove_prefix()
-            raise
-
         except Exception, e:
             if not self.dirty:
                 self.remove_prefix()
@@ -640,8 +658,9 @@ def setup_install_environment(self):
         path_set(SPACK_ENV_PATH, env_paths)
 
         # Pass along prefixes of dependencies here
-        path_set(SPACK_DEPENDENCIES,
-                 [dep.package.prefix for dep in self.dependencies.values()])
+        path_set(
+            SPACK_DEPENDENCIES,
+            [dep.package.prefix for dep in self.spec.dependencies.values()])
 
         # Install location
         os.environ[SPACK_PREFIX] = self.prefix
@@ -652,7 +671,7 @@ def setup_install_environment(self):
 
     def do_install_dependencies(self):
         # Pass along paths of dependencies here
-        for dep in self.dependencies.values():
+        for dep in self.spec.dependencies.values():
             dep.package.do_install()
 
 
@@ -717,7 +736,7 @@ def fetch_available_versions(self):
         if not self._available_versions:
             self._available_versions = VersionList()
             url_regex = os.path.basename(url.wildcard_version(self.url))
-            wildcard = self.version.wildcard()
+            wildcard = self.default_version.wildcard()
 
             try:
                 page_map = get_pages(self.list_url, depth=self.list_depth)
@@ -748,7 +767,7 @@ def fetch_available_versions(self):
     def available_versions(self):
         # If the package overrode available_versions, then use that.
         if self.versions is not None:
-            return self.versions
+            return VersionList(self.versions.keys())
         else:
             vlist = self.fetch_available_versions()
             if not vlist:
diff --git a/lib/spack/spack/packages/__init__.py b/lib/spack/spack/packages/__init__.py
index 0fd22fa4803c5e0aabf0ef7a9f61328ed6ab8df7..e3046d89570e820772dd26303b182f9d92315fea 100644
--- a/lib/spack/spack/packages/__init__.py
+++ b/lib/spack/spack/packages/__init__.py
@@ -21,6 +21,16 @@
 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:
@@ -87,23 +97,32 @@ def providers_for(self, *vpkg_specs):
         return sorted(providers)
 
 
-def get(pkg_name):
-    if not pkg_name in instances:
-        package_class = get_class_for_package_name(pkg_name)
-        instances[pkg_name] = package_class(pkg_name)
+@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[pkg_name]
+    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 providers_for.index is None:
+    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
-providers_for.index = None
 
 
 def valid_package_name(pkg_name):
@@ -122,7 +141,7 @@ def filename_for_package_name(pkg_name):
     return new_path(spack.packages_path, "%s.py" % pkg_name)
 
 
-def installed_packages():
+def installed_package_specs():
     return spack.install_layout.all_specs()
 
 
@@ -198,6 +217,9 @@ 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 = []
diff --git a/lib/spack/spack/packages/callpath.py b/lib/spack/spack/packages/callpath.py
index 958960e0abf1fb6ff0254ab212cec59b8974ef1e..e5102a5fad0e94b5777c44e7a066453853e29237 100644
--- a/lib/spack/spack/packages/callpath.py
+++ b/lib/spack/spack/packages/callpath.py
@@ -3,7 +3,6 @@
 class Callpath(Package):
     homepage = "https://github.com/tgamblin/callpath"
     url      = "http://github.com/tgamblin/callpath-0.2.tar.gz"
-    md5      = "foobarbaz"
 
     depends_on("dyninst")
     depends_on("mpich")
diff --git a/lib/spack/spack/packages/cmake.py b/lib/spack/spack/packages/cmake.py
index 9b75bd6273d57008fa6e4793c2ee730c65194841..d73cdd2c88fa2c679148eab67ad041fbb27e9c4f 100644
--- a/lib/spack/spack/packages/cmake.py
+++ b/lib/spack/spack/packages/cmake.py
@@ -3,7 +3,7 @@
 class Cmake(Package):
     homepage  = 'https://www.cmake.org'
     url       = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
-    md5       = '097278785da7182ec0aea8769d06860c'
+    versions  = { '2.8.10.2' : '097278785da7182ec0aea8769d06860c' }
 
     def install(self, prefix):
         configure('--prefix=%s'   % prefix,
diff --git a/lib/spack/spack/packages/dyninst.py b/lib/spack/spack/packages/dyninst.py
index 3e5b4a5bd4e7e50881b6a96db75791f5349cf32c..7648a389ee74120fc388f2cba9c5aa722e7a1e3e 100644
--- a/lib/spack/spack/packages/dyninst.py
+++ b/lib/spack/spack/packages/dyninst.py
@@ -3,9 +3,11 @@
 class Dyninst(Package):
     homepage = "https://paradyn.org"
     url      = "http://www.dyninst.org/sites/default/files/downloads/dyninst/8.1.2/DyninstAPI-8.1.2.tgz"
-    md5      = "bf03b33375afa66fe0efa46ce3f4b17a"
     list_url = "http://www.dyninst.org/downloads/dyninst-8.x"
 
+    versions = {'8.1.2' : 'bf03b33375afa66fe0efa46ce3f4b17a',
+                '8.1.1' : '1f8743e3a5662b25ce64a7edf647e77d' }
+
     depends_on("libelf")
     depends_on("libdwarf")
 
diff --git a/lib/spack/spack/packages/libdwarf.py b/lib/spack/spack/packages/libdwarf.py
index 05f792aae49929396bdf1614a7d357b55f465104..035465477b28a9f070cd8b1535276c8ae93c9068 100644
--- a/lib/spack/spack/packages/libdwarf.py
+++ b/lib/spack/spack/packages/libdwarf.py
@@ -9,7 +9,9 @@ class Libdwarf(Package):
     url      = "http://www.prevanders.net/libdwarf-20130729.tar.gz"
     list_url = homepage
 
-    md5      = "64b42692e947d5180e162e46c689dfbf"
+    versions = { '20130729' : '4cc5e48693f7b93b7aa0261e63c0e21d',
+                 '20130207' : '64b42692e947d5180e162e46c689dfbf',
+                 '20130126' : 'ded74a5e90edb5a12aac3c29d260c5db' }
 
     depends_on("libelf")
 
@@ -49,7 +51,7 @@ def install(self, prefix):
             install('dwarfdump.1',    man1)
 
 
-    @platform('macosx_10.8_x86_64')
-    def install(self, prefix):
-        raise UnsupportedPlatformError(
-            "libdwarf doesn't currently build on Mac OS X.")
+#    @platform('macosx_10.8_x86_64')
+#    def install(self, prefix):
+#        raise UnsupportedPlatformError(
+#            "libdwarf doesn't currently build on Mac OS X.")
diff --git a/lib/spack/spack/packages/libelf.py b/lib/spack/spack/packages/libelf.py
index 7e3046b174fcbb94fe2834ee6b8d2c7c0e578f80..67c1d83fc6d52a3d899f7988ba96f97053ca965c 100644
--- a/lib/spack/spack/packages/libelf.py
+++ b/lib/spack/spack/packages/libelf.py
@@ -3,7 +3,8 @@
 class Libelf(Package):
     homepage = "http://www.mr511.de/software/english.html"
     url      = "http://www.mr511.de/software/libelf-0.8.13.tar.gz"
-    md5      = "4136d7b4c04df68b686570afa26988ac"
+
+    versions = { '0.8.13' : '4136d7b4c04df68b686570afa26988ac' }
 
     def install(self, prefix):
         configure("--prefix=%s" % prefix,
diff --git a/lib/spack/spack/packages/libunwind.py b/lib/spack/spack/packages/libunwind.py
index 57b06248055687af3ba6bc8873a714adabeae0b7..f53985709e4600d0d68a200f512247e834225b0c 100644
--- a/lib/spack/spack/packages/libunwind.py
+++ b/lib/spack/spack/packages/libunwind.py
@@ -3,7 +3,8 @@
 class Libunwind(Package):
     homepage = "http://www.nongnu.org/libunwind/"
     url      = "http://download.savannah.gnu.org/releases/libunwind/libunwind-1.1.tar.gz"
-    md5      = "fb4ea2f6fbbe45bf032cd36e586883ce"
+
+    versions = { '1.1' : 'fb4ea2f6fbbe45bf032cd36e586883ce' }
 
     def install(self, prefix):
         configure("--prefix=%s" % prefix)
diff --git a/lib/spack/spack/packages/mpich.py b/lib/spack/spack/packages/mpich.py
index 865d6cb95e85b195cce4039aea772e395bae56b0..8cd201f965f013ac34c6cc58fac9e3f1a9de27d4 100644
--- a/lib/spack/spack/packages/mpich.py
+++ b/lib/spack/spack/packages/mpich.py
@@ -3,15 +3,12 @@
 class Mpich(Package):
     """MPICH is a high performance and widely portable implementation of
        the Message Passing Interface (MPI) standard."""
-
     homepage = "http://www.mpich.org"
     url      = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
-    md5      = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
-
     list_url   = "http://www.mpich.org/static/downloads/"
     list_depth = 2
 
-    versions = ['3.0.4', '3.0.3', '3.0.2', '3.0.1', '3.0']
+    versions = { '3.0.4' : '9c5d5d4fe1e17dd12153f40bc5b6dbc0' }
 
     provides('mpi@:3', when='@3:')
     provides('mpi@:1', when='@1:')
diff --git a/lib/spack/spack/packages/mpileaks.py b/lib/spack/spack/packages/mpileaks.py
index 224557cc5269760c1ea7cdf35b51dc62dc1dcd9a..ffeb38ea45273bfbe2b4133a2c0d849989f4c957 100644
--- a/lib/spack/spack/packages/mpileaks.py
+++ b/lib/spack/spack/packages/mpileaks.py
@@ -3,7 +3,6 @@
 class Mpileaks(Package):
     homepage = "http://www.llnl.gov"
     url      = "http://www.llnl.gov/mpileaks-1.0.tar.gz"
-    md5      = "foobarbaz"
 
     depends_on("mpich")
     depends_on("callpath")
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 748fa9b36a3593fac03297d34d6eceb50b6da19d..fafc3c852a8ce35e38053678126cf12ce676725a 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -70,6 +70,7 @@
 from StringIO import StringIO
 
 import tty
+import hashlib
 import spack.parse
 import spack.error
 import spack.compilers
@@ -238,6 +239,12 @@ def satisfies(self, other):
                    if name in other)
 
 
+    def sha1(self):
+        sha = hashlib.sha1()
+        sha.update(str(self))
+        return sha.hexdigest()
+
+
     def __str__(self):
         sorted_dep_names = sorted(self.keys())
         return ''.join(
@@ -341,10 +348,7 @@ def root(self):
 
     @property
     def package(self):
-        if self.virtual:
-            raise TypeError("Cannot get package for virtual spec '" +
-                            self.name + "'")
-        return packages.get(self.name)
+        return packages.get(self)
 
 
     @property
@@ -766,8 +770,8 @@ def copy(self, **kwargs):
 
     @property
     def version(self):
-        if not self.concrete:
-            raise SpecError("Spec is not concrete: " + str(self))
+        if not self.versions.concrete:
+            raise SpecError("Spec version is not concrete: " + str(self))
         return self.versions[0]
 
 
@@ -825,25 +829,37 @@ def str_no_deps(self, **kwargs):
     def tree(self, **kwargs):
         """Prints out this spec and its dependencies, tree-formatted
            with indentation."""
-        color = kwargs.get('color', False)
-        depth = kwargs.get('depth', False)
-        cover = kwargs.get('cover', 'paths')
+        color  = kwargs.get('color', False)
+        depth  = kwargs.get('depth', False)
+        showid = kwargs.get('ids',   False)
+        cover  = kwargs.get('cover', 'nodes')
+        indent = kwargs.get('indent', 0)
 
         out = ""
         cur_id = 0
         ids = {}
         for d, node in self.preorder_traversal(cover=cover, depth=True):
+            out += " " * indent
             if depth:
                 out += "%-4d" % d
             if not id(node) in ids:
                 cur_id += 1
                 ids[id(node)] = cur_id
-            out += "%-4d" % ids[id(node)]
+            if showid:
+                out += "%-4d" % ids[id(node)]
             out += ("    " * d)
+            if d > 0:
+                out += "^"
             out += node.str_no_deps(color=color) + "\n"
         return out
 
 
+    def sha1(self):
+        sha = hashlib.sha1()
+        sha.update(str(self))
+        return sha.hexdigest()
+
+
     def __repr__(self):
         return str(self)
 
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 9c5248e9caf6b8c10c745ee974a956aa55647109..7f01fca12b30f65c5ff40897c1e440141974f083 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -269,11 +269,6 @@ def destroy(self):
 
 
 
-def can_access(file=spack.stage_path):
-    """True if we have read/write access to the file."""
-    return os.access(file, os.R_OK|os.W_OK)
-
-
 def ensure_access(file=spack.stage_path):
     """Ensure we can access a directory and die with an error if we can't."""
     if not can_access(file):
@@ -305,10 +300,18 @@ def find_tmp_root():
     if spack.use_tmp_stage:
         for tmp in spack.tmp_dirs:
             try:
-                mkdirp(expand_user(tmp))
-                return tmp
+                # Replace %u with username
+                expanded = expand_user(tmp)
+
+                # try to create a directory for spack stuff
+                mkdirp(expanded)
+
+                # return it if successful.
+                return expanded
+
             except OSError:
                 continue
+
     return None
 
 
diff --git a/lib/spack/spack/test/mock_packages/__init__.py b/lib/spack/spack/test/mock_packages/__init__.py
index e6a8f467e6d41bef02ec77826f6c7e3999fb32b9..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/lib/spack/spack/test/mock_packages/__init__.py
+++ b/lib/spack/spack/test/mock_packages/__init__.py
@@ -1 +0,0 @@
-skip_test = True
diff --git a/lib/spack/spack/test/mock_packages/callpath.py b/lib/spack/spack/test/mock_packages/callpath.py
index ec59172d82237132c26931705740f30d861262a6..e9ad344eaa9b8e8f935c795a624325912a21b14d 100644
--- a/lib/spack/spack/test/mock_packages/callpath.py
+++ b/lib/spack/spack/test/mock_packages/callpath.py
@@ -2,12 +2,11 @@
 
 class Callpath(Package):
     homepage = "https://github.com/tgamblin/callpath"
-    url      = "http://github.com/tgamblin/callpath-0.2.tar.gz"
-    md5      = "foobarbaz"
+    url      = "http://github.com/tgamblin/callpath-1.0.tar.gz"
 
-    versions = { 0.8 : 'bf03b33375afa66fe0efa46ce3f4b17a',
-                 0.9 : 'bf03b33375afa66fe0efa46ce3f4b17a',
-                 1.0 : 'bf03b33375afa66fe0efa46ce3f4b17a' }
+    versions = { 0.8 : 'foobarbaz',
+                 0.9 : 'foobarbaz',
+                 1.0 : 'foobarbaz' }
 
     depends_on("dyninst")
     depends_on("mpi")
diff --git a/lib/spack/spack/test/mock_packages/dyninst.py b/lib/spack/spack/test/mock_packages/dyninst.py
index 2974e17f425cfac0138287d61a343ccbbe679eac..6940c7788dc567211c75639fc3a507b8d6aa5b5e 100644
--- a/lib/spack/spack/test/mock_packages/dyninst.py
+++ b/lib/spack/spack/test/mock_packages/dyninst.py
@@ -3,8 +3,6 @@
 class Dyninst(Package):
     homepage = "https://paradyn.org"
     url      = "http://www.dyninst.org/sites/default/files/downloads/dyninst/8.1.2/DyninstAPI-8.1.2.tgz"
-    md5      = "bf03b33375afa66fe0efa46ce3f4b17a"
-
     list_url = "http://www.dyninst.org/downloads/dyninst-8.x"
 
     versions = {
diff --git a/lib/spack/spack/test/mock_packages/fake.py b/lib/spack/spack/test/mock_packages/fake.py
index 4b04e76b01fedd2dc6dd7e40df1bc6858a63cb43..68077f5f82897e93cc57e89c5186781256dac33b 100644
--- a/lib/spack/spack/test/mock_packages/fake.py
+++ b/lib/spack/spack/test/mock_packages/fake.py
@@ -3,9 +3,7 @@
 class Fake(Package):
     homepage = "http://www.fake-spack-example.org"
     url      = "http://www.fake-spack-example.org/downloads/fake-1.0.tar.gz"
-    md5      = "foobarbaz"
-
-    versions = '1.0'
+    versions = { '1.0' : 'foobarbaz' }
 
     def install(self, prefix):
         configure("--prefix=%s" % prefix)
diff --git a/lib/spack/spack/test/mock_packages/libdwarf.py b/lib/spack/spack/test/mock_packages/libdwarf.py
index d3a5e8dc47242d1a277eca0c1156c60e775725eb..ccfff562869b52d21f2bad8dd97559d2951d0384 100644
--- a/lib/spack/spack/test/mock_packages/libdwarf.py
+++ b/lib/spack/spack/test/mock_packages/libdwarf.py
@@ -9,13 +9,13 @@ class Libdwarf(Package):
     url      = "http://www.prevanders.net/libdwarf-20130729.tar.gz"
     list_url = homepage
 
-    md5      = "64b42692e947d5180e162e46c689dfbf"
-
-    versions = [20070703, 20111030, 20130207]
+    versions = { 20130729 : "64b42692e947d5180e162e46c689dfbf",
+                 20130207 : 'foobarbaz',
+                 20111030 : 'foobarbaz',
+                 20070703 : 'foobarbaz' }
 
     depends_on("libelf")
 
-
     def clean(self):
         for dir in dwarf_dirs:
             with working_dir(dir):
@@ -49,9 +49,3 @@ def install(self, prefix):
             install('dwarfdump',     bin)
             install('dwarfdump.conf', lib)
             install('dwarfdump.1',    man1)
-
-
-    @platform('macosx_10.8_x86_64')
-    def install(self, prefix):
-        raise UnsupportedPlatformError(
-            "libdwarf doesn't currently build on Mac OS X.")
diff --git a/lib/spack/spack/test/mock_packages/libelf.py b/lib/spack/spack/test/mock_packages/libelf.py
index f003eff010fee32286eb060132d3ddef5e5e22fa..75948b26fba42cf117f191bdd3df449479562bd1 100644
--- a/lib/spack/spack/test/mock_packages/libelf.py
+++ b/lib/spack/spack/test/mock_packages/libelf.py
@@ -3,12 +3,10 @@
 class Libelf(Package):
     homepage = "http://www.mr511.de/software/english.html"
     url      = "http://www.mr511.de/software/libelf-0.8.13.tar.gz"
-    md5      = "4136d7b4c04df68b686570afa26988ac"
 
-    versions =   {
-        '0.8.13' : '4136d7b4c04df68b686570afa26988ac',
-        '0.8.12' : 'e21f8273d9f5f6d43a59878dc274fec7',
-        '0.8.10' : '9db4d36c283d9790d8fa7df1f4d7b4d9' }
+    versions =   {'0.8.13' : '4136d7b4c04df68b686570afa26988ac',
+                  '0.8.12' : 'e21f8273d9f5f6d43a59878dc274fec7',
+                  '0.8.10' : '9db4d36c283d9790d8fa7df1f4d7b4d9' }
 
     def install(self, prefix):
         configure("--prefix=%s" % prefix,
diff --git a/lib/spack/spack/test/mock_packages/mpich.py b/lib/spack/spack/test/mock_packages/mpich.py
index 1e964cd380f55e87cc01c5951fba2f00965a079c..c2a479c07a1ad14051ee31a7d38aa589b1af2ef1 100644
--- a/lib/spack/spack/test/mock_packages/mpich.py
+++ b/lib/spack/spack/test/mock_packages/mpich.py
@@ -1,14 +1,16 @@
 from spack import *
 
 class Mpich(Package):
-    homepage = "http://www.mpich.org"
-    url      = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
-    md5      = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
-
+    homepage   = "http://www.mpich.org"
+    url        = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
     list_url   = "http://www.mpich.org/static/downloads/"
     list_depth = 2
 
-    versions = '3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0'
+    versions = { '3.0.4' : '9c5d5d4fe1e17dd12153f40bc5b6dbc0',
+                 '3.0.3' : 'foobarbaz',
+                 '3.0.2' : 'foobarbaz',
+                 '3.0.1' : 'foobarbaz',
+                 '3.0'   : 'foobarbaz' }
 
     provides('mpi@:3', when='@3:')
     provides('mpi@:1', when='@1:')
diff --git a/lib/spack/spack/test/mock_packages/mpich2.py b/lib/spack/spack/test/mock_packages/mpich2.py
index 7150fa7249193f887e9cf9793bb3d496cd2e5b98..ecf99925ccac726b67bb046d3a66b9deb2011914 100644
--- a/lib/spack/spack/test/mock_packages/mpich2.py
+++ b/lib/spack/spack/test/mock_packages/mpich2.py
@@ -1,14 +1,17 @@
 from spack import *
 
 class Mpich2(Package):
-    homepage = "http://www.mpich.org"
-    url      = "http://www.mpich.org/static/downloads/1.5/mpich2-1.5.tar.gz"
-    md5      = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
-
+    homepage   = "http://www.mpich.org"
+    url        = "http://www.mpich.org/static/downloads/1.5/mpich2-1.5.tar.gz"
     list_url   = "http://www.mpich.org/static/downloads/"
     list_depth = 2
 
-    versions = '1.5, 1.4, 1.3, 1.2, 1.1, 1.0'
+    versions = { '1.5' : '9c5d5d4fe1e17dd12153f40bc5b6dbc0',
+                 '1.4' : 'foobarbaz',
+                 '1.3' : 'foobarbaz',
+                 '1.2' : 'foobarbaz',
+                 '1.1' : 'foobarbaz',
+                 '1.0' : 'foobarbaz' }
 
     provides('mpi@:2.0')
     provides('mpi@:2.1', when='@1.1:')
diff --git a/lib/spack/spack/test/mock_packages/mpileaks.py b/lib/spack/spack/test/mock_packages/mpileaks.py
index e99251a85cf0f652c57a2529cbfde92467377c62..6f9b143e9d1acb069f42ed6be2c469566378d2c4 100644
--- a/lib/spack/spack/test/mock_packages/mpileaks.py
+++ b/lib/spack/spack/test/mock_packages/mpileaks.py
@@ -3,12 +3,11 @@
 class Mpileaks(Package):
     homepage = "http://www.llnl.gov"
     url      = "http://www.llnl.gov/mpileaks-1.0.tar.gz"
-    md5      = "foobarbaz"
 
-    versions = { 1.0 : None,
-                 2.1 : None,
-                 2.2 : None,
-                 2.3 : None }
+    versions = { 1.0 : 'foobarbaz',
+                 2.1 : 'foobarbaz',
+                 2.2 : 'foobarbaz',
+                 2.3 : 'foobarbaz' }
 
     depends_on("mpi")
     depends_on("callpath")
diff --git a/lib/spack/spack/test/mock_packages/zmpi.py b/lib/spack/spack/test/mock_packages/zmpi.py
index f36f6a5bc41725c43deca6e626762f588b207a98..98231b6837b2ddf160ad4b3ce306a4f702517855 100644
--- a/lib/spack/spack/test/mock_packages/zmpi.py
+++ b/lib/spack/spack/test/mock_packages/zmpi.py
@@ -5,9 +5,8 @@ class Zmpi(Package):
        with dependencies."""
     homepage = "http://www.spack-fake-zmpi.org"
     url      = "http://www.spack-fake-zmpi.org/downloads/zmpi-1.0.tar.gz"
-    md5      = "foobarbaz"
 
-    versions = '1.0'
+    versions = { '1.0' : 'foobarbaz' }
 
     provides('mpi@10.0:')
     depends_on('fake')
diff --git a/lib/spack/spack/tty.py b/lib/spack/spack/tty.py
index 4cad0914e6dfd821fe084af89bbde94a81aef716..4584b3de3e78711cac41a5a604946b8ff118cccc 100644
--- a/lib/spack/spack/tty.py
+++ b/lib/spack/spack/tty.py
@@ -47,7 +47,7 @@ def pkg(message):
 
     mac_ver = platform.mac_ver()[0]
     if mac_ver and Version(mac_ver) >= Version('10.7'):
-        print u"\U0001F4E6" + indent,
+        print u"\U0001F4E6" + indent
     else:
-        cprint('@*g{[+]} ')
+        cwrite('@*g{[+]} ')
     print message
diff --git a/lib/spack/spack/util/crypto.py b/lib/spack/spack/util/crypto.py
index da6597ef64e1eccdd68fa8b155a6d2f488210c7e..558c6d906c0e83e7fa372eb40177be20a3c31560 100644
--- a/lib/spack/spack/util/crypto.py
+++ b/lib/spack/spack/util/crypto.py
@@ -1,13 +1,81 @@
 import hashlib
 from contextlib import closing
 
-def md5(filename, block_size=2**20):
-    """Computes the md5 hash of a file."""
-    md5 = hashlib.md5()
+"""Set of acceptable hashes that Spack will use."""
+_acceptable_hashes = [
+    hashlib.md5,
+    hashlib.sha1,
+    hashlib.sha224,
+    hashlib.sha256,
+    hashlib.sha384,
+    hashlib.sha512 ]
+
+"""Index for looking up hasher for a digest."""
+_size_to_hash = { h().digest_size : h for h in _acceptable_hashes }
+
+
+def checksum(hashlib_algo, filename, **kwargs):
+    """Returns a hex digest of the filename generated using an
+       algorithm from hashlib.
+    """
+    block_size = kwargs.get('block_size', 2**20)
+    hasher = hashlib_algo()
     with closing(open(filename)) as file:
         while True:
             data = file.read(block_size)
             if not data:
                 break
-            md5.update(data)
-        return md5.hexdigest()
+            hasher.update(data)
+    return hasher.hexdigest()
+
+
+
+class Checker(object):
+    """A checker checks files against one particular hex digest.
+       It will automatically determine what hashing algorithm
+       to used based on the length of the digest it's initialized
+       with.  e.g., if the digest is 32 hex characters long this will
+       use md5.
+
+       Example: know your tarball should hash to 'abc123'.  You want
+       to check files against this.  You would use this class like so::
+
+          hexdigest = 'abc123'
+          checker = Checker(hexdigest)
+          success = checker.check('downloaded.tar.gz')
+
+       After the call to check, the actual checksum is available in
+       checker.sum, in case it's needed for error output.
+
+       You can trade read performance and memory usage by
+       adjusting the block_size optional arg.  By default it's
+       a 1MB (2**20 bytes) buffer.
+    """
+    def __init__(self, hexdigest, **kwargs):
+        self.block_size = kwargs.get('block_size', 2**20)
+        self.hexdigest = hexdigest
+        self.sum       = None
+
+        bytes = len(hexdigest) / 2
+        if not bytes in _size_to_hash:
+            raise ValueError(
+                'Spack knows no hash algorithm for this digest: %s' % hexdigest)
+
+        self.hash_fun = _size_to_hash[bytes]
+
+
+    @property
+    def hash_name(self):
+        """Get the name of the hash function this Checker is using."""
+        return self.hash_fun().name
+
+
+    def check(self, filename):
+        """Read the file with the specified name and check its checksum
+           against self.hexdigest.  Return True if they match, False
+           otherwise.  Actual checksum is stored in self.sum.
+        """
+        self.sum = checksum(
+            self.hash_fun, filename, block_size=self.block_size)
+        return self.sum == self.hexdigest
+
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
index 4673b55ed322fd159c9b5075b911573601f49fc1..b0d23803145abf67293b86df1c139e1662955d63 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -1,3 +1,4 @@
+import os
 
 def env_flag(name):
     if name in os.environ:
diff --git a/lib/spack/spack/util/filesystem.py b/lib/spack/spack/util/filesystem.py
index 32f99af533e0a2d95ee70814a0dac57106fa9983..18020d5b2508648cf2b2cb7ddc271806a6df88bf 100644
--- a/lib/spack/spack/util/filesystem.py
+++ b/lib/spack/spack/util/filesystem.py
@@ -68,3 +68,10 @@ def stem(path):
         if re.search(suffix, path):
             return re.sub(suffix, "", path)
     return path
+
+
+def can_access(file_name):
+    """True if we have read/write access to the file."""
+    return os.access(file_name, os.R_OK|os.W_OK)
+
+
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index 5564d80a4e28827a21692fcd2406709d9eca4b4a..2f4c80a85d14dd641d4f45139d953b2a351d5046 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -23,7 +23,7 @@
 import sys
 import re
 from bisect import bisect_left
-from functools import total_ordering
+from functools import total_ordering, wraps
 
 import spack.util.none_high as none_high
 import spack.util.none_low as none_low
@@ -71,6 +71,7 @@ def check_type(t):
 
 def coerced(method):
     """Decorator that ensures that argument types of a method are coerced."""
+    @wraps(method)
     def coercing_method(a, b):
         if type(a) == type(b) or a is None or b is None:
             return method(a, b)
@@ -84,6 +85,8 @@ def coercing_method(a, b):
 class Version(object):
     """Class to represent versions"""
     def __init__(self, string):
+        string = str(string)
+
         if not re.match(VALID_VERSION, string):
             raise ValueError("Bad characters in version string: %s" % string)