Allow users to specify root env dir

Environments managed by spack have some advantages over anonymous Environments
but they are tucked away inside spack's directory tree. This PR gives
users the ability to specify where the environments should live.

See #32823
This commit is contained in:
psakiev 2022-09-26 22:31:29 -06:00
parent c25b7ea898
commit 3f2e77e5fa
6 changed files with 51 additions and 21 deletions

View File

@ -76,6 +76,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

View File

@ -63,7 +63,10 @@
#: path where environments are stored in the spack tree
env_path = os.path.join(spack.paths.var_path, "environments")
# circular dependency to use canonicalize_path but we want to ignore an active env anyways
# env_path = spack.config.get(os.path.expandvars(os.path.expanduser("config:environments_root")))
def env_path():
return spack.util.path.canonicalize_path(spack.config.get("config:environments_root"), False)
#: Name of the input yaml file for an environment
@ -205,7 +208,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_path(), name)
def root(name):
@ -257,10 +260,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_path()):
return []
candidates = sorted(os.listdir(env_path))
candidates = sorted(os.listdir(env_path()))
names = []
for candidate in candidates:
yaml_path = os.path.join(_root(candidate), manifest_name)
@ -842,7 +845,7 @@ def clear(self, re_read=False):
@property
def internal(self):
"""Whether this environment is managed by Spack."""
return self.path.startswith(env_path)
return self.path.startswith(env_path())
@property
def name(self):

View File

@ -52,6 +52,7 @@
"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"},

View File

@ -28,6 +28,9 @@
import spack.schema.repos
import spack.util.path as spack_path
import spack.util.spack_yaml as syaml
from spack.main import SpackCommand
env = SpackCommand("env")
# sample config data
config_low = {
@ -1005,6 +1008,7 @@ def test_good_env_yaml(tmpdir):
config:
verify_ssl: False
dirty: False
environtments_dir: ~/my/env/location
repos:
- ~/my/repo/location
mirrors:
@ -1431,3 +1435,18 @@ def test_config_file_read_invalid_yaml(tmpdir, mutable_empty_config):
with pytest.raises(spack.config.ConfigFileError, match="parsing yaml"):
spack.config.read_config_file(filename)
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)
os.makedirs(env_dir)
env('create', 'test')
out = env('list')
assert 'test' in out
assert env_dir in ev.root('test')
assert os.path.isdir(os.path.join(env_dir, 'test'))

View File

@ -1493,11 +1493,13 @@ def get_rev():
@pytest.fixture()
def mutable_mock_env_path(tmpdir_factory):
"""Fixture for mocking the internal spack environments directory."""
saved_path = ev.environment.env_path
saved_path_fun = ev.environment.env_path
mock_path = tmpdir_factory.mktemp("mock-env-path")
ev.environment.env_path = str(mock_path)
def mock_fun():
return str(mock_path)
ev.environment.env_path = mock_fun
yield mock_path
ev.environment.env_path = saved_path
ev.environment.env_path = saved_path_fun
@pytest.fixture()

View File

@ -226,7 +226,7 @@ def convert_to_platform_path(path):
return format_os_path(path, mode=Path.platform_path)
def substitute_config_variables(path):
def substitute_config_variables(path, allow_env=True):
"""Substitute placeholders into paths.
Spack allows paths in configs to have some placeholders, as follows:
@ -242,15 +242,16 @@ def substitute_config_variables(path):
replaced if there is an active environment, and should only be used in
environment yaml files.
"""
import spack.environment as ev # break circular
_replacements = replacements()
env = ev.active_environment()
if env:
_replacements.update({"env": env.path})
else:
# If a previous invocation added env, remove it
_replacements.pop("env", None)
if allow_env:
import spack.environment as ev # break circular
env = ev.active_environment()
if env:
_replacements.update({"env": env.path})
else:
# If a previous invocation added env, remove it
_replacements.pop("env", None)
# Look up replacements
def repl(match):
@ -261,9 +262,9 @@ def repl(match):
return re.sub(r"(\$\w+\b|\$\{\w+\})", repl, path)
def substitute_path_variables(path):
def substitute_path_variables(path, allow_env = True):
"""Substitute config vars, expand environment vars, expand user home."""
path = substitute_config_variables(path)
path = substitute_config_variables(path, allow_env)
path = os.path.expandvars(path)
path = os.path.expanduser(path)
return path
@ -305,7 +306,7 @@ def add_padding(path, length):
return os.path.join(path, padding)
def canonicalize_path(path):
def canonicalize_path(path, allow_env = True):
"""Same as substitute_path_variables, but also take absolute path.
Arguments:
@ -321,7 +322,7 @@ def canonicalize_path(path):
filename = os.path.dirname(path._start_mark.name)
assert path._start_mark.name == path._end_mark.name
path = substitute_path_variables(path)
path = substitute_path_variables(path, allow_env)
if not os.path.isabs(path):
if filename:
path = os.path.join(filename, path)