summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/basic_usage.rst27
-rw-r--r--lib/spack/docs/module_file_support.rst39
-rw-r--r--lib/spack/docs/workflows.rst33
-rw-r--r--lib/spack/spack/cmd/__init__.py14
-rw-r--r--lib/spack/spack/cmd/find.py10
-rw-r--r--lib/spack/spack/cmd/load.py66
-rw-r--r--lib/spack/spack/cmd/module.py2
-rw-r--r--lib/spack/spack/cmd/unload.py63
-rw-r--r--lib/spack/spack/environment.py68
-rw-r--r--lib/spack/spack/main.py11
-rw-r--r--lib/spack/spack/test/cmd/find.py13
-rw-r--r--lib/spack/spack/test/cmd/load.py125
-rw-r--r--lib/spack/spack/user_environment.py91
13 files changed, 454 insertions, 108 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index 3ad83c57bd..8342ab4489 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -1317,10 +1317,9 @@ directly when you run ``python``:
Using Extensions
^^^^^^^^^^^^^^^^
-There are three ways to get ``numpy`` working in Python. The first is
-to use :ref:`shell-support`. You can simply ``load`` the
-module for the extension, and it will be added to the ``PYTHONPATH``
-in your current shell:
+There are four ways to get ``numpy`` working in Python. The first is
+to use :ref:`shell-support`. You can simply ``load`` the extension,
+and it will be added to the ``PYTHONPATH`` in your current shell:
.. code-block:: console
@@ -1330,11 +1329,29 @@ in your current shell:
Now ``import numpy`` will succeed for as long as you keep your current
session open.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Loading Extensions via Modules
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Instead of using Spack's environment modification capabilities through
+the ``spack load`` command, you can load numpy through your
+environment modules (using ``environment-modules`` or ``lmod``). This
+will also add the extension to the ``PYTHONPATH`` in your current
+shell.
+
+.. code-block:: console
+
+ $ module load <name of numpy module>
+
+If you do not know the name of the specific numpy module you wish to
+load, you can use the ``spack module tcl|lmod loads`` command to get
+the name of the module from the Spack spec.
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Activating Extensions in a View
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The second way to use extensions is to create a view, which merges the
+Another way to use extensions is to create a view, which merges the
python installation along with the extensions into a single prefix.
See :ref:`filesystem-views` for a more in-depth description of views and
:ref:`cmd-spack-view` for usage of the ``spack view`` command.
diff --git a/lib/spack/docs/module_file_support.rst b/lib/spack/docs/module_file_support.rst
index ba964c2d61..aa7eb57653 100644
--- a/lib/spack/docs/module_file_support.rst
+++ b/lib/spack/docs/module_file_support.rst
@@ -119,7 +119,7 @@ For example this will add the ``mpich`` package built with ``gcc`` to your path:
# ... wait for install ...
- $ spack load mpich %gcc@4.4.7 # modules
+ $ spack load mpich %gcc@4.4.7
$ which mpicc
~/spack/opt/linux-debian7-x86_64/gcc@4.4.7/mpich@3.0.4/bin/mpicc
@@ -129,27 +129,29 @@ want to use a package, you can type unload or unuse similarly:
.. code-block:: console
- $ spack unload mpich %gcc@4.4.7 # modules
+ $ spack unload mpich %gcc@4.4.7
.. note::
- The ``load`` and ``unload`` subcommands are
- only available if you have enabled Spack's shell support *and* you
- have environment-modules installed on your machine.
+ The ``load`` and ``unload`` subcommands are only available if you
+ have enabled Spack's shell support. These command DO NOT use the
+ underlying Spack-generated module files.
-^^^^^^^^^^^^^^^^^^^^^^
-Ambiguous module names
-^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^
+Ambiguous specs
+^^^^^^^^^^^^^^^
-If a spec used with load/unload or use/unuse is ambiguous (i.e. more
-than one installed package matches it), then Spack will warn you:
+If a spec used with load/unload or is ambiguous (i.e. more than one
+installed package matches it), then Spack will warn you:
.. code-block:: console
$ spack load libelf
- ==> Error: Multiple matches for spec libelf. Choose one:
- libelf@0.8.13%gcc@4.4.7 arch=linux-debian7-x86_64
- libelf@0.8.13%intel@15.0.0 arch=linux-debian7-x86_64
+ ==> Error: libelf matches multiple packages.
+ Matching packages:
+ libelf@0.8.13%gcc@4.4.7 arch=linux-debian7-x86_64
+ libelf@0.8.13%intel@15.0.0 arch=linux-debian7-x86_64
+ Use a more specific spec
You can either type the ``spack load`` command again with a fully
qualified argument, or you can add just enough extra constraints to
@@ -171,8 +173,15 @@ To identify just the one built with the Intel compiler.
``spack module tcl loads``
^^^^^^^^^^^^^^^^^^^^^^^^^^
-In some cases, it is desirable to load not just a module, but also all
-the modules it depends on. This is not required for most modules
+In some cases, it is desirable to use a Spack-generated module, rather
+than relying on Spack's built-in user-environment modification
+capabilities. To translate a spec into a module name, use ``spack
+module tcl loads`` or ``spack module lmod loads`` depending on the
+module system desired.
+
+
+To load not just a module, but also all the modules it depends on, use
+the ``--dependencies`` option. This is not required for most modules
because Spack builds binaries with RPATH support. However, not all
packages use RPATH to find their dependencies: this can be true in
particular for Python extensions, which are currently *not* built with
diff --git a/lib/spack/docs/workflows.rst b/lib/spack/docs/workflows.rst
index 73f4655d1e..914f84041b 100644
--- a/lib/spack/docs/workflows.rst
+++ b/lib/spack/docs/workflows.rst
@@ -253,14 +253,14 @@ However, other more powerful methods are generally preferred for user
environments.
-^^^^^^^^^^^^^^^^^^^^^^^
-Spack-Generated Modules
-^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Using ``spack load`` to Manage the User Environment
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Suppose that Spack has been used to install a set of command-line
programs, which users now wish to use. One can in principle put a
number of ``spack load`` commands into ``.bashrc``, for example, to
-load a set of Spack-generated modules:
+load a set of Spack packages:
.. code-block:: sh
@@ -273,7 +273,7 @@ load a set of Spack-generated modules:
Although simple load scripts like this are useful in many cases, they
have some drawbacks:
-1. The set of modules loaded by them will in general not be
+1. The set of packages loaded by them will in general not be
consistent. They are a decent way to load commands to be called
from command shells. See below for better ways to assemble a
consistent set of packages for building application programs.
@@ -285,19 +285,24 @@ have some drawbacks:
other hand, are not very smart: if the user-supplied spec matches
more than one installed package, then ``spack module tcl loads`` will
fail. This may change in the future. For now, the workaround is to
- be more specific on any ``spack module tcl loads`` lines that fail.
+ be more specific on any ``spack load`` commands that fail.
""""""""""""""""""""""
Generated Load Scripts
""""""""""""""""""""""
-Another problem with using `spack load` is, it is slow; a typical user
-environment could take several seconds to load, and would not be
-appropriate to put into ``.bashrc`` directly. It is preferable to use
-a series of ``spack module tcl loads`` commands to pre-compute which
-modules to load. These can be put in a script that is run whenever
-installed Spack packages change. For example:
+Another problem with using `spack load` is, it can be slow; a typical
+user environment could take several seconds to load, and would not be
+appropriate to put into ``.bashrc`` directly. This is because it
+requires the full start-up overhead of python/Spack for each command.
+In some circumstances it is preferable to use a series of ``spack
+module tcl loads`` (or ``spack module lmod loads``) commands to
+pre-compute which modules to load. This will generate the modulenames
+to load the packages using environment modules, rather than Spack's
+built-in support for environment modifications. These can be put in a
+script that is run whenever installed Spack packages change. For
+example:
.. code-block:: sh
@@ -634,7 +639,7 @@ Global Activations
Python (and similar systems) packages directly or creating a view.
If extensions are globally activated, then ``spack load python`` will
also load all the extensions activated for the given ``python``.
-This reduces the need for users to load a large number of modules.
+This reduces the need for users to load a large number of packages.
However, Spack global activations have two potential drawbacks:
@@ -1254,7 +1259,7 @@ In order to build and run the image, execute:
RUN spack install tar \
&& spack clean -a
- # need the modules already during image build?
+ # need the executables from a package already during image build?
#RUN /bin/bash -l -c ' \
# spack load tar \
# && which tar'
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index ef620c112b..2a75a87b54 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -190,6 +190,20 @@ def disambiguate_spec(spec, env, local=False, installed=True):
database query. See ``spack.database.Database._query`` for details.
"""
hashes = env.all_hashes() if env else None
+ return disambiguate_spec_from_hashes(spec, hashes, local, installed)
+
+
+def disambiguate_spec_from_hashes(spec, hashes, local=False, installed=True):
+ """Given a spec and a list of hashes, get concrete spec the spec refers to.
+
+ Arguments:
+ spec (spack.spec.Spec): a spec to disambiguate
+ hashes (iterable): a set of hashes of specs among which to disambiguate
+ local (boolean, default False): do not search chained spack instances
+ installed (boolean or any, or spack.database.InstallStatus or iterable
+ of spack.database.InstallStatus): install status argument passed to
+ database query. See ``spack.database.Database._query`` for details.
+ """
if local:
matching_specs = spack.store.db.query_local(spec, hashes=hashes,
installed=installed)
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index b337cd0738..fa36e1cd26 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -4,7 +4,9 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from __future__ import print_function
+
import copy
+import os
import llnl.util.tty as tty
import llnl.util.tty.color as color
@@ -14,6 +16,7 @@ import spack.environment as ev
import spack.repo
import spack.cmd as cmd
import spack.cmd.common.arguments as arguments
+import spack.user_environment as uenv
from spack.util.string import plural
from spack.database import InstallStatuses
@@ -81,6 +84,9 @@ def setup_parser(subparser):
action='store_true',
dest='variants',
help='show variants in output (can be long)')
+ subparser.add_argument(
+ '--loaded', action='store_true',
+ help='show only packages loaded in the user environment')
subparser.add_argument('-M', '--only-missing',
action='store_true',
dest='only_missing',
@@ -220,6 +226,10 @@ def find(parser, args):
packages_with_tags = spack.repo.path.packages_with_tags(*args.tags)
results = [x for x in results if x.name in packages_with_tags]
+ if args.loaded:
+ hashes = os.environ.get(uenv.spack_loaded_hashes_var, '').split(':')
+ results = [x for x in results if x.dag_hash() in hashes]
+
# Display the result
if args.json:
cmd.display_specs_as_json(results, deps=args.deps)
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index 9c48fe802a..80c7263a7a 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -3,10 +3,18 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
-from spack.cmd.common import print_module_placeholder_help, arguments
+import sys
-description = "add package to environment using `module load`"
-section = "modules"
+import llnl.util.tty as tty
+
+import spack.cmd
+import spack.cmd.common.arguments as arguments
+import spack.environment as ev
+import spack.util.environment
+import spack.user_environment as uenv
+
+description = "add package to the user environment"
+section = "user environment"
level = "short"
@@ -14,8 +22,56 @@ def setup_parser(subparser):
"""Parser is only constructed so that this prints a nice help
message with -h. """
arguments.add_common_arguments(
- subparser, ['recurse_dependencies', 'installed_spec'])
+ subparser, ['recurse_dependencies', 'installed_specs'])
+
+ shells = subparser.add_mutually_exclusive_group()
+ shells.add_argument(
+ '--sh', action='store_const', dest='shell', const='sh',
+ help="print sh commands to load the package")
+ shells.add_argument(
+ '--csh', action='store_const', dest='shell', const='csh',
+ help="print csh commands to load the package")
+
+ subparser.add_argument(
+ '--only',
+ default='package,dependencies',
+ dest='things_to_load',
+ choices=['package', 'dependencies'],
+ help="""select whether to load the package and its dependencies
+the default is to load the package and all dependencies
+alternatively one can decide to load only the package or only
+the dependencies"""
+ )
def load(parser, args):
- print_module_placeholder_help()
+ env = ev.get_env(args, 'load')
+ specs = [spack.cmd.disambiguate_spec(spec, env)
+ for spec in spack.cmd.parse_specs(args.specs)]
+
+ if not args.shell:
+ msg = [
+ "This command works best with Spack's shell support",
+ ""
+ ] + spack.cmd.common.shell_init_instructions + [
+ 'Or, if you want to use `spack load` without initializing',
+ 'shell support, you can run one of these:',
+ '',
+ ' eval `spack load --sh %s` # for bash/sh' % args.specs,
+ ' eval `spack load --csh %s` # for csh/tcsh' % args.specs,
+ ]
+ tty.msg(*msg)
+ return 1
+
+ if 'dependencies' in args.things_to_load:
+ include_roots = 'package' in args.things_to_load
+ specs = [dep for spec in specs
+ for dep in spec.traverse(root=include_roots, order='post')]
+
+ env_mod = spack.util.environment.EnvironmentModifications()
+ for spec in specs:
+ env_mod.extend(uenv.environment_modifications_for_spec(spec))
+ env_mod.prepend_path(uenv.spack_loaded_hashes_var, spec.dag_hash())
+ cmds = env_mod.shell_modifications(args.shell)
+
+ sys.stdout.write(cmds)
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
index 19a2eacce1..f86f3e5f25 100644
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -11,7 +11,7 @@ import spack.cmd.modules.lmod
import spack.cmd.modules.tcl
description = "manipulate module files"
-section = "modules"
+section = "user environment"
level = "short"
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index 92a25478b6..d19a33102f 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -3,18 +3,71 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
-from spack.cmd.common import print_module_placeholder_help, arguments
+import sys
+import os
-description = "remove package from environment using `module unload`"
-section = "modules"
+import llnl.util.tty as tty
+
+import spack.cmd
+import spack.cmd.common.arguments as arguments
+import spack.util.environment
+import spack.user_environment as uenv
+import spack.error
+
+description = "remove package from the user environment"
+section = "user environment"
level = "short"
def setup_parser(subparser):
"""Parser is only constructed so that this prints a nice help
message with -h. """
- arguments.add_common_arguments(subparser, ['installed_spec'])
+ arguments.add_common_arguments(subparser, ['installed_specs'])
+
+ shells = subparser.add_mutually_exclusive_group()
+ shells.add_argument(
+ '--sh', action='store_const', dest='shell', const='sh',
+ help="print sh commands to activate the environment")
+ shells.add_argument(
+ '--csh', action='store_const', dest='shell', const='csh',
+ help="print csh commands to activate the environment")
+
+ subparser.add_argument('-a', '--all', action='store_true',
+ help='unload all loaded Spack packages.')
def unload(parser, args):
- print_module_placeholder_help()
+ """Unload spack packages from the user environment."""
+ if args.specs and args.all:
+ raise spack.error.SpackError("Cannot specify specs on command line"
+ " when unloading all specs with '--all'")
+
+ hashes = os.environ.get(uenv.spack_loaded_hashes_var, '').split(':')
+ if args.specs:
+ specs = [spack.cmd.disambiguate_spec_from_hashes(spec, hashes)
+ for spec in spack.cmd.parse_specs(args.specs)]
+ else:
+ specs = spack.store.db.query(hashes=hashes)
+
+ if not args.shell:
+ msg = [
+ "This command works best with Spack's shell support",
+ ""
+ ] + spack.cmd.common.shell_init_instructions + [
+ 'Or, if you want to use `spack unload` without initializing',
+ 'shell support, you can run one of these:',
+ '',
+ ' eval `spack unload --sh %s` # for bash/sh' % args.specs,
+ ' eval `spack unload --csh %s` # for csh/tcsh' % args.specs,
+ ]
+ tty.msg(*msg)
+ return 1
+
+ env_mod = spack.util.environment.EnvironmentModifications()
+ for spec in specs:
+ env_mod.extend(
+ uenv.environment_modifications_for_spec(spec).reversed())
+ env_mod.remove_path(uenv.spack_loaded_hashes_var, spec.dag_hash())
+ cmds = env_mod.shell_modifications(args.shell)
+
+ sys.stdout.write(cmds)
diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py
index 341d62fecb..351120b127 100644
--- a/lib/spack/spack/environment.py
+++ b/lib/spack/spack/environment.py
@@ -29,9 +29,7 @@ import spack.store
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
import spack.config
-import spack.build_environment as build_env
-
-from spack.util.prefix import Prefix
+import spack.user_environment as uenv
from spack.filesystem_view import YamlFilesystemView
import spack.util.environment
import spack.architecture as architecture
@@ -1070,62 +1068,6 @@ class Environment(object):
for view in self.views.values():
view.regenerate(specs, self.roots())
- prefix_inspections = {
- 'bin': ['PATH'],
- 'lib': ['LD_LIBRARY_PATH', 'LIBRARY_PATH', 'DYLD_LIBRARY_PATH'],
- 'lib64': ['LD_LIBRARY_PATH', 'LIBRARY_PATH', 'DYLD_LIBRARY_PATH'],
- 'man': ['MANPATH'],
- 'share/man': ['MANPATH'],
- 'share/aclocal': ['ACLOCAL_PATH'],
- 'include': ['CPATH'],
- 'lib/pkgconfig': ['PKG_CONFIG_PATH'],
- 'lib64/pkgconfig': ['PKG_CONFIG_PATH'],
- '': ['CMAKE_PREFIX_PATH']
- }
-
- def unconditional_environment_modifications(self, view):
- """List of environment (shell) modifications to be processed for view.
-
- This list does not depend on the specs in this environment"""
- env = spack.util.environment.EnvironmentModifications()
-
- for subdir, vars in self.prefix_inspections.items():
- full_subdir = os.path.join(view.root, subdir)
- for var in vars:
- env.prepend_path(var, full_subdir)
-
- return env
-
- def environment_modifications_for_spec(self, spec, view=None):
- """List of environment (shell) modifications to be processed for spec.
-
- This list is specific to the location of the spec or its projection in
- the view."""
- spec = spec.copy()
- if view:
- spec.prefix = Prefix(view.view().get_projection_for_spec(spec))
-
- # generic environment modifications determined by inspecting the spec
- # prefix
- env = spack.util.environment.inspect_path(
- spec.prefix,
- self.prefix_inspections,
- exclude=spack.util.environment.is_system_path
- )
-
- # Let the extendee/dependency modify their extensions/dependents
- # before asking for package-specific modifications
- env.extend(
- build_env.modifications_from_dependencies(
- spec, context='run'
- )
- )
- # Package specific modifications
- build_env.set_module_variables_for_package(spec.package)
- spec.package.setup_run_environment(env)
-
- return env
-
def add_default_view_to_shell(self, shell):
env_mod = spack.util.environment.EnvironmentModifications()
@@ -1133,12 +1075,12 @@ class Environment(object):
# No default view to add to shell
return env_mod.shell_modifications(shell)
- env_mod.extend(self.unconditional_environment_modifications(
+ env_mod.extend(uenv.unconditional_environment_modifications(
self.default_view))
for _, spec in self.concretized_specs():
if spec in self.default_view and spec.package.installed:
- env_mod.extend(self.environment_modifications_for_spec(
+ env_mod.extend(uenv.environment_modifications_for_spec(
spec, self.default_view))
# deduplicate paths from specs mapped to the same location
@@ -1154,13 +1096,13 @@ class Environment(object):
# No default view to add to shell
return env_mod.shell_modifications(shell)
- env_mod.extend(self.unconditional_environment_modifications(
+ env_mod.extend(uenv.unconditional_environment_modifications(
self.default_view).reversed())
for _, spec in self.concretized_specs():
if spec in self.default_view and spec.package.installed:
env_mod.extend(
- self.environment_modifications_for_spec(
+ uenv.environment_modifications_for_spec(
spec, self.default_view).reversed())
return env_mod.shell_modifications(shell)
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
index 2a6b774536..4ce4ae331e 100644
--- a/lib/spack/spack/main.py
+++ b/lib/spack/spack/main.py
@@ -645,6 +645,17 @@ def main(argv=None):
parser.add_argument('command', nargs=argparse.REMAINDER)
args, unknown = parser.parse_known_args(argv)
+ # Recover stored LD_LIBRARY_PATH variables from spack shell function
+ # This is necessary because MacOS System Integrity Protection clears
+ # (DY?)LD_LIBRARY_PATH variables on process start.
+ # Spack clears these variables before building and installing packages,
+ # but needs to know the prior state for commands like `spack load` and
+ # `spack env activate that modify the user environment.
+ for var in ('LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH'):
+ stored_var_name = 'SPACK_%s' % var
+ if stored_var_name in os.environ:
+ os.environ[var] = os.environ[stored_var_name]
+
# activate an environment if one was specified on the command line
if not args.no_env:
env = ev.find_environment(args)
diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py
index 560d8169f5..8516569592 100644
--- a/lib/spack/spack/test/cmd/find.py
+++ b/lib/spack/spack/test/cmd/find.py
@@ -5,10 +5,12 @@
import argparse
import json
+import os
import pytest
import spack.cmd as cmd
import spack.cmd.find
+import spack.user_environment as uenv
from spack.main import SpackCommand
from spack.spec import Spec
from spack.util.pattern import Bunch
@@ -318,3 +320,14 @@ def test_find_prefix_in_env(mutable_mock_env_path, install_mockery, mock_fetch,
find('-l')
find('-L')
# Would throw error on regression
+
+
+def test_find_loaded(database, working_env):
+ output = find('--loaded', '--group')
+ assert output == '' # 0 packages installed printed separately
+
+ os.environ[uenv.spack_loaded_hashes_var] = ':'.join(
+ [x.dag_hash() for x in spack.store.db.query()])
+ output = find('--loaded')
+ expected = find()
+ assert output == expected
diff --git a/lib/spack/spack/test/cmd/load.py b/lib/spack/spack/test/cmd/load.py
new file mode 100644
index 0000000000..a10b99d45b
--- /dev/null
+++ b/lib/spack/spack/test/cmd/load.py
@@ -0,0 +1,125 @@
+# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
+from spack.main import SpackCommand
+import spack.spec
+import spack.user_environment as uenv
+
+load = SpackCommand('load')
+unload = SpackCommand('unload')
+install = SpackCommand('install')
+location = SpackCommand('location')
+
+
+def test_load(install_mockery, mock_fetch, mock_archive, mock_packages):
+ """Test that the commands generated by load add the specified prefix
+ inspections. Also test that Spack records loaded specs by hash in the
+ user environment.
+
+ CMAKE_PREFIX_PATH is the only prefix inspection guaranteed for fake
+ packages, since it keys on the prefix instead of a subdir."""
+ install('mpileaks')
+ mpileaks_spec = spack.spec.Spec('mpileaks').concretized()
+
+ sh_out = load('--sh', '--only', 'package', 'mpileaks')
+ csh_out = load('--csh', '--only', 'package', 'mpileaks')
+
+ # Test prefix inspections
+ sh_out_test = 'export CMAKE_PREFIX_PATH=%s' % mpileaks_spec.prefix
+ csh_out_test = 'setenv CMAKE_PREFIX_PATH %s' % mpileaks_spec.prefix
+ assert sh_out_test in sh_out
+ assert csh_out_test in csh_out
+
+ # Test hashes recorded properly
+ hash_test_replacements = (uenv.spack_loaded_hashes_var,
+ mpileaks_spec.dag_hash())
+ sh_hash_test = 'export %s=%s' % hash_test_replacements
+ csh_hash_test = 'setenv %s %s' % hash_test_replacements
+ assert sh_hash_test in sh_out
+ assert csh_hash_test in csh_out
+
+
+def test_load_recursive(install_mockery, mock_fetch, mock_archive,
+ mock_packages):
+ """Test that the '-r' option to the load command prepends dependency prefix
+ inspections in post-order"""
+ install('mpileaks')
+ mpileaks_spec = spack.spec.Spec('mpileaks').concretized()
+
+ sh_out = load('--sh', 'mpileaks')
+ csh_out = load('--csh', 'mpileaks')
+
+ # Test prefix inspections
+ prefix_test_replacement = ':'.join(reversed(
+ [s.prefix for s in mpileaks_spec.traverse(order='post')]))
+
+ sh_prefix_test = 'export CMAKE_PREFIX_PATH=%s' % prefix_test_replacement
+ csh_prefix_test = 'setenv CMAKE_PREFIX_PATH %s' % prefix_test_replacement
+ assert sh_prefix_test in sh_out
+ assert csh_prefix_test in csh_out
+
+ # Test spack records loaded hashes properly
+ hash_test_replacement = (uenv.spack_loaded_hashes_var, ':'.join(reversed(
+ [s.dag_hash() for s in mpileaks_spec.traverse(order='post')])))
+ sh_hash_test = 'export %s=%s' % hash_test_replacement
+ csh_hash_test = 'setenv %s %s' % hash_test_replacement
+ assert sh_hash_test in sh_out
+ assert csh_hash_test in csh_out
+
+
+def test_load_includes_run_env(install_mockery, mock_fetch, mock_archive,
+ mock_packages):
+ """Tests that environment changes from the package's
+ `setup_run_environment` method are added to the user environment in
+ addition to the prefix inspections"""
+ install('mpileaks')
+
+ sh_out = load('--sh', 'mpileaks')
+ csh_out = load('--csh', 'mpileaks')
+
+ assert 'export FOOBAR=mpileaks' in sh_out
+ assert 'setenv FOOBAR mpileaks' in csh_out
+
+
+def test_load_fails_no_shell(install_mockery, mock_fetch, mock_archive,
+ mock_packages):
+ """Test that spack load prints an error message without a shell."""
+ install('mpileaks')
+
+ out = load('mpileaks', fail_on_error=False)
+ assert "To initialize spack's shell commands" in out
+
+
+def test_unload(install_mockery, mock_fetch, mock_archive, mock_packages,
+ working_env):
+ """Tests that any variables set in the user environment are undone by the
+ unload command"""
+ install('mpileaks')
+ mpileaks_spec = spack.spec.Spec('mpileaks').concretized()
+
+ # Set so unload has something to do
+ os.environ['FOOBAR'] = 'mpileaks'
+ os.environ[uenv.spack_loaded_hashes_var] = '%s:%s' % (
+ mpileaks_spec.dag_hash(), 'garbage')
+
+ sh_out = unload('--sh', 'mpileaks')
+ csh_out = unload('--csh', 'mpileaks')
+
+ assert 'unset FOOBAR' in sh_out
+ assert 'unsetenv FOOBAR' in csh_out
+
+ assert 'export %s=garbage' % uenv.spack_loaded_hashes_var in sh_out
+ assert 'setenv %s garbage' % uenv.spack_loaded_hashes_var in csh_out
+
+
+def test_unload_fails_no_shell(install_mockery, mock_fetch, mock_archive,
+ mock_packages, working_env):
+ """Test that spack unload prints an error message without a shell."""
+ install('mpileaks')
+ mpileaks_spec = spack.spec.Spec('mpileaks').concretized()
+ os.environ[uenv.spack_loaded_hashes_var] = mpileaks_spec.dag_hash()
+
+ out = unload('mpileaks', fail_on_error=False)
+ assert "To initialize spack's shell commands" in out
diff --git a/lib/spack/spack/user_environment.py b/lib/spack/spack/user_environment.py
new file mode 100644
index 0000000000..4c9fdc3d67
--- /dev/null
+++ b/lib/spack/spack/user_environment.py
@@ -0,0 +1,91 @@
+# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import sys
+import os
+
+import spack.util.prefix as prefix
+import spack.util.environment as environment
+import spack.build_environment as build_env
+
+#: Environment variable name Spack uses to track individually loaded packages
+spack_loaded_hashes_var = 'SPACK_LOADED_HASHES'
+
+
+def prefix_inspections(platform):
+ """Get list of prefix inspections for platform
+
+ Arguments:
+ platform (string): the name of the platform to consider. The platform
+ determines what environment variables Spack will use for some
+ inspections.
+
+ Returns:
+ A dictionary mapping subdirectory names to lists of environment
+ variables to modify with that directory if it exists.
+ """
+ inspections = {
+ 'bin': ['PATH'],
+ 'lib': ['LD_LIBRARY_PATH', 'LIBRARY_PATH'],
+ 'lib64': ['LD_LIBRARY_PATH', 'LIBRARY_PATH'],
+ 'man': ['MANPATH'],
+ 'share/man': ['MANPATH'],
+ 'share/aclocal': ['ACLOCAL_PATH'],
+ 'include': ['CPATH'],
+ 'lib/pkgconfig': ['PKG_CONFIG_PATH'],
+ 'lib64/pkgconfig': ['PKG_CONFIG_PATH'],
+ '': ['CMAKE_PREFIX_PATH']
+ }
+
+ if platform == 'darwin':
+ for subdir in ('lib', 'lib64'):
+ inspections[subdir].append('DYLD_LIBRARY_PATH')
+
+ return inspections
+
+
+def unconditional_environment_modifications(view):
+ """List of environment (shell) modifications to be processed for view.
+
+ This list does not depend on the specs in this environment"""
+ env = environment.EnvironmentModifications()
+
+ for subdir, vars in prefix_inspections(sys.platform).items():
+ full_subdir = os.path.join(view.root, subdir)
+ for var in vars:
+ env.prepend_path(var, full_subdir)
+
+ return env
+
+
+def environment_modifications_for_spec(spec, view=None):
+ """List of environment (shell) modifications to be processed for spec.
+
+ This list is specific to the location of the spec or its projection in
+ the view."""
+ spec = spec.copy()
+ if view:
+ spec.prefix = prefix.Prefix(view.view().get_projection_for_spec(spec))
+
+ # generic environment modifications determined by inspecting the spec
+ # prefix
+ env = environment.inspect_path(
+ spec.prefix,
+ prefix_inspections(spec.platform),
+ exclude=environment.is_system_path
+ )
+
+ # Let the extendee/dependency modify their extensions/dependents
+ # before asking for package-specific modifications
+ env.extend(
+ build_env.modifications_from_dependencies(
+ spec, context='run'
+ )
+ )
+
+ # Package specific modifications
+ build_env.set_module_variables_for_package(spec.package)
+ spec.package.setup_run_environment(env)
+
+ return env