summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/spack-tmpconfig1
-rw-r--r--etc/spack/defaults/config.yaml4
-rw-r--r--lib/spack/docs/environments.rst9
-rw-r--r--lib/spack/spack/cmd/env.py2
-rw-r--r--lib/spack/spack/cmd/location.py2
-rw-r--r--lib/spack/spack/environment/environment.py46
-rw-r--r--lib/spack/spack/main.py2
-rw-r--r--lib/spack/spack/schema/config.py1
-rw-r--r--lib/spack/spack/test/cmd/concretize.py9
-rw-r--r--lib/spack/spack/test/cmd/env.py17
-rw-r--r--lib/spack/spack/test/cmd/uninstall.py5
-rw-r--r--lib/spack/spack/test/conftest.py10
-rw-r--r--lib/spack/spack/test/env.py18
13 files changed, 97 insertions, 29 deletions
diff --git a/bin/spack-tmpconfig b/bin/spack-tmpconfig
index a3d053d340..a477daa4d3 100755
--- a/bin/spack-tmpconfig
+++ b/bin/spack-tmpconfig
@@ -72,6 +72,7 @@ config:
root: $TMP_DIR/install
misc_cache: $$user_cache_path/cache
source_cache: $$user_cache_path/source
+ environments_root: $TMP_DIR/envs
EOF
cat >"$SPACK_USER_CONFIG_PATH/bootstrap.yaml" <<EOF
bootstrap:
diff --git a/etc/spack/defaults/config.yaml b/etc/spack/defaults/config.yaml
index 06d6a10909..43f8a98dff 100644
--- a/etc/spack/defaults/config.yaml
+++ b/etc/spack/defaults/config.yaml
@@ -81,6 +81,10 @@ config:
source_cache: $spack/var/spack/cache
+ ## Directory where spack managed environments are created and stored
+ # environments_root: $spack/var/spack/environments
+
+
# Cache directory for miscellaneous files, like the package index.
# This can be purged with `spack clean --misc-cache`
misc_cache: $user_cache_path/cache
diff --git a/lib/spack/docs/environments.rst b/lib/spack/docs/environments.rst
index 09d8f07af1..e2805e4f01 100644
--- a/lib/spack/docs/environments.rst
+++ b/lib/spack/docs/environments.rst
@@ -58,9 +58,9 @@ Using Environments
Here we follow a typical use case of creating, concretizing,
installing and loading an environment.
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Creating a named Environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Creating a managed Environment
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An environment is created by:
@@ -72,7 +72,8 @@ Spack then creates the directory ``var/spack/environments/myenv``.
.. note::
- All named environments are stored in the ``var/spack/environments`` folder.
+ All managed environments by default are stored in the ``var/spack/environments`` folder.
+ This location can be changed by setting the ``environments_root`` variable in ``config.yaml``.
In the ``var/spack/environments/myenv`` directory, Spack creates the
file ``spack.yaml`` and the hidden directory ``.spack-env``.
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index fe7d1ad59a..085757ca0f 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -165,7 +165,7 @@ def env_activate(args):
short_name = os.path.basename(env_path)
ev.Environment(env).write(regenerate=False)
- # Named environment
+ # Managed environment
elif ev.exists(env_name_or_dir) and not args.dir:
env_path = ev.root(env_name_or_dir)
short_name = env_name_or_dir
diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py
index 7cce744ca7..aba8264a28 100644
--- a/lib/spack/spack/cmd/location.py
+++ b/lib/spack/spack/cmd/location.py
@@ -95,7 +95,7 @@ def location(parser, args):
spack.cmd.require_active_env("location -e")
path = ev.active_environment().path
else:
- # Get named environment path
+ # Get path of requested environment
if not ev.exists(args.location_env):
tty.die("no such environment: '%s'" % args.location_env)
path = ev.root(args.location_env)
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index 813ffca0b6..cabb255e23 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -62,8 +62,8 @@ spack_env_var = "SPACK_ENV"
_active_environment = None
-#: path where environments are stored in the spack tree
-env_path = os.path.join(spack.paths.var_path, "environments")
+#: default path where environments are stored in the spack tree
+default_env_path = os.path.join(spack.paths.var_path, "environments")
#: Name of the input yaml file for an environment
@@ -78,6 +78,26 @@ lockfile_name = "spack.lock"
env_subdir_name = ".spack-env"
+def env_root_path():
+ """Override default root path if the user specified it"""
+ return spack.util.path.canonicalize_path(
+ spack.config.get("config:environments_root", default=default_env_path)
+ )
+
+
+def check_disallowed_env_config_mods(scopes):
+ for scope in scopes:
+ with spack.config.use_configuration(scope):
+ if spack.config.get("config:environments_root"):
+ raise SpackEnvironmentError(
+ "Spack environments are prohibited from modifying 'config:environments_root' "
+ "because it can make the definition of the environment ill-posed. Please "
+ "remove from your environment and place it in a permanent scope such as "
+ "defaults, system, site, etc."
+ )
+ return scopes
+
+
def default_manifest_yaml():
"""default spack.yaml file to put in new environments"""
return """\
@@ -214,7 +234,7 @@ def active_environment():
def _root(name):
"""Non-validating version of root(), to be used internally."""
- return os.path.join(env_path, name)
+ return os.path.join(env_root_path(), name)
def root(name):
@@ -249,10 +269,12 @@ def read(name):
def create(name, init_file=None, with_view=None, keep_relative=False):
- """Create a named environment in Spack."""
+ """Create a managed environment in Spack."""
+ if not os.path.isdir(env_root_path()):
+ fs.mkdirp(env_root_path())
validate_env_name(name)
if exists(name):
- raise SpackEnvironmentError("'%s': environment already exists" % name)
+ raise SpackEnvironmentError("'%s': environment already exists at %s" % (name, root(name)))
return Environment(root(name), init_file, with_view, keep_relative)
@@ -266,10 +288,10 @@ def all_environment_names():
"""List the names of environments that currently exist."""
# just return empty if the env path does not exist. A read-only
# operation like list should not try to create a directory.
- if not os.path.exists(env_path):
+ if not os.path.exists(env_root_path()):
return []
- candidates = sorted(os.listdir(env_path))
+ candidates = sorted(os.listdir(env_root_path()))
names = []
for candidate in candidates:
yaml_path = os.path.join(_root(candidate), manifest_name)
@@ -279,7 +301,7 @@ def all_environment_names():
def all_environments():
- """Generator for all named Environments."""
+ """Generator for all managed Environments."""
for name in all_environment_names():
yield read(name)
@@ -859,14 +881,14 @@ class Environment(object):
@property
def internal(self):
"""Whether this environment is managed by Spack."""
- return self.path.startswith(env_path)
+ return self.path.startswith(env_root_path())
@property
def name(self):
"""Human-readable representation of the environment.
This is the path for directory environments, and just the name
- for named environments.
+ for managed environments.
"""
if self.internal:
return os.path.basename(self.path)
@@ -1044,7 +1066,9 @@ class Environment(object):
def config_scopes(self):
"""A list of all configuration scopes for this environment."""
- return self.included_config_scopes() + [self.env_file_config_scope()]
+ return check_disallowed_env_config_mods(
+ self.included_config_scopes() + [self.env_file_config_scope()]
+ )
def destroy(self):
"""Remove this environment from Spack entirely."""
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
index 06183ac112..ae6bed7fd0 100644
--- a/lib/spack/spack/main.py
+++ b/lib/spack/spack/main.py
@@ -459,7 +459,7 @@ def make_argument_parser(**kwargs):
dest="env_dir",
metavar="DIR",
action="store",
- help="run with an environment directory (ignore named environments)",
+ help="run with an environment directory (ignore managed environments)",
)
env_group.add_argument(
"-E",
diff --git a/lib/spack/spack/schema/config.py b/lib/spack/spack/schema/config.py
index 7ecd1ad627..abe207cac0 100644
--- a/lib/spack/spack/schema/config.py
+++ b/lib/spack/spack/schema/config.py
@@ -67,6 +67,7 @@ properties = {
"license_dir": {"type": "string"},
"source_cache": {"type": "string"},
"misc_cache": {"type": "string"},
+ "environments_root": {"type": "string"},
"connect_timeout": {"type": "integer", "minimum": 0},
"verify_ssl": {"type": "boolean"},
"suppress_gpg_warnings": {"type": "boolean"},
diff --git a/lib/spack/spack/test/cmd/concretize.py b/lib/spack/spack/test/cmd/concretize.py
index 08a19162f9..868da6df61 100644
--- a/lib/spack/spack/test/cmd/concretize.py
+++ b/lib/spack/spack/test/cmd/concretize.py
@@ -9,8 +9,7 @@ import pytest
import spack.environment as ev
from spack.main import SpackCommand
-# everything here uses the mock_env_path
-pytestmark = pytest.mark.usefixtures("mutable_mock_env_path", "config", "mutable_mock_repo")
+pytestmark = pytest.mark.usefixtures("config", "mutable_mock_repo")
env = SpackCommand("env")
add = SpackCommand("add")
@@ -21,7 +20,7 @@ unification_strategies = [False, True, "when_possible"]
@pytest.mark.parametrize("unify", unification_strategies)
-def test_concretize_all_test_dependencies(unify):
+def test_concretize_all_test_dependencies(unify, mutable_mock_env_path):
"""Check all test dependencies are concretized."""
env("create", "test")
@@ -33,7 +32,7 @@ def test_concretize_all_test_dependencies(unify):
@pytest.mark.parametrize("unify", unification_strategies)
-def test_concretize_root_test_dependencies_not_recursive(unify):
+def test_concretize_root_test_dependencies_not_recursive(unify, mutable_mock_env_path):
"""Check that test dependencies are not concretized recursively."""
env("create", "test")
@@ -45,7 +44,7 @@ def test_concretize_root_test_dependencies_not_recursive(unify):
@pytest.mark.parametrize("unify", unification_strategies)
-def test_concretize_root_test_dependencies_are_concretized(unify):
+def test_concretize_root_test_dependencies_are_concretized(unify, mutable_mock_env_path):
"""Check that root test dependencies are concretized."""
env("create", "test")
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index e757384b8f..ce7862cd12 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -3222,3 +3222,20 @@ def test_relative_view_path_on_command_line_is_made_absolute(tmpdir, config):
env("create", "--with-view", "view", "--dir", "env")
environment = ev.Environment(os.path.join(".", "env"))
assert os.path.samefile("view", environment.default_view.root)
+
+
+def test_environment_created_in_users_location(mutable_config, tmpdir):
+ """Test that an environment is created in a location based on the config"""
+ spack.config.set("config:environments_root", str(tmpdir.join("envs")))
+ env_dir = spack.config.get("config:environments_root")
+
+ assert tmpdir.strpath in env_dir
+ assert not os.path.isdir(env_dir)
+
+ dir_name = "user_env"
+ env("create", dir_name)
+ out = env("list")
+
+ assert dir_name in out
+ assert env_dir in ev.root(dir_name)
+ assert os.path.isdir(os.path.join(env_dir, dir_name))
diff --git a/lib/spack/spack/test/cmd/uninstall.py b/lib/spack/spack/test/cmd/uninstall.py
index a4b6410ef8..50bdf63d46 100644
--- a/lib/spack/spack/test/cmd/uninstall.py
+++ b/lib/spack/spack/test/cmd/uninstall.py
@@ -225,7 +225,7 @@ class TestUninstallFromEnv(object):
concretize = SpackCommand("concretize")
find = SpackCommand("find")
- @pytest.fixture
+ @pytest.fixture(scope="function")
def environment_setup(
self, mutable_mock_env_path, config, mock_packages, mutable_database, install_mockery
):
@@ -244,6 +244,9 @@ class TestUninstallFromEnv(object):
TestUninstallFromEnv.add("diamond-link-bottom")
TestUninstallFromEnv.concretize()
install("--fake")
+ yield "environment_setup"
+ TestUninstallFromEnv.env("rm", "e1", "-y")
+ TestUninstallFromEnv.env("rm", "e2", "-y")
def test_basic_env_sanity(self, environment_setup):
for env_name in ["e1", "e2"]:
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index 9a7b3a6a9a..b32ea4b4f8 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -1535,14 +1535,14 @@ def mock_svn_repository(tmpdir_factory):
yield t
-@pytest.fixture()
-def mutable_mock_env_path(tmpdir_factory):
+@pytest.fixture(scope="function")
+def mutable_mock_env_path(tmpdir_factory, mutable_config):
"""Fixture for mocking the internal spack environments directory."""
- saved_path = ev.environment.env_path
+ saved_path = ev.environment.default_env_path
mock_path = tmpdir_factory.mktemp("mock-env-path")
- ev.environment.env_path = str(mock_path)
+ ev.environment.default_env_path = str(mock_path)
yield mock_path
- ev.environment.env_path = saved_path
+ ev.environment.default_env_path = saved_path
@pytest.fixture()
diff --git a/lib/spack/spack/test/env.py b/lib/spack/spack/test/env.py
index 83538e7d71..8614a92813 100644
--- a/lib/spack/spack/test/env.py
+++ b/lib/spack/spack/test/env.py
@@ -143,3 +143,21 @@ def test_user_view_path_is_not_canonicalized_in_yaml(tmpdir, config):
snd = ev.Environment(env_path)
assert snd.yaml["spack"]["view"] == view
assert os.path.samefile(snd.default_view.root, absolute_view)
+
+
+def test_environment_cant_modify_environments_root(tmpdir):
+ filename = str(tmpdir.join("spack.yaml"))
+ with open(filename, "w") as f:
+ f.write(
+ """\
+ spack:
+ config:
+ environments_root: /a/black/hole
+ view: false
+ specs: []
+ """
+ )
+ with tmpdir.as_cwd():
+ with pytest.raises(ev.SpackEnvironmentError):
+ e = ev.Environment(tmpdir.strpath)
+ ev.activate(e)