diff --git a/lib/spack/llnl/util/tty/log.py b/lib/spack/llnl/util/tty/log.py
index b67edcf9ccf0bba536daebf54bd63be3f0c7c687..a4ba2a9bdf90a4f0fd9f5bc3756ba674c1535c3d 100644
--- a/lib/spack/llnl/util/tty/log.py
+++ b/lib/spack/llnl/util/tty/log.py
@@ -24,11 +24,11 @@
 ##############################################################################
 """Utility classes for logging the output of blocks of code.
 """
-import sys
+import multiprocessing
 import os
 import re
 import select
-import inspect
+import sys
 
 import llnl.util.tty as tty
 import llnl.util.tty.color as color
@@ -100,25 +100,29 @@ def __exit__(self, exc_type, exception, traceback):
 
 
 class log_output(object):
-    """Redirects output and error of enclosed block to a file.
+    """Spawns a daemon that reads from a pipe and writes to a file
 
     Usage:
-        with log_output(open('logfile.txt', 'w')):
-           # do things ... output will be logged.
+        # Spawns the daemon
+        with log_output('logfile.txt', 'w') as log_redirection:
+           # do things ... output is not redirected
+           with log_redirection:
+                # do things ... output will be logged
 
     or:
-        with log_output(open('logfile.txt', 'w'), echo=True):
-           # do things ... output will be logged
-           # and also printed to stdout.
-
-    Closes the provided stream when done with the block.
-    If echo is True, also prints the output to stdout.
+        with log_output('logfile.txt', echo=True) as log_redirection:
+           # do things ... output is not redirected
+           with log_redirection:
+               # do things ... output will be logged
+               # and also printed to stdout.
+
+    Opens a stream in 'w' mode at daemon spawning and closes it at
+    daemon joining. If echo is True, also prints the output to stdout.
     """
 
-    def __init__(self, stream, echo=False, force_color=False, debug=False):
-        self.stream = stream
-
-        # various output options
+    def __init__(self, filename, echo=False, force_color=False, debug=False):
+        self.filename = filename
+        # Various output options
         self.echo = echo
         self.force_color = force_color
         self.debug = debug
@@ -126,70 +130,81 @@ def __init__(self, stream, echo=False, force_color=False, debug=False):
         # Default is to try file-descriptor reassignment unless the system
         # out/err streams do not have an associated file descriptor
         self.directAssignment = False
-
-    def trace(self, frame, event, arg):
-        """Jumps to __exit__ on the child process."""
-        raise _SkipWithBlock()
+        self.read, self.write = os.pipe()
+
+        # Sets a daemon that writes to file what it reads from a pipe
+        self.p = multiprocessing.Process(
+            target=self._spawn_writing_daemon,
+            args=(self.read,),
+            name='logger_daemon'
+        )
+        self.p.daemon = True
+        # Needed to un-summon the daemon
+        self.parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
     def __enter__(self):
-        """Redirect output from the with block to a file.
-
-        This forks the with block as a separate process, with stdout
-        and stderr redirected back to the parent via a pipe.  If
-        echo is set, also writes to standard out.
-
-        """
-        # remember these values for later.
-        self._force_color = color._force_color
-        self._debug = tty._debug
-
-        read, write = os.pipe()
-
-        self.pid = os.fork()
-        if self.pid:
-            # Parent: read from child, skip the with block.
-            os.close(write)
-
-            read_file = os.fdopen(read, 'r', 0)
-            with self.stream as log_file:
-                with keyboard_input(sys.stdin):
-                    while True:
-                        rlist, w, x = select.select(
-                            [read_file, sys.stdin], [], [])
-                        if not rlist:
+        self.p.start()
+        return log_output.OutputRedirection(self)
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.parent_pipe.send(True)
+        self.p.join(60.0)  # 1 minute to join the child
+
+    def _spawn_writing_daemon(self, read):
+        # Parent: read from child, skip the with block.
+        read_file = os.fdopen(read, 'r', 0)
+        with open(self.filename, 'w') as log_file:
+            with keyboard_input(sys.stdin):
+                while True:
+                    rlist, _, _ = select.select([read_file, sys.stdin], [], [])
+                    if not rlist:
+                        break
+
+                    # Allow user to toggle echo with 'v' key.
+                    # Currently ignores other chars.
+                    if sys.stdin in rlist:
+                        if sys.stdin.read(1) == 'v':
+                            self.echo = not self.echo
+
+                    # Handle output from the with block process.
+                    if read_file in rlist:
+                        line = read_file.readline()
+                        if not line:
+                            # For some reason we never reach this point...
                             break
 
-                        # Allow user to toggle echo with 'v' key.
-                        # Currently ignores other chars.
-                        if sys.stdin in rlist:
-                            if sys.stdin.read(1) == 'v':
-                                self.echo = not self.echo
+                        # Echo to stdout if requested.
+                        if self.echo:
+                            sys.stdout.write(line)
 
-                        # handle output from the with block process.
-                        if read_file in rlist:
-                            line = read_file.readline()
-                            if not line:
-                                break
+                        # Stripped output to log file.
+                        log_file.write(_strip(line))
+                        log_file.flush()
 
-                            # Echo to stdout if requested.
-                            if self.echo:
-                                sys.stdout.write(line)
+                    if self.child_pipe.poll():
+                        break
 
-                            # Stripped output to log file.
-                            log_file.write(_strip(line))
+    def __del__(self):
+        """Closes the pipes"""
+        os.close(self.write)
+        os.close(self.read)
 
-            read_file.flush()
-            read_file.close()
+    class OutputRedirection(object):
 
-            # Set a trace function to skip the with block.
-            sys.settrace(lambda *args, **keys: None)
-            frame = inspect.currentframe(1)
-            frame.f_trace = self.trace
+        def __init__(self, other):
+            self.__dict__.update(other.__dict__)
 
-        else:
-            # Child: redirect output, execute the with block.
-            os.close(read)
+        def __enter__(self):
+            """Redirect output from the with block to a file.
 
+            Hijacks stdout / stderr and writes to the pipe
+            connected to the logger daemon
+            """
+            # remember these values for later.
+            self._force_color = color._force_color
+            self._debug = tty._debug
+            # Redirect this output to a pipe
+            write = self.write
             try:
                 # Save old stdout and stderr
                 self._stdout = os.dup(sys.stdout.fileno())
@@ -205,53 +220,26 @@ def __enter__(self):
                 output_redirect = os.fdopen(write, 'w')
                 sys.stdout = output_redirect
                 sys.stderr = output_redirect
-
             if self.force_color:
                 color._force_color = True
-
             if self.debug:
                 tty._debug = True
 
-    def __exit__(self, exc_type, exception, traceback):
-        """Exits on child, handles skipping the with block on parent."""
-        # Child should just exit here.
-        if self.pid == 0:
+        def __exit__(self, exc_type, exception, traceback):
+            """Plugs back the original file descriptors
+            for stdout and stderr
+            """
             # Flush the log to disk.
             sys.stdout.flush()
             sys.stderr.flush()
-
-            if exception:
-                # Restore stdout on the child if there's an exception,
-                # and let it be raised normally.
-                #
-                # This assumes that even if the exception is caught,
-                # the child will exit with a nonzero return code.  If
-                # it doesn't, the child process will continue running.
-                #
-                # TODO: think about how this works outside install.
-                # TODO: ideally would propagate exception to parent...
-                if self.directAssignment:
-                    sys.stdout = self._stdout
-                    sys.stderr = self._stderr
-                else:
-                    os.dup2(self._stdout, sys.stdout.fileno())
-                    os.dup2(self._stderr, sys.stderr.fileno())
-
-                return False
-
+            if self.directAssignment:
+                # We seem to need this only to pass test/install.py
+                sys.stdout = self._stdout
+                sys.stderr = self._stderr
             else:
-                # Die quietly if there was no exception.
-                os._exit(0)
-
-        else:
-            # If the child exited badly, parent also should exit.
-            pid, returncode = os.waitpid(self.pid, 0)
-            if returncode != 0:
-                os._exit(1)
-
-        # restore output options.
-        color._force_color = self._force_color
-        tty._debug = self._debug
+                os.dup2(self._stdout, sys.stdout.fileno())
+                os.dup2(self._stderr, sys.stderr.fileno())
 
-        # Suppresses exception if it's our own.
-        return exc_type is _SkipWithBlock
+            # restore output options.
+            color._force_color = self._force_color
+            tty._debug = self._debug
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index e284a58194431ad0350073e4a1e6caffbca4914d..67c64276ee579d0bbb7a9f524b1fbf53d6c5aa6e 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -186,10 +186,19 @@
 #       packages should live.  This file is overloaded for spack core vs.
 #       for packages.
 #
-__all__ = ['Package', 'StagedPackage', 'CMakePackage',
-           'Version', 'when', 'ver', 'alldeps', 'nolink']
+__all__ = ['Package',
+           'CMakePackage',
+           'AutotoolsPackage',
+           'MakefilePackage',
+           'Version',
+           'when',
+           'ver',
+           'alldeps',
+           'nolink']
 from spack.package import Package, ExtensionConflictError
-from spack.package import StagedPackage, CMakePackage
+from spack.build_systems.makefile import MakefilePackage
+from spack.build_systems.autotools import AutotoolsPackage
+from spack.build_systems.cmake import CMakePackage
 from spack.version import Version, ver
 from spack.spec import DependencySpec, alldeps, nolink
 from spack.multimethod import when
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 792cd09eb8fbe4de0fb80e8e3a2a7cda557e912c..7579f9adc6b06d420256164292d82b1c04995491 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -51,16 +51,14 @@
 Skimming this module is a nice way to get acquainted with the types of
 calls you can make from within the install() function.
 """
+import multiprocessing
 import os
-import sys
 import shutil
-import multiprocessing
-import platform
+import sys
 
 import llnl.util.tty as tty
-from llnl.util.filesystem import *
-
 import spack
+from llnl.util.filesystem import *
 from spack.environment import EnvironmentModifications, validate
 from spack.util.environment import *
 from spack.util.executable import Executable, which
@@ -351,8 +349,8 @@ def set_module_variables_for_package(pkg, module):
     m.cmake = Executable('cmake')
     m.ctest = Executable('ctest')
 
-    # standard CMake arguments
-    m.std_cmake_args = get_std_cmake_args(pkg)
+    # Standard CMake arguments
+    m.std_cmake_args = spack.CMakePackage._std_args(pkg)
 
     # Put spack compiler paths in module scope.
     link_dir = spack.build_env_path
@@ -522,41 +520,26 @@ def child_fun():
     carries on.
     """
 
-    try:
-        pid = os.fork()
-    except OSError as e:
-        raise InstallError("Unable to fork build process: %s" % e)
-
-    if pid == 0:
-        # Give the child process the package's build environment.
-        setup_package(pkg, dirty=dirty)
-
+    def child_execution(child_connection):
         try:
-            # call the forked function.
+            setup_package(pkg, dirty=dirty)
             function()
-
-            # Use os._exit here to avoid raising a SystemExit exception,
-            # which interferes with unit tests.
-            os._exit(0)
-
-        except spack.error.SpackError as e:
-            e.die()
-
-        except:
-            # Child doesn't raise or return to main spack code.
-            # Just runs default exception handler and exits.
-            sys.excepthook(*sys.exc_info())
-            os._exit(1)
-
-    else:
-        # Parent process just waits for the child to complete.  If the
-        # child exited badly, assume it already printed an appropriate
-        # message.  Just make the parent exit with an error code.
-        pid, returncode = os.waitpid(pid, 0)
-        if returncode != 0:
-            message = "Installation process had nonzero exit code : {code}"
-            strcode = str(returncode)
-            raise InstallError(message.format(code=strcode))
+            child_connection.send([None, None, None])
+        except Exception as e:
+            child_connection.send([type(e), e, None])
+        finally:
+            child_connection.close()
+
+    parent_connection, child_connection = multiprocessing.Pipe()
+    p = multiprocessing.Process(
+        target=child_execution,
+        args=(child_connection,)
+    )
+    p.start()
+    exc_type, exception, traceback = parent_connection.recv()
+    p.join()
+    if exception is not None:
+        raise exception
 
 
 class InstallError(spack.error.SpackError):
diff --git a/lib/spack/spack/build_systems/__init__.py b/lib/spack/spack/build_systems/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bb5576708c7388dad7312803740a2c021d71a8e
--- /dev/null
+++ b/lib/spack/spack/build_systems/autotools.py
@@ -0,0 +1,106 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+import os.path
+
+import llnl.util.tty as tty
+from spack.package import PackageBase
+
+
+class AutotoolsPackage(PackageBase):
+    """Specialized class for packages that are built using GNU Autotools
+
+    This class provides four phases that can be overridden:
+    - autoreconf
+    - configure
+    - build
+    - install
+
+    They all have sensible defaults and for many packages the only thing
+    necessary will be to override `configure_args`
+    """
+    phases = ['autoreconf', 'configure', 'build', 'install']
+    # To be used in UI queries that require to know which
+    # build-system class we are using
+    build_system_class = 'AutotoolsPackage'
+
+    def autoreconf(self, spec, prefix):
+        """Not needed usually, configure should be already there"""
+        pass
+
+    @PackageBase.sanity_check('autoreconf')
+    def is_configure_or_die(self):
+        """Checks the presence of a `configure` file after the
+        autoreconf phase"""
+        if not os.path.exists('configure'):
+            raise RuntimeError(
+                'configure script not found in {0}'.format(os.getcwd()))
+
+    def configure_args(self):
+        """Method to be overridden. Should return an iterable containing
+        all the arguments that must be passed to configure, except --prefix
+        """
+        return []
+
+    def configure(self, spec, prefix):
+        """Runs configure with the arguments specified in `configure_args`
+        and an appropriately set prefix
+        """
+        options = ['--prefix={0}'.format(prefix)] + self.configure_args()
+        inspect.getmodule(self).configure(*options)
+
+    def build(self, spec, prefix):
+        """The usual `make` after configure"""
+        inspect.getmodule(self).make()
+
+    def install(self, spec, prefix):
+        """...and the final `make install` after configure"""
+        inspect.getmodule(self).make('install')
+
+    @PackageBase.sanity_check('build')
+    @PackageBase.on_package_attributes(run_tests=True)
+    def _run_default_function(self):
+        """This function is run after build if self.run_tests == True
+
+        It will search for a method named `check` and run it. A sensible
+        default is provided in the base class.
+        """
+        try:
+            fn = getattr(self, 'check')
+            tty.msg('Trying default sanity checks [check]')
+            fn()
+        except AttributeError:
+            tty.msg('Skipping default sanity checks [method `check` not implemented]')  # NOQA: ignore=E501
+
+    def check(self):
+        """Default test : search the Makefile for targets `test` and `check`
+        and run them if found.
+        """
+        self._if_make_target_execute('test')
+        self._if_make_target_execute('check')
+
+    # Check that self.prefix is there after installation
+    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb1076d7b777ad1dcdb6a0dc2c2f39169a1ea56f
--- /dev/null
+++ b/lib/spack/spack/build_systems/cmake.py
@@ -0,0 +1,143 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+import os
+import platform
+
+import llnl.util.tty as tty
+import spack.build_environment
+from llnl.util.filesystem import working_dir, join_path
+from spack.package import PackageBase
+
+
+class CMakePackage(PackageBase):
+    """Specialized class for packages that are built using cmake
+
+    This class provides three phases that can be overridden:
+    - cmake
+    - build
+    - install
+
+    They all have sensible defaults and for many packages the only thing
+    necessary will be to override `cmake_args`
+    """
+    phases = ['cmake', 'build', 'install']
+    # To be used in UI queries that require to know which
+    # build-system class we are using
+    build_system_class = 'CMakePackage'
+
+    def build_type(self):
+        """Override to provide the correct build_type in case a complex
+        logic is needed
+        """
+        return 'RelWithDebInfo'
+
+    def root_cmakelists_dir(self):
+        """Directory where to find the root CMakeLists.txt"""
+        return self.stage.source_path
+
+    @property
+    def std_cmake_args(self):
+        """Standard cmake arguments provided as a property for
+        convenience of package writers
+        """
+        # standard CMake arguments
+        return CMakePackage._std_args(self)
+
+    @staticmethod
+    def _std_args(pkg):
+        """Computes the standard cmake arguments for a generic package"""
+        try:
+            build_type = pkg.build_type()
+        except AttributeError:
+            build_type = 'RelWithDebInfo'
+
+        args = ['-DCMAKE_INSTALL_PREFIX:PATH={0}'.format(pkg.prefix),
+                '-DCMAKE_BUILD_TYPE:STRING={0}'.format(build_type),
+                '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON']
+        if platform.mac_ver()[0]:
+            args.append('-DCMAKE_FIND_FRAMEWORK:STRING=LAST')
+
+        # Set up CMake rpath
+        args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=FALSE')
+        rpaths = ':'.join(spack.build_environment.get_rpaths(pkg))
+        args.append('-DCMAKE_INSTALL_RPATH:STRING={0}'.format(rpaths))
+        return args
+
+    def build_directory(self):
+        """Override to provide another place to build the package"""
+        return join_path(self.stage.source_path, 'spack-build')
+
+    def cmake_args(self):
+        """Method to be overridden. Should return an iterable containing
+        all the arguments that must be passed to configure, except:
+        - CMAKE_INSTALL_PREFIX
+        - CMAKE_BUILD_TYPE
+        """
+        return []
+
+    def cmake(self, spec, prefix):
+        """Run cmake in the build directory"""
+        options = [self.root_cmakelists_dir()] + self.std_cmake_args + \
+            self.cmake_args()
+        create = not os.path.exists(self.build_directory())
+        with working_dir(self.build_directory(), create=create):
+            inspect.getmodule(self).cmake(*options)
+
+    def build(self, spec, prefix):
+        """The usual `make` after cmake"""
+        with working_dir(self.build_directory()):
+            inspect.getmodule(self).make()
+
+    def install(self, spec, prefix):
+        """...and the final `make install` after cmake"""
+        with working_dir(self.build_directory()):
+            inspect.getmodule(self).make('install')
+
+    @PackageBase.sanity_check('build')
+    @PackageBase.on_package_attributes(run_tests=True)
+    def _run_default_function(self):
+        """This function is run after build if self.run_tests == True
+
+        It will search for a method named `check` and run it. A sensible
+        default is provided in the base class.
+        """
+        try:
+            fn = getattr(self, 'check')
+            tty.msg('Trying default build sanity checks [check]')
+            fn()
+        except AttributeError:
+            tty.msg('Skipping default build sanity checks [method `check` not implemented]')  # NOQA: ignore=E501
+
+    def check(self):
+        """Default test : search the Makefile for the target `test`
+        and run them if found.
+        """
+        with working_dir(self.build_directory()):
+            self._if_make_target_execute('test')
+
+    # Check that self.prefix is there after installation
+    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/makefile.py b/lib/spack/spack/build_systems/makefile.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcddadeedc06eaa7746e1cc1b0aaa21383b7e79f
--- /dev/null
+++ b/lib/spack/spack/build_systems/makefile.py
@@ -0,0 +1,77 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+
+from llnl.util.filesystem import working_dir
+from spack.package import PackageBase
+
+
+class MakefilePackage(PackageBase):
+    """Specialized class for packages that are built using editable Makefiles
+
+    This class provides three phases that can be overridden:
+    - edit
+    - build
+    - install
+
+    It is necessary to override the 'edit' phase, while 'build' and 'install'
+    have sensible defaults.
+    """
+    phases = ['edit', 'build', 'install']
+    # To be used in UI queries that require to know which
+    # build-system class we are using
+    build_system_class = 'MakefilePackage'
+
+    def build_directory(self):
+        """Directory where the main Makefile is located"""
+        return self.stage.source_path
+
+    def build_args(self):
+        """List of arguments that should be passed to make at build time"""
+        return []
+
+    def install_args(self):
+        """List of arguments that should be passed to make at install time"""
+        return []
+
+    def edit(self, spec, prefix):
+        """This phase cannot be defaulted for obvious reasons..."""
+        raise NotImplementedError('\'edit\' function not implemented')
+
+    def build(self, spec, prefix):
+        """Default build phase : call make passing build_args"""
+        args = self.build_args()
+        with working_dir(self.build_directory()):
+            inspect.getmodule(self).make(*args)
+
+    def install(self, spec, prefix):
+        """Default install phase : call make passing install_args"""
+        args = self.install_args() + ['install']
+        with working_dir(self.build_directory()):
+            inspect.getmodule(self).make(*args)
+
+    # Check that self.prefix is there after installation
+    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c43acc2b3c23d09ef24d46c96dae177430b3164
--- /dev/null
+++ b/lib/spack/spack/cmd/build.py
@@ -0,0 +1,43 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 spack.cmd.configure as cfg
+
+from spack import *
+
+description = 'Stops at build stage when installing a package, if possible'
+
+build_system_to_phase = {
+    CMakePackage: 'build',
+    AutotoolsPackage: 'build'
+}
+
+
+def setup_parser(subparser):
+    cfg.setup_parser(subparser)
+
+
+def build(parser, args):
+    cfg._stop_at_phase_during_install(args, build, build_system_to_phase)
diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py
new file mode 100644
index 0000000000000000000000000000000000000000..3eebe2584bb92ae3370c3dc3119b86f796ff8c9d
--- /dev/null
+++ b/lib/spack/spack/cmd/configure.py
@@ -0,0 +1,90 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 argparse
+
+import llnl.util.tty as tty
+import spack.cmd
+import spack.cmd.install as inst
+
+from spack import *
+
+description = 'Stops at configuration stage when installing a package, if possible'  # NOQA: ignore=E501
+
+
+build_system_to_phase = {
+    CMakePackage: 'cmake',
+    AutotoolsPackage: 'configure'
+}
+
+
+def setup_parser(subparser):
+    subparser.add_argument(
+        'package',
+        nargs=argparse.REMAINDER,
+        help="spec of the package to install"
+    )
+    subparser.add_argument(
+        '-v', '--verbose',
+        action='store_true',
+        help="Print additional output during builds"
+    )
+
+
+def _stop_at_phase_during_install(args, calling_fn, phase_mapping):
+    if not args.package:
+        tty.die("configure requires at least one package argument")
+
+    # TODO: to be refactored with code in install
+    specs = spack.cmd.parse_specs(args.package, concretize=True)
+    if len(specs) != 1:
+        tty.error('only one spec can be installed at a time.')
+    spec = specs.pop()
+    pkg = spec.package
+    try:
+        key = [cls for cls in phase_mapping if isinstance(pkg, cls)].pop()
+        phase = phase_mapping[key]
+        # Install package dependencies if needed
+        parser = argparse.ArgumentParser()
+        inst.setup_parser(parser)
+        tty.msg('Checking dependencies for {0}'.format(args.package))
+        cli_args = ['-v'] if args.verbose else []
+        install_args = parser.parse_args(cli_args + ['--only=dependencies'])
+        install_args.package = args.package
+        inst.install(parser, install_args)
+        # Install package and stop at the given phase
+        cli_args = ['-v'] if args.verbose else []
+        install_args = parser.parse_args(cli_args + ['--only=package'])
+        install_args.package = args.package
+        inst.install(parser, install_args, stop_at=phase)
+    except IndexError:
+        tty.error(
+            'Package {0} has no {1} phase, or its {1} phase is not separated from install'.format(  # NOQA: ignore=E501
+                spec.name, calling_fn.__name__)
+        )
+
+
+def configure(parser, args):
+    _stop_at_phase_during_install(args, configure, build_system_to_phase)
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index 741a320ea789ce4a074e4a273536105fa60d7649..5db0601d44e281dca6bc1d3a78d57af60bcd26a6 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -22,25 +22,24 @@
 # 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 string
+from __future__ import print_function
+
 import os
 import re
+import string
 
-from ordereddict_backport import OrderedDict
 import llnl.util.tty as tty
-from llnl.util.filesystem import mkdirp
-
 import spack
 import spack.cmd
 import spack.cmd.checksum
 import spack.url
 import spack.util.web
-from spack.spec import Spec
-from spack.util.naming import *
+from llnl.util.filesystem import mkdirp
+from ordereddict_backport import OrderedDict
 from spack.repository import Repo, RepoError
-
+from spack.spec import Spec
 from spack.util.executable import which
-
+from spack.util.naming import *
 
 description = "Create a new package file from an archive URL"
 
@@ -87,7 +86,7 @@
 from spack import *
 
 
-class ${class_name}(Package):
+class ${class_name}(${base_class_name}):
     ""\"FIXME: Put a proper description of your package here.""\"
 
     # FIXME: Add a proper url for your package's homepage here.
@@ -98,109 +97,160 @@ class ${class_name}(Package):
 
 ${dependencies}
 
-    def install(self, spec, prefix):
-${install}
+${body}
 """)
 
-# Build dependencies and extensions
-dependencies_dict = {
-    'autotools': """\
+
+class DefaultGuess(object):
+    """Provides the default values to be used for the package file template"""
+    base_class_name = 'Package'
+
+    dependencies = """\
     # FIXME: Add dependencies if required.
-    # depends_on('foo')""",
+    # depends_on('foo')"""
 
-    'cmake': """\
-    # FIXME: Add additional dependencies if required.
-    depends_on('cmake', type='build')""",
+    body = """\
+    def install(self, spec, prefix):
+        # FIXME: Unknown build system
+        make()
+        make('install')"""
 
-    'scons': """\
-    # FIXME: Add additional dependencies if required.
-    depends_on('scons', type='build')""",
+    def __init__(self, name, url, version_hash_tuples):
+        self.name = name
+        self.class_name = mod_to_class(name)
+        self.url = url
+        self.version_hash_tuples = version_hash_tuples
 
-    'bazel': """\
-    # FIXME: Add additional dependencies if required.
-    depends_on('bazel', type='build')""",
+    @property
+    def versions(self):
+        """Adds a version() call to the package for each version found."""
+        max_len = max(len(str(v)) for v, h in self.version_hash_tuples)
+        format = "    version(%%-%ds, '%%s')" % (max_len + 2)
+        return '\n'.join(
+            format % ("'%s'" % v, h) for v, h in self.version_hash_tuples
+        )
 
-    'python': """\
-    extends('python')
 
-    # FIXME: Add additional dependencies if required.
-    # depends_on('py-setuptools', type='build')
-    # depends_on('py-foo',        type=nolink)""",
+class AutotoolsGuess(DefaultGuess):
+    """Provides appropriate overrides for autotools-based packages"""
+    base_class_name = 'AutotoolsPackage'
 
-    'R': """\
-    extends('R')
+    dependencies = """\
+    # FIXME: Add dependencies if required.
+    # depends_on('m4', type='build')
+    # depends_on('autoconf', type='build')
+    # depends_on('automake', type='build')
+    # depends_on('libtool', type='build')
+    # depends_on('foo')"""
 
-    # FIXME: Add additional dependencies if required.
-    # depends_on('r-foo', type=nolink)""",
+    body = """\
+    def configure_args(self):
+       # FIXME: Add arguments other than --prefix
+       # FIXME: If not needed delete the function
+       args = []
+       return args"""
 
-    'octave': """\
-    extends('octave')
 
+class CMakeGuess(DefaultGuess):
+    """Provides appropriate overrides for cmake-based packages"""
+    base_class_name = 'CMakePackage'
+
+    dependencies = """\
     # FIXME: Add additional dependencies if required.
-    # depends_on('octave-foo', type=nolink)""",
+    depends_on('cmake', type='build')"""
+
+    body = """\
+    def cmake_args(self):
+       # FIXME: Add arguments other than
+       # FIXME: CMAKE_INSTALL_PREFIX and CMAKE_BUILD_TYPE
+       # FIXME: If not needed delete the function
+       args = []
+       return args"""
 
-    'unknown': """\
-    # FIXME: Add dependencies if required.
-    # depends_on('foo')"""
-}
 
-# Default installation instructions
-install_dict = {
-    'autotools': """\
-        # FIXME: Modify the configure line to suit your build system here.
-        configure('--prefix={0}'.format(prefix))
+class SconsGuess(DefaultGuess):
+    """Provides appropriate overrides for scons-based packages"""
+    dependencies = """\
+    # FIXME: Add additional dependencies if required.
+    depends_on('scons', type='build')"""
 
+    body = """\
+    def install(self, spec, prefix):
         # FIXME: Add logic to build and install here.
-        make()
-        make('install')""",
+        scons('prefix={0}'.format(prefix))
+        scons('install')"""
 
-    'cmake': """\
-        with working_dir('spack-build', create=True):
-            # FIXME: Modify the cmake line to suit your build system here.
-            cmake('..', *std_cmake_args)
 
-            # FIXME: Add logic to build and install here.
-            make()
-            make('install')""",
+class BazelGuess(DefaultGuess):
+    """Provides appropriate overrides for bazel-based packages"""
+    dependencies = """\
+    # FIXME: Add additional dependencies if required.
+    depends_on('bazel', type='build')"""
 
-    'scons': """\
+    body = """\
+    def install(self, spec, prefix):
         # FIXME: Add logic to build and install here.
-        scons('prefix={0}'.format(prefix))
-        scons('install')""",
+        bazel()"""
 
-    'bazel': """\
-        # FIXME: Add logic to build and install here.
-        bazel()""",
 
-    'python': """\
+class PythonGuess(DefaultGuess):
+    """Provides appropriate overrides for python extensions"""
+    dependencies = """\
+    extends('python')
+
+    # FIXME: Add additional dependencies if required.
+    # depends_on('py-setuptools', type='build')
+    # depends_on('py-foo',        type=nolink)"""
+
+    body = """\
+    def install(self, spec, prefix):
         # FIXME: Add logic to build and install here.
-        setup_py('install', '--prefix={0}'.format(prefix))""",
+        setup_py('install', '--prefix={0}'.format(prefix))"""
+
+    def __init__(self, name, *args):
+        name = 'py-{0}'.format(name)
+        super(PythonGuess, self).__init__(name, *args)
+
 
-    'R': """\
+class RGuess(DefaultGuess):
+    """Provides appropriate overrides for R extensions"""
+    dependencies = """\
+    extends('R')
+
+    # FIXME: Add additional dependencies if required.
+    # depends_on('r-foo', type=nolink)"""
+
+    body = """\
+    def install(self, spec, prefix):
         # FIXME: Add logic to build and install here.
         R('CMD', 'INSTALL', '--library={0}'.format(self.module.r_lib_dir),
-          self.stage.source_path)""",
+          self.stage.source_path)"""
 
-    'octave': """\
+    def __init__(self, name, *args):
+        name = 'r-{0}'.format(name)
+        super(RGuess, self).__init__(name, *args)
+
+
+class OctaveGuess(DefaultGuess):
+    """Provides appropriate overrides for octave packages"""
+    dependencies = """\
+    extends('octave')
+
+    # FIXME: Add additional dependencies if required.
+    # depends_on('octave-foo', type=nolink)"""
+
+    body = """\
+    def install(self, spec, prefix):
         # FIXME: Add logic to build and install here.
         octave('--quiet', '--norc',
                '--built-in-docstrings-file=/dev/null',
                '--texi-macros-file=/dev/null',
                '--eval', 'pkg prefix {0}; pkg install {1}'.format(
-                   prefix, self.stage.archive_file))""",
+                   prefix, self.stage.archive_file))"""
 
-    'unknown': """\
-        # FIXME: Unknown build system
-        make()
-        make('install')"""
-}
-
-
-def make_version_calls(ver_hash_tuples):
-    """Adds a version() call to the package for each version found."""
-    max_len = max(len(str(v)) for v, h in ver_hash_tuples)
-    format = "    version(%%-%ds, '%%s')" % (max_len + 2)
-    return '\n'.join(format % ("'%s'" % v, h) for v, h in ver_hash_tuples)
+    def __init__(self, name, *args):
+        name = 'octave-{0}'.format(name)
+        super(OctaveGuess, self).__init__(name, *args)
 
 
 def setup_parser(subparser):
@@ -227,6 +277,16 @@ def setup_parser(subparser):
 
 class BuildSystemGuesser(object):
 
+    _choices = {
+        'autotools': AutotoolsGuess,
+        'cmake': CMakeGuess,
+        'scons': SconsGuess,
+        'bazel': BazelGuess,
+        'python': PythonGuess,
+        'R': RGuess,
+        'octave': OctaveGuess
+    }
+
     def __call__(self, stage, url):
         """Try to guess the type of build system used by a project based on
         the contents of its archive or the URL it was downloaded from."""
@@ -275,6 +335,10 @@ def __call__(self, stage, url):
 
         self.build_system = build_system
 
+    def make_guess(self, name, url, ver_hash_tuples):
+        cls = self._choiches.get(self.build_system, DefaultGuess)
+        return cls(name, url, ver_hash_tuples)
+
 
 def guess_name_and_version(url, args):
     # Try to deduce name and version of the new package from the URL
@@ -348,7 +412,7 @@ def fetch_tarballs(url, name, version):
         tty.msg("Found %s versions of %s:" % (len(versions), name),
                 *spack.cmd.elide_list(
                     ["%-10s%s" % (v, u) for v, u in versions.iteritems()]))
-        print
+        print('')
         archives_to_fetch = tty.get_number(
             "Include how many checksums in the package file?",
             default=5, abort='q')
@@ -389,16 +453,10 @@ def create(parser, args):
     if not ver_hash_tuples:
         tty.die("Could not fetch any tarballs for %s" % name)
 
-    # Add prefix to package name if it is an extension.
-    if guesser.build_system == 'python':
-        name = 'py-{0}'.format(name)
-    if guesser.build_system == 'R':
-        name = 'r-{0}'.format(name)
-    if guesser.build_system == 'octave':
-        name = 'octave-{0}'.format(name)
+    guess = guesser.make_guess(name, url, ver_hash_tuples)
 
     # Create a directory for the new package.
-    pkg_path = repo.filename_for_package_name(name)
+    pkg_path = repo.filename_for_package_name(guess.name)
     if os.path.exists(pkg_path) and not args.force:
         tty.die("%s already exists." % pkg_path)
     else:
@@ -408,12 +466,15 @@ def create(parser, args):
     with open(pkg_path, "w") as pkg_file:
         pkg_file.write(
             package_template.substitute(
-                name=name,
-                class_name=mod_to_class(name),
-                url=url,
-                versions=make_version_calls(ver_hash_tuples),
-                dependencies=dependencies_dict[guesser.build_system],
-                install=install_dict[guesser.build_system]))
+                name=guess.name,
+                class_name=guess.class_name,
+                base_class_name=guess.base_class_name,
+                url=guess.url,
+                versions=guess.versions,
+                dependencies=guess.dependencies,
+                body=guess.body
+            )
+        )
 
     # If everything checks out, go ahead and edit.
     spack.editor(pkg_path)
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 2fa3a07525f8460f19403c1e4220d89e943eb8c3..5366ad4aa8a2d73d4d36fb5dea6f559bb370c3c4 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -48,8 +48,11 @@ def setup_parser(subparser):
 
 def print_text_info(pkg):
     """Print out a plain text description of a package."""
-    print "Package:   ", pkg.name
-    print "Homepage:  ", pkg.homepage
+    header = "{0}:   ".format(pkg.build_system_class)
+
+    print header, pkg.name
+    whitespaces = ''.join([' '] * (len(header) - len("Homepage: ")))
+    print "Homepage:", whitespaces, pkg.homepage
 
     print
     print "Safe versions:  "
@@ -84,6 +87,13 @@ def print_text_info(pkg):
 
             print "    " + fmt % (name, default, desc)
 
+    print
+    print "Installation Phases:"
+    phase_str = ''
+    for phase in pkg.phases:
+        phase_str += "    {0}".format(phase)
+    print phase_str
+
     for deptype in ('build', 'link', 'run'):
         print
         print "%s Dependencies:" % deptype.capitalize()
@@ -94,7 +104,7 @@ def print_text_info(pkg):
             print "    None"
 
     print
-    print "Virtual packages: "
+    print "Virtual Packages: "
     if pkg.provided:
         for spec, when in pkg.provided.items():
             print "    %s provides %s" % (when, spec)
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 8cc7f40efcfc72a2d7b319f3b3541d6b880b4326..aab7c0abc747780a5564c15468da6912592cb769 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -22,7 +22,6 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-from __future__ import print_function
 import argparse
 
 import llnl.util.tty as tty
@@ -75,7 +74,7 @@ def setup_parser(subparser):
         help="Run tests during installation of a package.")
 
 
-def install(parser, args):
+def install(parser, args, **kwargs):
     if not args.package:
         tty.die("install requires at least one package argument")
 
@@ -88,7 +87,7 @@ def install(parser, args):
 
     # Parse cli arguments and construct a dictionary
     # that will be passed to Package.do_install API
-    kwargs = {
+    kwargs.update({
         'keep_prefix': args.keep_prefix,
         'keep_stage': args.keep_stage,
         'install_deps': 'dependencies' in args.things_to_install,
@@ -97,7 +96,7 @@ def install(parser, args):
         'verbose': args.verbose,
         'fake': args.fake,
         'dirty': args.dirty
-    }
+    })
 
     # Spec from cli
     specs = spack.cmd.parse_specs(args.package, concretize=True)
diff --git a/lib/spack/spack/cmd/setup.py b/lib/spack/spack/cmd/setup.py
index c393378a8d7e0953789b7ddb4394001804fd6cc9..50bc03133039cb6a5e71057a21ceca209a64d3c8 100644
--- a/lib/spack/spack/cmd/setup.py
+++ b/lib/spack/spack/cmd/setup.py
@@ -22,16 +22,18 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import sys
-import os
 import argparse
+import os
+import string
+import sys
 
 import llnl.util.tty as tty
-
 import spack
 import spack.cmd
+from spack import which
 from spack.cmd.edit import edit_package
 from spack.stage import DIYStage
+from llnl.util.filesystem import set_executable
 
 description = "Create a configuration script and module, but don't build."
 
@@ -51,6 +53,72 @@ def setup_parser(subparser):
         help="Install a package *without* cleaning the environment.")
 
 
+def spack_transitive_include_path():
+    return ';'.join(
+        os.path.join(dep, 'include')
+        for dep in os.environ['SPACK_DEPENDENCIES'].split(os.pathsep)
+    )
+
+
+def write_spconfig(package):
+    # Set-up the environment
+    spack.build_environment.setup_package(package)
+
+    cmd = [str(which('cmake'))] + package.std_cmake_args + package.cmake_args()
+
+    env = dict()
+
+    paths = os.environ['PATH'].split(':')
+    paths = [item for item in paths if 'spack/env' not in item]
+    env['PATH'] = ':'.join(paths)
+    env['SPACK_TRANSITIVE_INCLUDE_PATH'] = spack_transitive_include_path()
+    env['CMAKE_PREFIX_PATH'] = os.environ['CMAKE_PREFIX_PATH']
+    env['CC'] = os.environ['SPACK_CC']
+    env['CXX'] = os.environ['SPACK_CXX']
+    env['FC'] = os.environ['SPACK_FC']
+
+    setup_fname = 'spconfig.py'
+    with open(setup_fname, 'w') as fout:
+        fout.write(
+            r"""#!%s
+#
+
+import sys
+import os
+import subprocess
+
+def cmdlist(str):
+    return list(x.strip().replace("'",'') for x in str.split('\n') if x)
+env = dict(os.environ)
+""" % sys.executable)
+
+        env_vars = sorted(list(env.keys()))
+        for name in env_vars:
+            val = env[name]
+            if string.find(name, 'PATH') < 0:
+                fout.write('env[%s] = %s\n' % (repr(name), repr(val)))
+            else:
+                if name == 'SPACK_TRANSITIVE_INCLUDE_PATH':
+                    sep = ';'
+                else:
+                    sep = ':'
+
+                fout.write(
+                    'env[%s] = "%s".join(cmdlist("""\n' % (repr(name), sep))
+                for part in string.split(val, sep):
+                    fout.write('    %s\n' % part)
+                fout.write('"""))\n')
+
+        fout.write("env['CMAKE_TRANSITIVE_INCLUDE_PATH'] = env['SPACK_TRANSITIVE_INCLUDE_PATH']   # Deprecated\n")  # NOQA: ignore=E501
+        fout.write('\ncmd = cmdlist("""\n')
+        fout.write('%s\n' % cmd[0])
+        for arg in cmd[1:]:
+            fout.write('    %s\n' % arg)
+        fout.write('""") + sys.argv[1:]\n')
+        fout.write('\nproc = subprocess.Popen(cmd, env=env)\nproc.wait()\n')
+        set_executable(setup_fname)
+
+
 def setup(self, args):
     if not args.spec:
         tty.die("spack setup requires a package spec argument.")
@@ -80,6 +148,12 @@ def setup(self, args):
 
         spec.concretize()
         package = spack.repo.get(spec)
+        if not isinstance(package, spack.CMakePackage):
+            tty.die(
+                'Support for {0} derived packages not yet implemented'.format(
+                    package.build_system_class
+                )
+            )
 
         # It's OK if the package is already installed.
 
@@ -89,10 +163,4 @@ def setup(self, args):
         # TODO: make this an argument, not a global.
         spack.do_checksum = False
 
-        package.do_install(
-            keep_prefix=True,  # Don't remove install directory
-            install_deps=not args.ignore_deps,
-            verbose=args.verbose,
-            keep_stage=True,   # don't remove source dir for SETUP.
-            install_phases=set(['setup', 'provenance']),
-            dirty=args.dirty)
+        write_spconfig(package)
diff --git a/lib/spack/spack/error.py b/lib/spack/spack/error.py
index c94875e91a792c8aaad43e24257260830026627b..5e5c1b1c7e7ac5f3bd5318ba44246b674025bd77 100644
--- a/lib/spack/spack/error.py
+++ b/lib/spack/spack/error.py
@@ -26,6 +26,7 @@
 import sys
 import llnl.util.tty as tty
 import spack
+import inspect
 
 
 class SpackError(Exception):
@@ -49,7 +50,7 @@ def die(self):
         else:
             tty.error(self.message)
             if self.long_message:
-                print self.long_message
+                print(self.long_message)
             os._exit(1)
 
     def __str__(self):
@@ -58,6 +59,16 @@ def __str__(self):
             msg += "\n    %s" % self._long_message
         return msg
 
+    def __repr__(self):
+        args = [repr(self.message), repr(self.long_message)]
+        args = ','.join(args)
+        qualified_name = inspect.getmodule(
+            self).__name__ + '.' + type(self).__name__
+        return qualified_name + '(' + args + ')'
+
+    def __reduce__(self):
+        return type(self), (self.message, self.long_message)
+
 
 class UnsupportedPlatformError(SpackError):
     """Raised by packages when a platform is not supported"""
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 7387fbed58e0d610311cf62a800bb563e14d48c4..52dbd40f6f54f4f12254198e26971c16ad65322c 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -33,24 +33,20 @@
 rundown on spack and how it differs from homebrew, look at the
 README.
 """
+import contextlib
+import copy
+import functools
+import inspect
 import os
-import sys
 import re
+import sys
 import textwrap
 import time
-import string
-import contextlib
 from StringIO import StringIO
 
 import llnl.util.lock
 import llnl.util.tty as tty
-from llnl.util.filesystem import *
-from llnl.util.lang import *
-from llnl.util.link_tree import LinkTree
-from llnl.util.tty.log import log_output
-
 import spack
-import spack.build_environment
 import spack.compilers
 import spack.directives
 import spack.error
@@ -60,20 +56,188 @@
 import spack.repository
 import spack.url
 import spack.util.web
-
+from llnl.util.filesystem import *
+from llnl.util.lang import *
+from llnl.util.link_tree import LinkTree
+from llnl.util.tty.log import log_output
+from spack import directory_layout
 from spack.stage import Stage, ResourceStage, StageComposite
 from spack.util.crypto import bit_length
 from spack.util.environment import dump_environment
-from spack.util.executable import ProcessError, which
+from spack.util.executable import ProcessError
 from spack.version import *
-from spack import directory_layout
-
 
 """Allowed URL schemes for spack packages."""
 _ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
 
 
-class Package(object):
+class InstallPhase(object):
+    """Manages a single phase of the installation
+
+    This descriptor stores at creation time the name of the method it should
+    search for execution. The method is retrieved at __get__ time, so that
+    it can be overridden by subclasses of whatever class declared the phases.
+
+    It also provides hooks to execute prerequisite and sanity checks.
+    """
+
+    def __init__(self, name):
+        self.name = name
+        self.preconditions = []
+        self.sanity_checks = []
+
+    def __get__(self, instance, owner):
+        # The caller is a class that is trying to customize
+        # my behavior adding something
+        if instance is None:
+            return self
+        # If instance is there the caller wants to execute the
+        # install phase, thus return a properly set wrapper
+        phase = getattr(instance, self.name)
+
+        @functools.wraps(phase)
+        def phase_wrapper(spec, prefix):
+            # Check instance attributes at the beginning of a phase
+            self._on_phase_start(instance)
+            # Execute phase pre-conditions,
+            # and give them the chance to fail
+            for check in self.preconditions:
+                # Do something sensible at some point
+                check(instance)
+            phase(spec, prefix)
+            # Execute phase sanity_checks,
+            # and give them the chance to fail
+            for check in self.sanity_checks:
+                check(instance)
+            # Check instance attributes at the end of a phase
+            self._on_phase_exit(instance)
+        return phase_wrapper
+
+    def _on_phase_start(self, instance):
+        pass
+
+    def _on_phase_exit(self, instance):
+        # If a phase has a matching last_phase attribute,
+        # stop the installation process raising a StopIteration
+        if getattr(instance, 'last_phase', None) == self.name:
+            raise StopIteration('Stopping at \'{0}\' phase'.format(self.name))
+
+    def copy(self):
+        try:
+            return copy.deepcopy(self)
+        except TypeError:
+            # This bug-fix was not back-ported in Python 2.6
+            # http://bugs.python.org/issue1515
+            other = InstallPhase(self.name)
+            other.preconditions.extend(self.preconditions)
+            other.sanity_checks.extend(self.sanity_checks)
+            return other
+
+
+class PackageMeta(type):
+    """Conveniently transforms attributes to permit extensible phases
+
+    Iterates over the attribute 'phases' and creates / updates private
+    InstallPhase attributes in the class that is being initialized
+    """
+    phase_fmt = '_InstallPhase_{0}'
+
+    _InstallPhase_sanity_checks = {}
+    _InstallPhase_preconditions = {}
+
+    def __new__(meta, name, bases, attr_dict):
+        # Check if phases is in attr dict, then set
+        # install phases wrappers
+        if 'phases' in attr_dict:
+            _InstallPhase_phases = [PackageMeta.phase_fmt.format(x) for x in attr_dict['phases']]  # NOQA: ignore=E501
+            for phase_name, callback_name in zip(_InstallPhase_phases, attr_dict['phases']):  # NOQA: ignore=E501
+                attr_dict[phase_name] = InstallPhase(callback_name)
+            attr_dict['_InstallPhase_phases'] = _InstallPhase_phases
+
+        def _append_checks(check_name):
+            # Name of the attribute I am going to check it exists
+            attr_name = PackageMeta.phase_fmt.format(check_name)
+            checks = getattr(meta, attr_name)
+            if checks:
+                for phase_name, funcs in checks.items():
+                    try:
+                        # Search for the phase in the attribute dictionary
+                        phase = attr_dict[
+                            PackageMeta.phase_fmt.format(phase_name)]
+                    except KeyError:
+                        # If it is not there it's in the bases
+                        # and we added a check. We need to copy
+                        # and extend
+                        for base in bases:
+                            phase = getattr(
+                                base,
+                                PackageMeta.phase_fmt.format(phase_name),
+                                None
+                            )
+                        attr_dict[PackageMeta.phase_fmt.format(
+                            phase_name)] = phase.copy()
+                        phase = attr_dict[
+                            PackageMeta.phase_fmt.format(phase_name)]
+                    getattr(phase, check_name).extend(funcs)
+                # Clear the attribute for the next class
+                setattr(meta, attr_name, {})
+
+        @classmethod
+        def _register_checks(cls, check_type, *args):
+            def _register_sanity_checks(func):
+                attr_name = PackageMeta.phase_fmt.format(check_type)
+                check_list = getattr(meta, attr_name)
+                for item in args:
+                    checks = check_list.setdefault(item, [])
+                    checks.append(func)
+                setattr(meta, attr_name, check_list)
+                return func
+            return _register_sanity_checks
+
+        @staticmethod
+        def on_package_attributes(**attrs):
+            def _execute_under_condition(func):
+                @functools.wraps(func)
+                def _wrapper(instance):
+                    # If all the attributes have the value we require, then
+                    # execute
+                    if all([getattr(instance, key, None) == value for key, value in attrs.items()]):  # NOQA: ignore=E501
+                        func(instance)
+                return _wrapper
+            return _execute_under_condition
+
+        @classmethod
+        def precondition(cls, *args):
+            return cls._register_checks('preconditions', *args)
+
+        @classmethod
+        def sanity_check(cls, *args):
+            return cls._register_checks('sanity_checks', *args)
+
+        if all([not hasattr(x, '_register_checks') for x in bases]):
+            attr_dict['_register_checks'] = _register_checks
+
+        if all([not hasattr(x, 'sanity_check') for x in bases]):
+            attr_dict['sanity_check'] = sanity_check
+
+        if all([not hasattr(x, 'precondition') for x in bases]):
+            attr_dict['precondition'] = precondition
+
+        if all([not hasattr(x, 'on_package_attributes') for x in bases]):
+            attr_dict['on_package_attributes'] = on_package_attributes
+
+        # Preconditions
+        _append_checks('preconditions')
+        # Sanity checks
+        _append_checks('sanity_checks')
+        return super(PackageMeta, meta).__new__(meta, name, bases, attr_dict)
+
+    def __init__(cls, name, bases, dict):
+        type.__init__(cls, name, bases, dict)
+        spack.directives.ensure_dicts(cls)
+
+
+class PackageBase(object):
     """This is the superclass for all spack packages.
 
     ***The Package class***
@@ -309,7 +473,7 @@ class SomePackage(Package):
     Package creators override functions like install() (all of them do this),
     clean() (some of them do this), and others to provide custom behavior.
     """
-
+    __metaclass__ = PackageMeta
     #
     # These are default values for instance variables.
     #
@@ -344,12 +508,6 @@ class SomePackage(Package):
     """Per-process lock objects for each install prefix."""
     prefix_locks = {}
 
-    class __metaclass__(type):
-        """Ensure  attributes required by Spack directives are present."""
-        def __init__(cls, name, bases, dict):
-            type.__init__(cls, name, bases, dict)
-            spack.directives.ensure_dicts(cls)
-
     def __init__(self, spec):
         # this determines how the package should be built.
         self.spec = spec
@@ -429,6 +587,8 @@ def __init__(self, spec):
         if self.is_extension:
             spack.repo.get(self.extendee_spec)._check_extendable()
 
+        self.extra_args = {}
+
     def possible_dependencies(self, visited=None):
         """Return set of possible transitive dependencies of this package."""
         if visited is None:
@@ -886,12 +1046,34 @@ def namespace(self):
         return namespace
 
     def do_fake_install(self):
-        """Make a fake install directory contaiing a 'fake' file in bin."""
+        """Make a fake install directory containing a 'fake' file in bin."""
+        # FIXME : Make this part of the 'install' behavior ?
         mkdirp(self.prefix.bin)
         touch(join_path(self.prefix.bin, 'fake'))
         mkdirp(self.prefix.lib)
         mkdirp(self.prefix.man1)
 
+    def _if_make_target_execute(self, target):
+        try:
+            # Check if we have a makefile
+            file = [x for x in ('Makefile', 'makefile') if os.path.exists(x)]
+            file = file.pop()
+        except IndexError:
+            tty.msg('No Makefile found in the build directory')
+            return
+
+        # Check if 'target' is in the makefile
+        regex = re.compile('^' + target + ':')
+        with open(file, 'r') as f:
+            matches = [line for line in f.readlines() if regex.match(line)]
+
+        if not matches:
+            tty.msg('Target \'' + target + ':\' not found in Makefile')
+            return
+
+        # Execute target
+        inspect.getmodule(self).make(target)
+
     def _get_needed_resources(self):
         resources = []
         # Select the resources that are needed for this build
@@ -925,13 +1107,10 @@ def _prefix_write_lock(self):
         finally:
             self.prefix_lock.release_write()
 
-    install_phases = set(['configure', 'build', 'install', 'provenance'])
-
     def do_install(self,
                    keep_prefix=False,
                    keep_stage=False,
                    install_deps=True,
-                   install_self=True,
                    skip_patch=False,
                    verbose=False,
                    make_jobs=None,
@@ -939,7 +1118,7 @@ def do_install(self,
                    fake=False,
                    explicit=False,
                    dirty=False,
-                   install_phases=install_phases):
+                   **kwargs):
         """Called by commands to install a package and its dependencies.
 
         Package implementations should override install() to describe
@@ -975,9 +1154,7 @@ def do_install(self,
         # Ensure package is not already installed
         layout = spack.install_layout
         with self._prefix_read_lock():
-            if ('install' in install_phases and
-                layout.check_installed(self.spec)):
-
+            if layout.check_installed(self.spec):
                 tty.msg(
                     "%s is already installed in %s" % (self.name, self.prefix))
                 rec = spack.installed_db.get_record(self.spec)
@@ -987,6 +1164,8 @@ def do_install(self,
                         rec.explicit = True
                 return
 
+        self._do_install_pop_kwargs(kwargs)
+
         tty.msg("Installing %s" % self.name)
 
         # First, install dependencies recursively.
@@ -1009,7 +1188,6 @@ def do_install(self,
         # Set parallelism before starting build.
         self.make_jobs = make_jobs
 
-        # ------------------- BEGIN def build_process()
         # Then install the package itself.
         def build_process():
             """Forked for each build. Has its own process and python
@@ -1022,112 +1200,129 @@ def build_process():
                 else:
                     self.do_stage()
 
-            tty.msg("Building %s" % self.name)
+            tty.msg(
+                'Building {0} [{1}]'.format(self.name, self.build_system_class)
+            )
 
             self.stage.keep = keep_stage
-            self.install_phases = install_phases
-            self.build_directory = join_path(self.stage.path, 'spack-build')
-            self.source_directory = self.stage.source_path
 
-            with contextlib.nested(self.stage, self._prefix_write_lock()):
-                # Run the pre-install hook in the child process after
-                # the directory is created.
-                spack.hooks.pre_install(self)
-
-                if fake:
-                    self.do_fake_install()
-                else:
-                    # Do the real install in the source directory.
-                    self.stage.chdir_to_source()
-
-                    # Save the build environment in a file before building.
-                    env_path = join_path(os.getcwd(), 'spack-build.env')
-
-                    try:
+            try:
+                with contextlib.nested(self.stage, self._prefix_write_lock()):
+                    # Run the pre-install hook in the child process after
+                    # the directory is created.
+                    spack.hooks.pre_install(self)
+                    if fake:
+                        self.do_fake_install()
+                    else:
+                        # Do the real install in the source directory.
+                        self.stage.chdir_to_source()
+                        # Save the build environment in a file before building.
+                        env_path = join_path(os.getcwd(), 'spack-build.env')
                         # Redirect I/O to a build log (and optionally to
                         # the terminal)
                         log_path = join_path(os.getcwd(), 'spack-build.out')
-                        log_file = open(log_path, 'w')
-                        with log_output(log_file, verbose, sys.stdout.isatty(),
-                                        True):
-                            dump_environment(env_path)
-                            self.install(self.spec, self.prefix)
-
-                    except ProcessError as e:
-                        # Annotate ProcessErrors with the location of
-                        # the build log
-                        e.build_log = log_path
-                        raise e
-
-                    # Ensure that something was actually installed.
-                    if 'install' in self.install_phases:
-                        self.sanity_check_prefix()
-
-                    # Copy provenance into the install directory on success
-                    if 'provenance' in self.install_phases:
-                        log_install_path = layout.build_log_path(self.spec)
-                        env_install_path = layout.build_env_path(self.spec)
-                        packages_dir = layout.build_packages_path(self.spec)
-
-                        # Remove first if we're overwriting another build
-                        # (can happen with spack setup)
-                        try:
-                            # log_install_path and env_install_path are here
-                            shutil.rmtree(packages_dir)
-                        except:
-                            pass
-
-                        install(log_path, log_install_path)
-                        install(env_path, env_install_path)
-                        dump_packages(self.spec, packages_dir)
-
-                # Run post install hooks before build stage is removed.
-                spack.hooks.post_install(self)
-
-            # Stop timer.
-            self._total_time = time.time() - start_time
-            build_time = self._total_time - self._fetch_time
-
-            tty.msg("Successfully installed %s" % self.name,
-                    "Fetch: %s.  Build: %s.  Total: %s." %
-                    (_hms(self._fetch_time), _hms(build_time),
-                     _hms(self._total_time)))
-            print_pkg(self.prefix)
-        # ------------------- END def build_process()
+                        # FIXME : refactor this assignment
+                        self.log_path = log_path
+                        self.env_path = env_path
+                        dump_environment(env_path)
+                        # Spawn a daemon that reads from a pipe and redirects
+                        # everything to log_path
+                        redirection_context = log_output(
+                            log_path, verbose,
+                            sys.stdout.isatty(),
+                            True
+                        )
+                        with redirection_context as log_redirection:
+                            for phase_name, phase in zip(self.phases, self._InstallPhase_phases):  # NOQA: ignore=E501
+                                tty.msg(
+                                    'Executing phase : \'{0}\''.format(phase_name)  # NOQA: ignore=E501
+                                )
+                                # Redirect stdout and stderr to daemon pipe
+                                with log_redirection:
+                                    getattr(self, phase)(
+                                        self.spec, self.prefix)
+                        self.log()
+                    # Run post install hooks before build stage is removed.
+                    spack.hooks.post_install(self)
+
+                # Stop timer.
+                self._total_time = time.time() - start_time
+                build_time = self._total_time - self._fetch_time
+
+                tty.msg("Successfully installed %s" % self.name,
+                        "Fetch: %s.  Build: %s.  Total: %s." %
+                        (_hms(self._fetch_time), _hms(build_time),
+                         _hms(self._total_time)))
+                print_pkg(self.prefix)
+
+            except ProcessError as e:
+                # Annotate ProcessErrors with the location of
+                # the build log
+                e.build_log = log_path
+                raise e
 
         try:
             # Create the install prefix and fork the build process.
             spack.install_layout.create_install_directory(self.spec)
-        except directory_layout.InstallDirectoryAlreadyExistsError:
-            if 'install' in install_phases:
-                # Abort install if install directory exists.
-                # But do NOT remove it (you'd be overwriting someone's data)
-                tty.warn("Keeping existing install prefix in place.")
-                raise
-            else:
-                # We're not installing anyway, so don't worry if someone
-                # else has already written in the install directory
-                pass
-
-        try:
+            # Fork a child to do the actual installation
             spack.build_environment.fork(self, build_process, dirty=dirty)
-        except:
-            # remove the install prefix if anything went wrong during install.
+            # If we installed then we should keep the prefix
+            keep_prefix = True if self.last_phase is None else keep_prefix
+            # note: PARENT of the build process adds the new package to
+            # the database, so that we don't need to re-read from file.
+            spack.installed_db.add(
+                self.spec, spack.install_layout, explicit=explicit
+            )
+        except directory_layout.InstallDirectoryAlreadyExistsError:
+            # Abort install if install directory exists.
+            # But do NOT remove it (you'd be overwriting someone else's stuff)
+            tty.warn("Keeping existing install prefix in place.")
+            raise
+        except StopIteration as e:
+            # A StopIteration exception means that do_install
+            # was asked to stop early from clients
+            tty.msg(e.message)
+            tty.msg(
+                'Package stage directory : {0}'.format(self.stage.source_path)
+            )
+        finally:
+            # Remove the install prefix if anything went wrong during install.
             if not keep_prefix:
                 self.remove_prefix()
-            else:
-                tty.warn("Keeping install prefix in place despite error.",
-                         "Spack will think this package is installed. " +
-                         "Manually remove this directory to fix:",
-                         self.prefix,
-                         wrap=False)
-            raise
 
-        # Parent of the build process adds the new package to
-        # the database, so that we don't need to re-read from file.
-        # NOTE: add() implicitly acquires a write-lock
-        spack.installed_db.add(
-            self.spec, spack.install_layout, explicit=explicit)
+    def _do_install_pop_kwargs(self, kwargs):
+        """Pops kwargs from do_install before starting the installation
+
+        Args:
+            kwargs:
+              'stop_at': last installation phase to be executed (or None)
+
+        """
+        self.last_phase = kwargs.pop('stop_at', None)
+        if self.last_phase is not None and self.last_phase not in self.phases:
+            tty.die('\'{0.last_phase}\' is not among the allowed phases for package {0.name}'.format(self))  # NOQA: ignore=E501
+
+    def log(self):
+        # Copy provenance into the install directory on success
+        log_install_path = spack.install_layout.build_log_path(
+            self.spec)
+        env_install_path = spack.install_layout.build_env_path(
+            self.spec)
+        packages_dir = spack.install_layout.build_packages_path(
+            self.spec)
+
+        # Remove first if we're overwriting another build
+        # (can happen with spack setup)
+        try:
+            # log_install_path and env_install_path are inside this
+            shutil.rmtree(packages_dir)
+        except Exception:
+            # FIXME : this potentially catches too many things...
+            pass
+
+        install(self.log_path, log_install_path)
+        install(self.env_path, env_install_path)
+        dump_packages(self.spec, packages_dir)
 
     def sanity_check_prefix(self):
         """This function checks whether install succeeded."""
@@ -1281,13 +1476,6 @@ def setup_dependent_package(self, module, dependent_spec):
         """
         pass
 
-    def install(self, spec, prefix):
-        """
-        Package implementations override this with their own configuration
-        """
-        raise InstallError("Package %s provides no install method!" %
-                           self.name)
-
     def do_uninstall(self, force=False):
         if not self.installed:
             # prefix may not exist, but DB may be inconsistent. Try to fix by
@@ -1498,6 +1686,16 @@ def rpath_args(self):
         return " ".join("-Wl,-rpath,%s" % p for p in self.rpath)
 
 
+class Package(PackageBase):
+    phases = ['install']
+    # To be used in UI queries that require to know which
+    # build-system class we are using
+    build_system_class = 'Package'
+    # This will be used as a registration decorator in user
+    # packages, if need be
+    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+
+
 def install_dependency_symlinks(pkg, spec, prefix):
     """Execute a dummy install and flatten dependencies"""
     flatten_dependencies(spec, prefix)
@@ -1598,166 +1796,6 @@ def _hms(seconds):
     return ' '.join(parts)
 
 
-class StagedPackage(Package):
-    """A Package subclass where the install() is split up into stages."""
-
-    def install_setup(self):
-        """Creates a spack_setup.py script to configure the package later."""
-        raise InstallError(
-            "Package %s provides no install_setup() method!" % self.name)
-
-    def install_configure(self):
-        """Runs the configure process."""
-        raise InstallError(
-            "Package %s provides no install_configure() method!" % self.name)
-
-    def install_build(self):
-        """Runs the build process."""
-        raise InstallError(
-            "Package %s provides no install_build() method!" % self.name)
-
-    def install_install(self):
-        """Runs the install process."""
-        raise InstallError(
-            "Package %s provides no install_install() method!" % self.name)
-
-    def install(self, spec, prefix):
-        if 'setup' in self.install_phases:
-            self.install_setup()
-
-        if 'configure' in self.install_phases:
-            self.install_configure()
-
-        if 'build' in self.install_phases:
-            self.install_build()
-
-        if 'install' in self.install_phases:
-            self.install_install()
-        else:
-            # Create a dummy file so the build doesn't fail.
-            # That way, the module file will also be created.
-            with open(os.path.join(prefix, 'dummy'), 'w'):
-                pass
-
-
-# stackoverflow.com/questions/12791997/how-do-you-do-a-simple-chmod-x-from-within-python
-def make_executable(path):
-    mode = os.stat(path).st_mode
-    mode |= (mode & 0o444) >> 2    # copy R bits to X
-    os.chmod(path, mode)
-
-
-class CMakePackage(StagedPackage):
-
-    def make_make(self):
-        import multiprocessing
-        # number of jobs spack will to build with.
-        jobs = multiprocessing.cpu_count()
-        if not self.parallel:
-            jobs = 1
-        elif self.make_jobs:
-            jobs = self.make_jobs
-
-        make  = spack.build_environment.MakeExecutable('make', jobs)
-        return make
-
-    def configure_args(self):
-        """Returns package-specific arguments to be provided to
-           the configure command.
-        """
-        return list()
-
-    def configure_env(self):
-        """Returns package-specific environment under which the
-           configure command should be run.
-        """
-        return dict()
-
-    def transitive_inc_path(self):
-        return ';'.join(
-            os.path.join(dep, 'include')
-            for dep in os.environ['SPACK_DEPENDENCIES'].split(os.pathsep)
-        )
-
-    def install_setup(self):
-        cmd = [str(which('cmake'))]
-        cmd += spack.build_environment.get_std_cmake_args(self)
-        cmd += ['-DCMAKE_INSTALL_PREFIX=%s' % os.environ['SPACK_PREFIX'],
-                '-DCMAKE_C_COMPILER=%s' % os.environ['SPACK_CC'],
-                '-DCMAKE_CXX_COMPILER=%s' % os.environ['SPACK_CXX'],
-                '-DCMAKE_Fortran_COMPILER=%s' % os.environ['SPACK_FC']]
-        cmd += self.configure_args()
-
-        env = {
-            'PATH': os.environ['PATH'],
-            'SPACK_TRANSITIVE_INCLUDE_PATH': self.transitive_inc_path(),
-            'CMAKE_PREFIX_PATH': os.environ['CMAKE_PREFIX_PATH']
-        }
-
-        setup_fname = 'spconfig.py'
-        with open(setup_fname, 'w') as fout:
-            fout.write(r"""#!%s
-#
-
-import sys
-import os
-import subprocess
-
-def cmdlist(str):
-    return list(x.strip().replace("'",'') for x in str.split('\n') if x)
-env = dict(os.environ)
-""" % sys.executable)
-
-            env_vars = sorted(list(env.keys()))
-            for name in env_vars:
-                val = env[name]
-                if string.find(name, 'PATH') < 0:
-                    fout.write('env[%s] = %s\n' % (repr(name), repr(val)))
-                else:
-                    if name == 'SPACK_TRANSITIVE_INCLUDE_PATH':
-                        sep = ';'
-                    else:
-                        sep = ':'
-
-                    fout.write('env[%s] = "%s".join(cmdlist("""\n'
-                               % (repr(name), sep))
-                    for part in string.split(val, sep):
-                        fout.write('    %s\n' % part)
-                    fout.write('"""))\n')
-
-            fout.write("env['CMAKE_TRANSITIVE_INCLUDE_PATH'] = "
-                       "env['SPACK_TRANSITIVE_INCLUDE_PATH']   # Deprecated\n")
-            fout.write('\ncmd = cmdlist("""\n')
-            fout.write('%s\n' % cmd[0])
-            for arg in cmd[1:]:
-                fout.write('    %s\n' % arg)
-            fout.write('""") + sys.argv[1:]\n')
-            fout.write('\nproc = subprocess.Popen(cmd, env=env)\n')
-            fout.write('proc.wait()\n')
-        make_executable(setup_fname)
-
-    def install_configure(self):
-        cmake = which('cmake')
-        with working_dir(self.build_directory, create=True):
-            env = os.environ
-            env.update(self.configure_env())
-            env['SPACK_TRANSITIVE_INCLUDE_PATH'] = self.transitive_inc_path()
-
-            options = self.configure_args()
-            options += spack.build_environment.get_std_cmake_args(self)
-            cmake(self.source_directory, *options)
-
-    def install_build(self):
-        make = self.make_make()
-        with working_dir(self.build_directory, create=False):
-            make()
-
-    def install_install(self):
-        make = self.make_make()
-        with working_dir(self.build_directory, create=False):
-            make('install')
-
-
 class FetchError(spack.error.SpackError):
     """Raised when something goes wrong during fetch."""
 
diff --git a/var/spack/repos/builtin/packages/astyle/package.py b/var/spack/repos/builtin/packages/astyle/package.py
index eecdcfdad7c456b415d756ea3191b9911769a794..31e1efb5915a51670e471e047ddb2f567246b968 100644
--- a/var/spack/repos/builtin/packages/astyle/package.py
+++ b/var/spack/repos/builtin/packages/astyle/package.py
@@ -25,28 +25,24 @@
 from spack import *
 
 
-class Astyle(Package):
+class Astyle(MakefilePackage):
     """A Free, Fast, and Small Automatic Formatter for C, C++, C++/CLI,
     Objective-C, C#, and Java Source Code.
     """
 
     homepage = "http://astyle.sourceforge.net/"
-    url      = "http://downloads.sourceforge.net/project/astyle/astyle/astyle%202.04/astyle_2.04_linux.tar.gz"
+    url = "http://downloads.sourceforge.net/project/astyle/astyle/astyle%202.04/astyle_2.04_linux.tar.gz"
 
     version('2.04', '30b1193a758b0909d06e7ee8dd9627f6')
 
-    def install(self, spec, prefix):
+    parallel = False
 
-        with working_dir('src'):
-            # we need to edit the makefile in place to set compiler:
-            make_file = join_path(self.stage.source_path,
-                                  'build', 'gcc', 'Makefile')
-            filter_file(r'^CXX\s*=.*', 'CXX=%s' % spack_cxx, make_file)
+    def build_directory(self):
+        return join_path(self.stage.source_path, 'build', self.compiler.name)
 
-            make('-f',
-                 make_file,
-                 parallel=False)
+    def edit(self, spec, prefix):
+        makefile = join_path(self.build_directory(), 'Makefile')
+        filter_file(r'^CXX\s*=.*', 'CXX=%s' % spack_cxx, makefile)
 
-            mkdirp(self.prefix.bin)
-            install(join_path(self.stage.source_path, 'src', 'bin', 'astyle'),
-                    self.prefix.bin)
+    def install_args(self):
+        return ['prefix={0}'.format(prefix)]
diff --git a/var/spack/repos/builtin/packages/autoconf/package.py b/var/spack/repos/builtin/packages/autoconf/package.py
index 5eb7f3347b797019cd5c2b4bbe203cdc078adafd..b6aba8c03f61dfe0b6a431680edde523565edbac 100644
--- a/var/spack/repos/builtin/packages/autoconf/package.py
+++ b/var/spack/repos/builtin/packages/autoconf/package.py
@@ -25,10 +25,8 @@
 from spack import *
 
 
-class Autoconf(Package):
-    """
-    Autoconf -- system configuration part of autotools
-    """
+class Autoconf(AutotoolsPackage):
+    """Autoconf -- system configuration part of autotools"""
     homepage = 'https://www.gnu.org/software/autoconf/'
     url = 'http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz'
 
@@ -54,8 +52,3 @@ def setup_dependent_package(self, module, dependent_spec):
                        'ifnames']
         for name in executables:
             setattr(module, name, self._make_executable(name))
-
-    def install(self, spec, prefix):
-        configure("--prefix=%s" % prefix)
-        make()
-        make("install")
diff --git a/var/spack/repos/builtin/packages/blitz/package.py b/var/spack/repos/builtin/packages/blitz/package.py
index c92e49a7324a9268050b5ebe143a8ea1eb19cb83..16ad3bc2ab7ba03415e2a693d68c071a3175a159 100644
--- a/var/spack/repos/builtin/packages/blitz/package.py
+++ b/var/spack/repos/builtin/packages/blitz/package.py
@@ -25,16 +25,9 @@
 from spack import *
 
 
-class Blitz(Package):
+class Blitz(AutotoolsPackage):
     """N-dimensional arrays for C++"""
     homepage = "http://github.com/blitzpp/blitz"
-    url      = "https://github.com/blitzpp/blitz/tarball/1.0.0"
+    url = "https://github.com/blitzpp/blitz/tarball/1.0.0"
 
     version('1.0.0', '9f040b9827fe22228a892603671a77af')
-
-    # No dependencies
-
-    def install(self, spec, prefix):
-        configure('--prefix=%s' % prefix)
-        make()
-        make("install")
diff --git a/var/spack/repos/builtin/packages/gmp/package.py b/var/spack/repos/builtin/packages/gmp/package.py
index 14ce08473dc83248c865e3ad624a8029a62f72d9..dcabbff67eadc352e801381defdbe90c459a1250 100644
--- a/var/spack/repos/builtin/packages/gmp/package.py
+++ b/var/spack/repos/builtin/packages/gmp/package.py
@@ -25,12 +25,12 @@
 from spack import *
 
 
-class Gmp(Package):
-    """GMP is a free library for arbitrary precision arithmetic, operating
-    on signed integers, rational numbers, and floating-point numbers."""
-
+class Gmp(AutotoolsPackage):
+    """GMP is a free library for arbitrary precision arithmetic,
+       operating on signed integers, rational numbers, and
+       floating-point numbers."""
     homepage = "https://gmplib.org"
-    url      = "https://gmplib.org/download/gmp/gmp-6.0.0a.tar.bz2"
+    url = "https://gmplib.org/download/gmp/gmp-6.0.0a.tar.bz2"
 
     version('6.1.1',  '4c175f86e11eb32d8bf9872ca3a8e11d')
     version('6.1.0',  '86ee6e54ebfc4a90b643a65e402c4048')
@@ -39,16 +39,10 @@ class Gmp(Package):
 
     depends_on('m4', type='build')
 
-    def install(self, spec, prefix):
-        config_args = ['--prefix=' + prefix,
-                       '--enable-cxx']
-
+    def configure_args(self):
+        args = ['--enable-cxx']
         # We need this flag if we want all the following checks to pass.
         if spec.compiler.name == 'intel':
-            config_args.append('CXXFLAGS=-no-ftz')
-
-        configure(*config_args)
+            args.append('CXXFLAGS=-no-ftz')
 
-        make()
-        make('check')
-        make('install')
+        return args
diff --git a/var/spack/repos/builtin/packages/hdf5/package.py b/var/spack/repos/builtin/packages/hdf5/package.py
index f2fe48feb31fe466a7b9abf0710425a6886213a6..cbb7501034b31fc78ea8986192bfb9750a498a0f 100644
--- a/var/spack/repos/builtin/packages/hdf5/package.py
+++ b/var/spack/repos/builtin/packages/hdf5/package.py
@@ -27,10 +27,10 @@
 import shutil
 
 
-class Hdf5(Package):
+class Hdf5(AutotoolsPackage):
     """HDF5 is a data model, library, and file format for storing and managing
-       data. It supports an unlimited variety of datatypes, and is designed for
-       flexible and efficient I/O and for high volume and complex data.
+    data. It supports an unlimited variety of datatypes, and is designed for
+    flexible and efficient I/O and for high volume and complex data.
     """
 
     homepage = "http://www.hdfgroup.org/HDF5/"
@@ -58,17 +58,19 @@ class Hdf5(Package):
     variant('threadsafe', default=False,
             description='Enable thread-safe capabilities')
 
-    depends_on("mpi", when='+mpi')
-    depends_on("szip", when='+szip')
-    depends_on("zlib@1.1.2:")
+    depends_on('mpi', when='+mpi')
+    depends_on('szip', when='+szip')
+    depends_on('zlib@1.1.2:')
 
-    def validate(self, spec):
+    @AutotoolsPackage.precondition('configure')
+    def validate(self):
         """
         Checks if incompatible variants have been activated at the same time
 
         :param spec: spec of the package
         :raises RuntimeError: in case of inconsistencies
         """
+        spec = self.spec
         if '+fortran' in spec and not self.compiler.fc:
             msg = 'cannot build a fortran variant without a fortran compiler'
             raise RuntimeError(msg)
@@ -77,8 +79,8 @@ def validate(self, spec):
             msg = 'cannot use variant +threadsafe with either +cxx or +fortran'
             raise RuntimeError(msg)
 
-    def install(self, spec, prefix):
-        self.validate(spec)
+    def configure_args(self):
+        spec = self.spec
         # Handle compilation after spec validation
         extra_args = []
 
@@ -139,21 +141,13 @@ def install(self, spec, prefix):
                 '--disable-hl',
             ])
 
-        configure(
-            "--prefix=%s" % prefix,
-            "--with-zlib=%s" % spec['zlib'].prefix,
-            *extra_args)
-        make()
+        return ["--with-zlib=%s" % spec['zlib'].prefix] + extra_args
 
-        if self.run_tests:
-            make("check")
-
-        make("install")
-        self.check_install(spec)
-
-    def check_install(self, spec):
-        "Build and run a small program to test the installed HDF5 library"
-        print "Checking HDF5 installation..."
+    def check(self):
+        super(Hdf5, self).check()
+        # Build and run a small program to test the installed HDF5 library
+        spec = self.spec
+        print("Checking HDF5 installation...")
         checkdir = "spack-check"
         with working_dir(checkdir, create=True):
             source = r"""
@@ -190,15 +184,15 @@ def check_install(self, spec):
                 output = ""
             success = output == expected
             if not success:
-                print "Produced output does not match expected output."
-                print "Expected output:"
-                print '-' * 80
-                print expected
-                print '-' * 80
-                print "Produced output:"
-                print '-' * 80
-                print output
-                print '-' * 80
+                print("Produced output does not match expected output.")
+                print("Expected output:")
+                print('-' * 80)
+                print(expected)
+                print('-' * 80)
+                print("Produced output:")
+                print('-' * 80)
+                print(output)
+                print('-' * 80)
                 raise RuntimeError("HDF5 install check failed")
         shutil.rmtree(checkdir)
 
diff --git a/var/spack/repos/builtin/packages/ibmisc/package.py b/var/spack/repos/builtin/packages/ibmisc/package.py
index 736886df96fd0db0cb639c06537f70142b4ece84..172204a75bbcac7ec64d6ed28b1e264960beacf8 100644
--- a/var/spack/repos/builtin/packages/ibmisc/package.py
+++ b/var/spack/repos/builtin/packages/ibmisc/package.py
@@ -43,7 +43,7 @@ class Ibmisc(CMakePackage):
     depends_on('cmake', type='build')
     depends_on('doxygen', type='build')
 
-    def configure_args(self):
+    def cmake_args(self):
         spec = self.spec
         return [
             '-DUSE_EVERYTRACE=%s' % ('YES' if '+everytrace' in spec else 'NO'),
diff --git a/var/spack/repos/builtin/packages/lzo/package.py b/var/spack/repos/builtin/packages/lzo/package.py
index 1c44ced4d40cf7e3e7f2b7d1e1248670289b76a1..e9c98842f4e3a2dd6734b13c5971e24d35ed2c0b 100644
--- a/var/spack/repos/builtin/packages/lzo/package.py
+++ b/var/spack/repos/builtin/packages/lzo/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class Lzo(Package):
+class Lzo(AutotoolsPackage):
     """Real-time data compression library"""
 
     homepage = 'https://www.oberhumer.com/opensource/lzo/'
@@ -37,15 +37,8 @@ class Lzo(Package):
     version('2.06', '95380bd4081f85ef08c5209f4107e9f8')
     version('2.05', 'c67cda5fa191bab761c7cb06fe091e36')
 
-    def install(self, spec, prefix):
-        configure_args = [
-            '--prefix={0}'.format(prefix),
+    def configure_args(self):
+        return [
             '--disable-dependency-tracking',
             '--enable-shared'
         ]
-        configure(*configure_args)
-        make()
-        if self.run_tests:
-            make('check')
-            make('test')  # more exhaustive test
-        make('install')
diff --git a/var/spack/repos/builtin/packages/openjpeg/package.py b/var/spack/repos/builtin/packages/openjpeg/package.py
index fc505e19a9e2a645fd23b42ac9b757db2d34afba..988ebc155d7e5176e9cf96da3f065928a763343f 100644
--- a/var/spack/repos/builtin/packages/openjpeg/package.py
+++ b/var/spack/repos/builtin/packages/openjpeg/package.py
@@ -25,8 +25,9 @@
 from spack import *
 
 
-class Openjpeg(Package):
+class Openjpeg(CMakePackage):
     """OpenJPEG is an open-source JPEG 2000 codec written in C language.
+
     It has been developed in order to promote the use of JPEG 2000, a
     still-image compression standard from the Joint Photographic
     Experts Group (JPEG).
@@ -35,7 +36,7 @@ class Openjpeg(Package):
     """
 
     homepage = "https://github.com/uclouvain/openjpeg"
-    url      = "https://github.com/uclouvain/openjpeg/archive/version.2.1.tar.gz"
+    url = "https://github.com/uclouvain/openjpeg/archive/version.2.1.tar.gz"
 
     version('2.1',   '3e1c451c087f8462955426da38aa3b3d')
     version('2.0.1', '105876ed43ff7dbb2f90b41b5a43cfa5')
@@ -44,9 +45,3 @@ class Openjpeg(Package):
     version('1.5.1', 'd774e4b5a0db5f0f171c4fc0aabfa14e')
 
     depends_on('cmake', type='build')
-
-    def install(self, spec, prefix):
-        cmake('.', *std_cmake_args)
-
-        make()
-        make("install")
diff --git a/var/spack/repos/builtin/packages/qhull/package.py b/var/spack/repos/builtin/packages/qhull/package.py
index 462a681ad997cf86132ea37d86b9c2ef26c1a500..4456c16bd2bad8b420fc8c80ffe6fe305a67bbf8 100644
--- a/var/spack/repos/builtin/packages/qhull/package.py
+++ b/var/spack/repos/builtin/packages/qhull/package.py
@@ -25,7 +25,7 @@
 from spack import *
 
 
-class Qhull(Package):
+class Qhull(CMakePackage):
     """Qhull computes the convex hull, Delaunay triangulation, Voronoi
        diagram, halfspace intersection about a point, furt hest-site
        Delaunay triangulation, and furthest-site Voronoi diagram. The
@@ -44,9 +44,3 @@ class Qhull(Package):
             url="http://www.qhull.org/download/qhull-2012.1-src.tgz")
 
     depends_on('cmake@2.6:', type='build')
-
-    def install(self, spec, prefix):
-        with working_dir('spack-build', create=True):
-            cmake('..', *std_cmake_args)
-            make()
-            make("install")
diff --git a/var/spack/repos/builtin/packages/swiftsim/package.py b/var/spack/repos/builtin/packages/swiftsim/package.py
index c591c48d19d1212c99f0debb8f5db84d2edc7a84..1c424b5ca0e975d31d8acaa19fc32f06727042c7 100644
--- a/var/spack/repos/builtin/packages/swiftsim/package.py
+++ b/var/spack/repos/builtin/packages/swiftsim/package.py
@@ -26,7 +26,7 @@
 import llnl.util.tty as tty
 
 
-class Swiftsim(Package):
+class Swiftsim(AutotoolsPackage):
     """SPH With Inter-dependent Fine-grained Tasking (SWIFT) provides
     astrophysicists with a state of the art framework to perform
     particle based simulations.
@@ -58,20 +58,15 @@ def setup_environment(self, spack_env, run_env):
         tty.warn('This is needed to clone SWIFT repository')
         spack_env.set('GIT_SSL_NO_VERIFY', 1)
 
-    def install(self, spec, prefix):
-        # Generate configure from configure.ac
-        # and Makefile.am
+    def autoreconf(self, spec, prefix):
         libtoolize()
         aclocal()
         autoconf()
         autogen = Executable('./autogen.sh')
         autogen()
 
-        # Configure and install
-        options = ['--prefix=%s' % prefix,
-                   '--enable-mpi' if '+mpi' in spec else '--disable-mpi',
-                   '--with-metis={0}'.format(spec['metis'].prefix),
-                   '--enable-optimization']
-        configure(*options)
-        make()
-        make("install")
+    def configure_args(self):
+        return ['--prefix=%s' % self.prefix,
+                '--enable-mpi' if '+mpi' in self.spec else '--disable-mpi',
+                '--with-metis={0}'.format(self.spec['metis'].prefix),
+                '--enable-optimization']
diff --git a/var/spack/repos/builtin/packages/szip/package.py b/var/spack/repos/builtin/packages/szip/package.py
index b2ca6f3995d7a2c4eab4aa7a9e1ad13d820582e5..91934f7d039e7e91354d550d4b195fe1a703664d 100644
--- a/var/spack/repos/builtin/packages/szip/package.py
+++ b/var/spack/repos/builtin/packages/szip/package.py
@@ -25,24 +25,21 @@
 from spack import *
 
 
-class Szip(Package):
-    """An implementation of the extended-Rice lossless compression algorithm.
-    It provides lossless compression of scientific data, and is provided
-    with HDF software products.
+class Szip(AutotoolsPackage):
+    """Szip is an implementation of the extended-Rice lossless
+     compression algorithm.
 
+    It provides lossless compression of scientific data, and is
+    provided with HDF software products.
     """
 
     homepage = "https://www.hdfgroup.org/doc_resource/SZIP/"
-    url      = "http://www.hdfgroup.org/ftp/lib-external/szip/2.1/src/szip-2.1.tar.gz"
+    url = "http://www.hdfgroup.org/ftp/lib-external/szip/2.1/src/szip-2.1.tar.gz"
 
     version('2.1', '902f831bcefb69c6b635374424acbead')
 
-    def install(self, spec, prefix):
-        configure('--prefix=%s' % prefix,
-                  '--enable-production',
-                  '--enable-shared',
-                  '--enable-static',
-                  '--enable-encoding')
-
-        make()
-        make("install")
+    def configure_args(self):
+        return ['--enable-production',
+                '--enable-shared',
+                '--enable-static',
+                '--enable-encoding']