diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 00e4dc5f3731477952321289704a845706a6842e..f5017f5236bb8938498b1f6358482b0716765693 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -703,15 +703,32 @@ def set_executable(path):
     os.chmod(path, mode)
 
 
+def remove_empty_directories(root):
+    """Ascend up from the leaves accessible from `root` and remove empty
+    directories.
+
+    Parameters:
+        root (str): path where to search for empty directories
+    """
+    for dirpath, subdirs, files in os.walk(root, topdown=False):
+        for sd in subdirs:
+            sdp = os.path.join(dirpath, sd)
+            try:
+                os.rmdir(sdp)
+            except OSError:
+                pass
+
+
 def remove_dead_links(root):
-    """Removes any dead link that is present in root.
+    """Recursively removes any dead link that is present in root.
 
     Parameters:
         root (str): path where to search for dead links
     """
-    for file in os.listdir(root):
-        path = join_path(root, file)
-        remove_if_dead_link(path)
+    for dirpath, subdirs, files in os.walk(root, topdown=False):
+        for f in files:
+            path = join_path(dirpath, f)
+            remove_if_dead_link(path)
 
 
 def remove_if_dead_link(path):
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index e8ac8a5c861f1dedec768f7a22a9344a1ca8063e..1b85849e8fd507a38a350773cdeb5d941188cd8f 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -5,6 +5,7 @@
 
 import os
 import sys
+from collections import namedtuple
 
 import llnl.util.tty as tty
 import llnl.util.filesystem as fs
@@ -20,6 +21,7 @@
 import spack.environment as ev
 import spack.util.string as string
 
+
 description = "manage virtual environments"
 section = "environments"
 level = "short"
@@ -34,6 +36,7 @@
     ['list', 'ls'],
     ['status', 'st'],
     'loads',
+    'view',
 ]
 
 
@@ -49,10 +52,20 @@ def env_activate_setup_parser(subparser):
     shells.add_argument(
         '--csh', action='store_const', dest='shell', const='csh',
         help="print csh commands to activate the environment")
-    shells.add_argument(
+
+    view_options = subparser.add_mutually_exclusive_group()
+    view_options.add_argument(
+        '-v', '--with-view', action='store_const', dest='with_view',
+        const=True, default=True,
+        help="update PATH etc. with associated view")
+    view_options.add_argument(
+        '-V', '--without-view', action='store_const', dest='with_view',
+        const=False, default=True,
+        help="do not update PATH etc. with associated view")
+
+    subparser.add_argument(
         '-d', '--dir', action='store_true', default=False,
         help="force spack to treat env as a directory, not a name")
-
     subparser.add_argument(
         '-p', '--prompt', action='store_true', default=False,
         help="decorate the command line prompt when activating")
@@ -93,25 +106,13 @@ def env_activate(args):
     if spack_env == os.environ.get('SPACK_ENV'):
         tty.die("Environment %s is already active" % args.activate_env)
 
-    if args.shell == 'csh':
-        # TODO: figure out how to make color work for csh
-        sys.stdout.write('setenv SPACK_ENV %s;\n' % spack_env)
-        sys.stdout.write('alias despacktivate "spack env deactivate";\n')
-        if args.prompt:
-            sys.stdout.write('if (! $?SPACK_OLD_PROMPT ) '
-                             'setenv SPACK_OLD_PROMPT "${prompt}";\n')
-            sys.stdout.write('set prompt="%s ${prompt}";\n' % env_prompt)
-
-    else:
-        if 'color' in os.environ['TERM']:
-            env_prompt = colorize('@G{%s} ' % env_prompt, color=True)
-
-        sys.stdout.write('export SPACK_ENV=%s;\n' % spack_env)
-        sys.stdout.write("alias despacktivate='spack env deactivate';\n")
-        if args.prompt:
-            sys.stdout.write('if [ -z "${SPACK_OLD_PS1}" ]; then\n')
-            sys.stdout.write('export SPACK_OLD_PS1="${PS1}"; fi;\n')
-            sys.stdout.write('export PS1="%s ${PS1}";\n' % env_prompt)
+    active_env = ev.get_env(namedtuple('args', ['env'])(env),
+                            'activate')
+    cmds = ev.activate(
+        active_env, add_view=args.with_view, shell=args.shell,
+        prompt=env_prompt if args.prompt else None
+    )
+    sys.stdout.write(cmds)
 
 
 #
@@ -146,20 +147,8 @@ def env_deactivate(args):
     if 'SPACK_ENV' not in os.environ:
         tty.die('No environment is currently active.')
 
-    if args.shell == 'csh':
-        sys.stdout.write('unsetenv SPACK_ENV;\n')
-        sys.stdout.write('if ( $?SPACK_OLD_PROMPT ) '
-                         'set prompt="$SPACK_OLD_PROMPT" && '
-                         'unsetenv SPACK_OLD_PROMPT;\n')
-        sys.stdout.write('unalias despacktivate;\n')
-
-    else:
-        sys.stdout.write('unset SPACK_ENV; export SPACK_ENV;\n')
-        sys.stdout.write('unalias despacktivate;\n')
-        sys.stdout.write('if [ -n "$SPACK_OLD_PS1" ]; then\n')
-        sys.stdout.write('export PS1="$SPACK_OLD_PS1";\n')
-        sys.stdout.write('unset SPACK_OLD_PS1; export SPACK_OLD_PS1;\n')
-        sys.stdout.write('fi;\n')
+    cmds = ev.deactivate(shell=args.shell)
+    sys.stdout.write(cmds)
 
 
 #
@@ -172,20 +161,40 @@ def env_create_setup_parser(subparser):
     subparser.add_argument(
         '-d', '--dir', action='store_true',
         help='create an environment in a specific directory')
+    view_opts = subparser.add_mutually_exclusive_group()
+    view_opts.add_argument(
+        '--without-view', action='store_true',
+        help='do not maintain a view for this environment')
+    view_opts.add_argument(
+        '--with-view',
+        help='specify that this environment should maintain a view at the'
+             ' specified path (by default the view is maintained in the'
+             ' environment directory)')
     subparser.add_argument(
         'envfile', nargs='?', default=None,
         help='optional init file; can be spack.yaml or spack.lock')
 
 
 def env_create(args):
+    if args.with_view:
+        with_view = args.with_view
+    elif args.without_view:
+        with_view = False
+    else:
+        # Note that 'None' means unspecified, in which case the Environment
+        # object could choose to enable a view by default. False means that
+        # the environment should not include a view.
+        with_view = None
     if args.envfile:
         with open(args.envfile) as f:
-            _env_create(args.create_env, f, args.dir)
+            _env_create(args.create_env, f, args.dir,
+                        with_view=with_view)
     else:
-        _env_create(args.create_env, None, args.dir)
+        _env_create(args.create_env, None, args.dir,
+                    with_view=with_view)
 
 
-def _env_create(name_or_path, init_file=None, dir=False):
+def _env_create(name_or_path, init_file=None, dir=False, with_view=None):
     """Create a new environment, with an optional yaml description.
 
     Arguments:
@@ -196,11 +205,11 @@ def _env_create(name_or_path, init_file=None, dir=False):
             of a named environment
     """
     if dir:
-        env = ev.Environment(name_or_path, init_file)
+        env = ev.Environment(name_or_path, init_file, with_view)
         env.write()
         tty.msg("Created environment in %s" % env.path)
     else:
-        env = ev.create(name_or_path, init_file)
+        env = ev.create(name_or_path, init_file, with_view)
         env.write()
         tty.msg("Created environment '%s' in %s" % (name_or_path, env.path))
     return env
@@ -272,6 +281,50 @@ def env_list(args):
     colify(color_names, indent=4)
 
 
+class ViewAction(object):
+    regenerate = 'regenerate'
+    enable = 'enable'
+    disable = 'disable'
+
+    @staticmethod
+    def actions():
+        return [ViewAction.regenerate, ViewAction.enable, ViewAction.disable]
+
+
+#
+# env view
+#
+def env_view_setup_parser(subparser):
+    """manage a view associated with the environment"""
+    subparser.add_argument(
+        'action', choices=ViewAction.actions(),
+        help="action to take for the environment's view")
+    subparser.add_argument(
+        'view_path', nargs='?',
+        help="when enabling a view, optionally set the path manually"
+    )
+
+
+def env_view(args):
+    env = ev.get_env(args, 'env view')
+
+    if env:
+        if args.action == ViewAction.regenerate:
+            env.regenerate_view()
+        elif args.action == ViewAction.enable:
+            if args.view_path:
+                view_path = args.view_path
+            else:
+                view_path = env.default_view_path
+            env.update_view(view_path)
+            env.write()
+        elif args.action == ViewAction.disable:
+            env.update_view(None)
+            env.write()
+    else:
+        tty.msg("No active environment")
+
+
 #
 # env status
 #
diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py
index e9e3328bf685a6c1f96e60c854f0c42d286ed130..68639a9deb55c16216f9fe59fd6afca73da72cbf 100644
--- a/lib/spack/spack/environment.py
+++ b/lib/spack/spack/environment.py
@@ -9,9 +9,11 @@
 import shutil
 
 import ruamel.yaml
+import six
 
 import llnl.util.filesystem as fs
 import llnl.util.tty as tty
+from llnl.util.tty.color import colorize
 
 import spack.error
 import spack.repo
@@ -20,7 +22,9 @@
 import spack.util.spack_json as sjson
 import spack.config
 from spack.spec import Spec
+from spack.filesystem_view import YamlFilesystemView
 
+from spack.util.environment import EnvironmentModifications
 
 #: environment variable used to indicate the active environment
 spack_env_var = 'SPACK_ENV'
@@ -56,6 +60,7 @@
   # add package specs to the `specs` list
   specs:
   -
+  view: true
 """
 #: regex for validating enviroment names
 valid_environment_name_re = r'^\w[\w-]*$'
@@ -79,7 +84,9 @@ def validate_env_name(name):
     return name
 
 
-def activate(env, use_env_repo=False):
+def activate(
+    env, use_env_repo=False, add_view=True, shell='sh', prompt=None
+):
     """Activate an environment.
 
     To activate an environment, we add its configuration scope to the
@@ -90,8 +97,12 @@ def activate(env, use_env_repo=False):
         env (Environment): the environment to activate
         use_env_repo (bool): use the packages exactly as they appear in the
             environment's repository
+        add_view (bool): generate commands to add view to path variables
+        shell (string): One of `sh`, `csh`.
+        prompt (string): string to add to the users prompt, or None
 
-    TODO: Add support for views here.  Activation should set up the shell
+    Returns:
+        cmds: Shell commands to activate environment.
     TODO: environment to use the activated spack environment.
     """
     global _active_environment
@@ -103,13 +114,41 @@ def activate(env, use_env_repo=False):
 
     tty.debug("Using environmennt '%s'" % _active_environment.name)
 
+    # Construct the commands to run
+    cmds = ''
+    if shell == 'csh':
+        # TODO: figure out how to make color work for csh
+        cmds += 'setenv SPACK_ENV %s;\n' % env.path
+        cmds += 'alias despacktivate "spack env deactivate";\n'
+        if prompt:
+            cmds += 'if (! $?SPACK_OLD_PROMPT ) '
+            cmds += 'setenv SPACK_OLD_PROMPT "${prompt}";\n'
+            cmds += 'set prompt="%s ${prompt}";\n' % prompt
+    else:
+        if 'color' in os.environ['TERM'] and prompt:
+            prompt = colorize('@G{%s} ' % prompt, color=True)
+
+        cmds += 'export SPACK_ENV=%s;\n' % env.path
+        cmds += "alias despacktivate='spack env deactivate';\n"
+        if prompt:
+            cmds += 'if [ -z "${SPACK_OLD_PS1}" ]; then\n'
+            cmds += 'export SPACK_OLD_PS1="${PS1}"; fi;\n'
+            cmds += 'export PS1="%s ${PS1}";\n' % prompt
+
+    if add_view and env._view_path:
+        cmds += env.add_view_to_shell(shell)
+
+    return cmds
+
 
-def deactivate():
+def deactivate(shell='sh'):
     """Undo any configuration or repo settings modified by ``activate()``.
 
+    Arguments:
+        shell (string): One of `sh`, `csh`. Shell style to use.
+
     Returns:
-        (bool): True if an environment was deactivated, False if no
-        environment was active.
+        (string): shell commands for `shell` to undo environment variables
 
     """
     global _active_environment
@@ -123,9 +162,29 @@ def deactivate():
     if _active_environment._repo:
         spack.repo.path.remove(_active_environment._repo)
 
+    cmds = ''
+    if shell == 'csh':
+        cmds += 'unsetenv SPACK_ENV;\n'
+        cmds += 'if ( $?SPACK_OLD_PROMPT ) '
+        cmds += 'set prompt="$SPACK_OLD_PROMPT" && '
+        cmds += 'unsetenv SPACK_OLD_PROMPT;\n'
+        cmds += 'unalias despacktivate;\n'
+    else:
+        cmds += 'unset SPACK_ENV; export SPACK_ENV;\n'
+        cmds += 'unalias despacktivate;\n'
+        cmds += 'if [ -n "$SPACK_OLD_PS1" ]; then\n'
+        cmds += 'export PS1="$SPACK_OLD_PS1";\n'
+        cmds += 'unset SPACK_OLD_PS1; export SPACK_OLD_PS1;\n'
+        cmds += 'fi;\n'
+
+    if _active_environment._view_path:
+        cmds += _active_environment.rm_view_from_shell(shell)
+
     tty.debug("Deactivated environmennt '%s'" % _active_environment.name)
     _active_environment = None
 
+    return cmds
+
 
 def find_environment(args):
     """Find active environment from args, spack.yaml, or environment variable.
@@ -265,12 +324,12 @@ def read(name):
     return Environment(root(name))
 
 
-def create(name, init_file=None):
+def create(name, init_file=None, with_view=None):
     """Create a named environment in Spack."""
     validate_env_name(name)
     if exists(name):
         raise SpackEnvironmentError("'%s': environment already exists" % name)
-    return Environment(root(name), init_file)
+    return Environment(root(name), init_file, with_view)
 
 
 def config_dict(yaml_data):
@@ -327,7 +386,7 @@ def _write_yaml(data, str_or_file):
 
 
 class Environment(object):
-    def __init__(self, path, init_file=None):
+    def __init__(self, path, init_file=None, with_view=None):
         """Create a new environment.
 
         The environment can be optionally initialized with either a
@@ -337,39 +396,41 @@ def __init__(self, path, init_file=None):
             path (str): path to the root directory of this environment
             init_file (str or file object): filename or file object to
                 initialize the environment
+            with_view (str or bool): whether a view should be maintained for
+                the environment. If the value is a string, it specifies the
+                path to the view.
         """
         self.path = os.path.abspath(path)
         self.clear()
 
         if init_file:
-            # initialize the environment from a file if provided
             with fs.open_if_filename(init_file) as f:
                 if hasattr(f, 'name') and f.name.endswith('.lock'):
-                    # Initialize the environment from a lockfile
+                    self._read_manifest(default_manifest_yaml)
                     self._read_lockfile(f)
                     self._set_user_specs_from_lockfile()
-                    self.yaml = _read_yaml(default_manifest_yaml)
                 else:
-                    # Initialize the environment from a spack.yaml file
                     self._read_manifest(f)
         else:
-            # read lockfile, if it exists
+            default_manifest = not os.path.exists(self.manifest_path)
+            if default_manifest:
+                self._read_manifest(default_manifest_yaml)
+            else:
+                with open(self.manifest_path) as f:
+                    self._read_manifest(f)
+
             if os.path.exists(self.lock_path):
                 with open(self.lock_path) as f:
                     self._read_lockfile(f)
+                if default_manifest:
+                    self._set_user_specs_from_lockfile()
 
-            if os.path.exists(self.manifest_path):
-                # read the spack.yaml file, if exists
-                with open(self.manifest_path) as f:
-                    self._read_manifest(f)
-
-            elif self.concretized_user_specs:
-                # if not, take user specs from the lockfile
-                self._set_user_specs_from_lockfile()
-                self.yaml = _read_yaml(default_manifest_yaml)
-            else:
-                # if there's no manifest or lockfile, use the default
-                self._read_manifest(default_manifest_yaml)
+        if with_view is False:
+            self._view_path = None
+        elif isinstance(with_view, six.string_types):
+            self._view_path = with_view
+        # If with_view is None, then defer to the view settings determined by
+        # the manifest file
 
     def _read_manifest(self, f):
         """Read manifest file and set up user specs."""
@@ -378,6 +439,17 @@ def _read_manifest(self, f):
         if spec_list:
             self.user_specs = [Spec(s) for s in spec_list if s]
 
+        enable_view = config_dict(self.yaml).get('view')
+        # enable_view can be true/false, a string, or None (if the manifest did
+        # not specify it)
+        if enable_view is True or enable_view is None:
+            self._view_path = self.default_view_path
+        elif isinstance(enable_view, six.string_types):
+            self._view_path = enable_view
+        else:
+            # enable_view is False
+            self._view_path = None
+
     def _set_user_specs_from_lockfile(self):
         """Copy user_specs from a read-in lockfile."""
         self.user_specs = [Spec(s) for s in self.concretized_user_specs]
@@ -436,6 +508,10 @@ def repos_path(self):
     def log_path(self):
         return os.path.join(self.path, env_subdir_name, 'logs')
 
+    @property
+    def default_view_path(self):
+        return os.path.join(self.env_subdir_path, 'view')
+
     @property
     def repo(self):
         if self._repo is None:
@@ -619,7 +695,97 @@ def install(self, user_spec, concrete_spec=None, **install_args):
                 concrete = spec.concretized()
                 self._add_concrete_spec(spec, concrete)
 
-        concrete.package.do_install(**install_args)
+        self._install(concrete, **install_args)
+
+    def _install(self, spec, **install_args):
+        spec.package.do_install(**install_args)
+
+        # Make sure log directory exists
+        log_path = self.log_path
+        fs.mkdirp(log_path)
+
+        with fs.working_dir(self.path):
+            # Link the resulting log file into logs dir
+            build_log_link = os.path.join(
+                log_path, '%s-%s.log' % (spec.name, spec.dag_hash(7)))
+            if os.path.lexists(build_log_link):
+                os.remove(build_log_link)
+            os.symlink(spec.package.build_log_path, build_log_link)
+
+    def view(self):
+        if not self._view_path:
+            raise SpackEnvironmentError(
+                "{0} does not have a view enabled".format(self.name))
+
+        return YamlFilesystemView(
+            self._view_path, spack.store.layout, ignore_conflicts=True)
+
+    def update_view(self, view_path):
+        if self._view_path and self._view_path != view_path:
+            shutil.rmtree(self._view_path)
+
+        self._view_path = view_path
+
+    def regenerate_view(self):
+        if not self._view_path:
+            tty.debug("Skip view update, this environment does not"
+                      " maintain a view")
+            return
+
+        specs_for_view = []
+        for spec in self._get_environment_specs():
+            # The view does not store build deps, so if we want it to
+            # recognize environment specs (which do store build deps), then
+            # they need to be stripped
+            specs_for_view.append(spack.spec.Spec.from_dict(
+                spec.to_dict(all_deps=False)
+            ))
+        installed_specs_for_view = set(s for s in specs_for_view
+                                       if s.package.installed)
+
+        view = self.view()
+        view.clean()
+        specs_in_view = set(view.get_all_specs())
+        tty.msg("Updating view at {0}".format(self._view_path))
+
+        rm_specs = specs_in_view - installed_specs_for_view
+        view.remove_specs(*rm_specs, with_dependents=False)
+
+        add_specs = installed_specs_for_view - specs_in_view
+        view.add_specs(*add_specs, with_dependencies=False)
+
+    def _shell_vars(self):
+        updates = [
+            ('PATH', ['bin']),
+            ('MANPATH', ['man', 'share/man']),
+            ('ACLOCAL_PATH', ['share/aclocal']),
+            ('LD_LIBRARY_PATH', ['lib', 'lib64']),
+            ('LIBRARY_PATH', ['lib', 'lib64']),
+            ('CPATH', ['include']),
+            ('PKG_CONFIG_PATH', ['lib/pkgconfig', 'lib64/pkgconfig']),
+            ('CMAKE_PREFIX_PATH', ['']),
+        ]
+        path_updates = list()
+        for var, subdirs in updates:
+            paths = filter(lambda x: os.path.exists(x),
+                           list(os.path.join(self._view_path, x)
+                                for x in subdirs))
+            path_updates.append((var, paths))
+        return path_updates
+
+    def add_view_to_shell(self, shell):
+        env_mod = EnvironmentModifications()
+        for var, paths in self._shell_vars():
+            for path in paths:
+                env_mod.prepend_path(var, path)
+        return env_mod.shell_modifications(shell)
+
+    def rm_view_from_shell(self, shell):
+        env_mod = EnvironmentModifications()
+        for var, paths in self._shell_vars():
+            for path in paths:
+                env_mod.remove_path(var, path)
+        return env_mod.shell_modifications(shell)
 
     def _add_concrete_spec(self, spec, concrete, new=True):
         """Called when a new concretized spec is added to the environment.
@@ -648,11 +814,6 @@ def _add_concrete_spec(self, spec, concrete, new=True):
 
     def install_all(self, args=None):
         """Install all concretized specs in an environment."""
-
-        # Make sure log directory exists
-        log_path = self.log_path
-        fs.mkdirp(log_path)
-
         for concretized_hash in self.concretized_order:
             spec = self.specs_by_hash[concretized_hash]
 
@@ -662,17 +823,18 @@ def install_all(self, args=None):
             if args:
                 spack.cmd.install.update_kwargs_from_args(args, kwargs)
 
-            with fs.working_dir(self.path):
-                spec.package.do_install(**kwargs)
+            self._install(spec, **kwargs)
 
             if not spec.external:
                 # Link the resulting log file into logs dir
                 build_log_link = os.path.join(
-                    log_path, '%s-%s.log' % (spec.name, spec.dag_hash(7)))
-                if os.path.exists(build_log_link):
+                    self.log_path, '%s-%s.log' % (spec.name, spec.dag_hash(7)))
+                if os.path.lexists(build_log_link):
                     os.remove(build_log_link)
                 os.symlink(spec.package.build_log_path, build_log_link)
 
+        self.regenerate_view()
+
     def all_specs_by_hash(self):
         """Map of hashes to spec for all specs in this environment."""
         hashes = {}
@@ -857,13 +1019,26 @@ def write(self):
         self._repo = None
 
         # put the new user specs in the YAML
-        yaml_spec_list = config_dict(self.yaml).setdefault('specs', [])
+        yaml_dict = config_dict(self.yaml)
+        yaml_spec_list = yaml_dict.setdefault('specs', [])
         yaml_spec_list[:] = [str(s) for s in self.user_specs]
 
+        if self._view_path == self.default_view_path:
+            view = True
+        elif self._view_path:
+            view = self._view_path
+        else:
+            view = False
+        config_dict(self.yaml)['view'] = view
+
         # if all that worked, write out the manifest file at the top level
         with fs.write_tmp_and_move(self.manifest_path) as f:
             _write_yaml(self.yaml, f)
 
+        # TODO: for operations that just add to the env (install etc.) this
+        # could just call update_view
+        self.regenerate_view()
+
     def __enter__(self):
         self._previous_active = _active_environment
         activate(self)
diff --git a/lib/spack/spack/filesystem_view.py b/lib/spack/spack/filesystem_view.py
index ed69f53df6c014c245f47177e6c84e2127312078..abb6eb4c241e5c7032a723aed13a3c08c35a6a69 100644
--- a/lib/spack/spack/filesystem_view.py
+++ b/lib/spack/spack/filesystem_view.py
@@ -14,7 +14,8 @@
 from llnl.util import tty
 from llnl.util.lang import match_predicate, index_by
 from llnl.util.tty.color import colorize
-from llnl.util.filesystem import mkdirp
+from llnl.util.filesystem import (
+    mkdirp, remove_dead_links, remove_empty_directories)
 
 import spack.util.spack_yaml as s_yaml
 
@@ -407,7 +408,7 @@ def remove_specs(self, *specs, **kwargs):
         set(map(remove_extension, extensions))
         set(map(self.remove_standalone, standalones))
 
-        self.purge_empty_directories()
+        self._purge_empty_directories()
 
     def remove_extension(self, spec, with_dependents=True):
         """
@@ -575,18 +576,15 @@ def print_status(self, *specs, **kwargs):
         else:
             tty.warn(self._croot + "No packages found.")
 
-    def purge_empty_directories(self):
-        """
-            Ascend up from the leaves accessible from `path`
-            and remove empty directories.
-        """
-        for dirpath, subdirs, files in os.walk(self._root, topdown=False):
-            for sd in subdirs:
-                sdp = os.path.join(dirpath, sd)
-                try:
-                    os.rmdir(sdp)
-                except OSError:
-                    pass
+    def _purge_empty_directories(self):
+        remove_empty_directories(self._root)
+
+    def _purge_broken_links(self):
+        remove_dead_links(self._root)
+
+    def clean(self):
+        self._purge_broken_links()
+        self._purge_empty_directories()
 
     def unlink_meta_folder(self, spec):
         path = self.get_path_meta_folder(spec)
diff --git a/lib/spack/spack/schema/env.py b/lib/spack/spack/schema/env.py
index f65ffc987ad4bcda00d9c0ba82186572be4b60d1..9fbc59219c968433f513c407bc69c2989175acaa 100644
--- a/lib/spack/spack/schema/env.py
+++ b/lib/spack/spack/schema/env.py
@@ -47,6 +47,9 @@
                                 {'type': 'object'},
                             ]
                         }
+                    },
+                    'view': {
+                        'type': ['boolean', 'string']
                     }
                 }
             )
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index 2a0e8a85bc47d30870dce26296e75a6696e9774f..0da9377196dfc7bd2d11e900483d42b98a58dc99 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -603,3 +603,179 @@ def test_uninstall_removes_from_env(mock_stage, mock_fetch, install_mockery):
     assert not test.specs_by_hash
     assert not test.concretized_order
     assert not test.user_specs
+
+
+def test_env_updates_view_install(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    with ev.read('test'):
+        add('mpileaks')
+        install('--fake')
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+
+def test_env_without_view_install(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    # Test enabling a view after installing specs
+    env('create', '--without-view', 'test')
+
+    test_env = ev.read('test')
+    with pytest.raises(spack.environment.SpackEnvironmentError):
+        test_env.view()
+
+    view_dir = tmpdir.mkdir('view')
+
+    with ev.read('test'):
+        add('mpileaks')
+        install('--fake')
+
+        env('view', 'enable', str(view_dir))
+
+    # After enabling the view, the specs should be linked into the environment
+    # view dir
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+
+def test_env_config_view_default(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    # This config doesn't mention whether a view is enabled
+    test_config = """\
+env:
+  specs:
+  - mpileaks
+"""
+
+    _env_create('test', StringIO(test_config))
+
+    with ev.read('test'):
+        install('--fake')
+
+    e = ev.read('test')
+    # Try retrieving the view object
+    view = e.view()
+    assert view.get_spec('mpileaks')
+
+
+def test_env_updates_view_install_package(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    with ev.read('test'):
+        install('--fake', 'mpileaks')
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+
+def test_env_updates_view_add_concretize(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    install('--fake', 'mpileaks')
+    with ev.read('test'):
+        add('mpileaks')
+        concretize()
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+
+def test_env_updates_view_uninstall(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    with ev.read('test'):
+        install('--fake', 'mpileaks')
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+    with ev.read('test'):
+        uninstall('-ay')
+
+    assert (not os.path.exists(str(view_dir.join('.spack'))) or
+            os.listdir(str(view_dir.join('.spack'))) == ['projections.yaml'])
+
+
+def test_env_updates_view_uninstall_referenced_elsewhere(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    install('--fake', 'mpileaks')
+    with ev.read('test'):
+        add('mpileaks')
+        concretize()
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+    with ev.read('test'):
+        uninstall('-ay')
+
+    assert (not os.path.exists(str(view_dir.join('.spack'))) or
+            os.listdir(str(view_dir.join('.spack'))) == ['projections.yaml'])
+
+
+def test_env_updates_view_remove_concretize(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    install('--fake', 'mpileaks')
+    with ev.read('test'):
+        add('mpileaks')
+        concretize()
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+    with ev.read('test'):
+        remove('mpileaks')
+        concretize()
+
+    assert (not os.path.exists(str(view_dir.join('.spack'))) or
+            os.listdir(str(view_dir.join('.spack'))) == ['projections.yaml'])
+
+
+def test_env_updates_view_force_remove(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    view_dir = tmpdir.mkdir('view')
+    env('create', '--with-view=%s' % view_dir, 'test')
+    with ev.read('test'):
+        install('--fake', 'mpileaks')
+
+    assert os.path.exists(str(view_dir.join('.spack/mpileaks')))
+    # Check that dependencies got in too
+    assert os.path.exists(str(view_dir.join('.spack/libdwarf')))
+
+    with ev.read('test'):
+        remove('-f', 'mpileaks')
+
+    assert (not os.path.exists(str(view_dir.join('.spack'))) or
+            os.listdir(str(view_dir.join('.spack'))) == ['projections.yaml'])
+
+
+def test_env_activate_view_fails(
+    tmpdir, mock_stage, mock_fetch, install_mockery
+):
+    """Sanity check on env activate to make sure it requires shell support"""
+    out = env('activate', 'test')
+    assert "To initialize spack's shell commands:" in out
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
index 1666f4711e36096e328563899711e689ce81222c..4296e2cbee6a49c8ab8ecd93e614b8f3bae466a7 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -25,6 +25,18 @@
     system_paths
 
 
+_shell_set_strings = {
+    'sh': 'export {0}={1};\n',
+    'csh': 'setenv {0} {1};\n',
+}
+
+
+_shell_unset_strings = {
+    'sh': 'unset {0};\n',
+    'csh': 'unsetenv {0};\n',
+}
+
+
 def is_system_path(path):
     """Predicate that given a path returns True if it is a system path,
     False otherwise.
@@ -138,62 +150,62 @@ def update_args(self, **kwargs):
 
 class SetEnv(NameValueModifier):
 
-    def execute(self):
-        os.environ[self.name] = str(self.value)
+    def execute(self, env):
+        env[self.name] = str(self.value)
 
 
 class AppendFlagsEnv(NameValueModifier):
 
-    def execute(self):
-        if self.name in os.environ and os.environ[self.name]:
-            os.environ[self.name] += self.separator + str(self.value)
+    def execute(self, env):
+        if self.name in env and env[self.name]:
+            env[self.name] += self.separator + str(self.value)
         else:
-            os.environ[self.name] = str(self.value)
+            env[self.name] = str(self.value)
 
 
 class UnsetEnv(NameModifier):
 
-    def execute(self):
+    def execute(self, env):
         # Avoid throwing if the variable was not set
-        os.environ.pop(self.name, None)
+        env.pop(self.name, None)
 
 
 class SetPath(NameValueModifier):
 
-    def execute(self):
+    def execute(self, env):
         string_path = concatenate_paths(self.value, separator=self.separator)
-        os.environ[self.name] = string_path
+        env[self.name] = string_path
 
 
 class AppendPath(NameValueModifier):
 
-    def execute(self):
-        environment_value = os.environ.get(self.name, '')
+    def execute(self, env):
+        environment_value = env.get(self.name, '')
         directories = environment_value.split(
             self.separator) if environment_value else []
         directories.append(os.path.normpath(self.value))
-        os.environ[self.name] = self.separator.join(directories)
+        env[self.name] = self.separator.join(directories)
 
 
 class PrependPath(NameValueModifier):
 
-    def execute(self):
-        environment_value = os.environ.get(self.name, '')
+    def execute(self, env):
+        environment_value = env.get(self.name, '')
         directories = environment_value.split(
             self.separator) if environment_value else []
         directories = [os.path.normpath(self.value)] + directories
-        os.environ[self.name] = self.separator.join(directories)
+        env[self.name] = self.separator.join(directories)
 
 
 class RemovePath(NameValueModifier):
 
-    def execute(self):
-        environment_value = os.environ.get(self.name, '')
+    def execute(self, env):
+        environment_value = env.get(self.name, '')
         directories = environment_value.split(
             self.separator) if environment_value else []
         directories = [os.path.normpath(x) for x in directories
                        if x != os.path.normpath(self.value)]
-        os.environ[self.name] = self.separator.join(directories)
+        env[self.name] = self.separator.join(directories)
 
 
 class EnvironmentModifications(object):
@@ -361,7 +373,28 @@ def apply_modifications(self):
         # Apply modifications one variable at a time
         for name, actions in sorted(modifications.items()):
             for x in actions:
-                x.execute()
+                x.execute(os.environ)
+
+    def shell_modifications(self, shell='sh'):
+        """Return shell code to apply the modifications and clears the list."""
+        modifications = self.group_by_name()
+        new_env = os.environ.copy()
+
+        for name, actions in sorted(modifications.items()):
+            for x in actions:
+                x.execute(new_env)
+
+        cmds = ''
+        for name in set(new_env) & set(os.environ):
+            new = new_env.get(name, None)
+            old = os.environ.get(name, None)
+            if new != old:
+                if new is None:
+                    cmds += _shell_unset_strings[shell].format(name)
+                else:
+                    cmds += _shell_set_strings[shell].format(name,
+                                                             new_env[name])
+        return cmds
 
     @staticmethod
     def from_sourcing_file(filename, *args, **kwargs):