summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/conf.py1
-rw-r--r--lib/spack/spack/environment/environment.py55
-rw-r--r--lib/spack/spack/hooks/module_file_generation.py13
-rw-r--r--lib/spack/spack/modules/common.py43
-rw-r--r--lib/spack/spack/test/cmd/env.py56
-rw-r--r--lib/spack/spack/user_environment.py27
6 files changed, 91 insertions, 104 deletions
diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py
index 64a55b3151..8f52edb89c 100644
--- a/lib/spack/docs/conf.py
+++ b/lib/spack/docs/conf.py
@@ -215,6 +215,7 @@ nitpick_ignore = [
("py:class", "spack.spec.InstallStatus"),
("py:class", "spack.spec.SpecfileReaderBase"),
("py:class", "spack.install_test.Pb"),
+ ("py:class", "spack.filesystem_view.SimpleFilesystemView"),
]
# The reST default role (used for this markup: `text`) to use for all documents.
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index 25e2360fdc..2a6cc6e774 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -611,39 +611,33 @@ class ViewDescriptor:
return spack.util.hash.b32_hash(contents)
def get_projection_for_spec(self, spec):
- """Get projection for spec relative to view root
+ """Get projection for spec. This function does not require the view
+ to exist on the filesystem."""
+ return self._view(self.root).get_projection_for_spec(spec)
- Getting the projection from the underlying root will get the temporary
- projection. This gives the permanent projection relative to the root
- symlink.
+ def view(self, new: Optional[str] = None) -> SimpleFilesystemView:
"""
- view = self.view()
- view_path = view.get_projection_for_spec(spec)
- rel_path = os.path.relpath(view_path, self._current_root)
- return os.path.join(self.root, rel_path)
-
- def view(self, new=None):
- """
- Generate the FilesystemView object for this ViewDescriptor
-
- By default, this method returns a FilesystemView object rooted at the
- current underlying root of this ViewDescriptor (self._current_root)
+ Returns a view object for the *underlying* view directory. This means that the
+ self.root symlink is followed, and that the view has to exist on the filesystem
+ (unless ``new``). This function is useful when writing to the view.
Raise if new is None and there is no current view
Arguments:
- new (str or None): If a string, create a FilesystemView
- rooted at that path. Default None. This should only be used to
- regenerate the view, and cannot be used to access specs.
+ new: If a string, create a FilesystemView rooted at that path. Default None. This
+ should only be used to regenerate the view, and cannot be used to access specs.
"""
root = new if new else self._current_root
if not root:
# This can only be hit if we write a future bug
- msg = (
+ raise SpackEnvironmentViewError(
"Attempting to get nonexistent view from environment. "
- "View root is at %s" % self.root
+ f"View root is at {self.root}"
)
- raise SpackEnvironmentViewError(msg)
+ return self._view(root)
+
+ def _view(self, root: str) -> SimpleFilesystemView:
+ """Returns a view object for a given root dir."""
return SimpleFilesystemView(
root,
spack.store.STORE.layout,
@@ -810,7 +804,6 @@ class Environment:
self._unify = None
self.new_specs: List[Spec] = []
- self.new_installs: List[Spec] = []
self.views: Dict[str, ViewDescriptor] = {}
#: Specs from "spack.yaml"
@@ -939,10 +932,8 @@ class Environment:
"""Clear the contents of the environment
Arguments:
- re_read (bool): If ``True``, do not clear ``new_specs`` nor
- ``new_installs`` values. These values cannot be read from
- yaml, and need to be maintained when re-reading an existing
- environment.
+ re_read: If ``True``, do not clear ``new_specs``. This value cannot be read from yaml,
+ and needs to be maintained when re-reading an existing environment.
"""
self.spec_lists = collections.OrderedDict()
self.spec_lists[user_speclist_name] = SpecList()
@@ -956,7 +947,6 @@ class Environment:
if not re_read:
# things that cannot be recreated from file
self.new_specs = [] # write packages for these on write()
- self.new_installs = [] # write modules for these on write()
@property
def active(self):
@@ -1751,10 +1741,7 @@ class Environment:
installs = [(spec.package, {**install_args, "explicit": spec in roots}) for spec in specs]
- try:
- PackageInstaller(installs).install()
- finally:
- self.new_installs.extend(s for s in specs if s.installed)
+ PackageInstaller(installs).install()
def all_specs_generator(self) -> Iterable[Spec]:
"""Returns a generator for all concrete specs"""
@@ -2077,11 +2064,7 @@ class Environment:
self.regenerate_views()
spack.hooks.post_env_write(self)
- self._reset_new_specs_and_installs()
-
- def _reset_new_specs_and_installs(self) -> None:
- self.new_specs = []
- self.new_installs = []
+ self.new_specs.clear()
def update_lockfile(self) -> None:
with fs.write_tmp_and_move(self.lock_path) as f:
diff --git a/lib/spack/spack/hooks/module_file_generation.py b/lib/spack/spack/hooks/module_file_generation.py
index cb51a4eb85..485861d27c 100644
--- a/lib/spack/spack/hooks/module_file_generation.py
+++ b/lib/spack/spack/hooks/module_file_generation.py
@@ -34,21 +34,8 @@ def _for_each_enabled(
def post_install(spec, explicit: bool):
- import spack.environment as ev # break import cycle
-
- if ev.active_environment():
- # If the installed through an environment, we skip post_install
- # module generation and generate the modules on env_write so Spack
- # can manage interactions between env views and modules
- return
-
_for_each_enabled(spec, "write", explicit)
def post_uninstall(spec):
_for_each_enabled(spec, "remove")
-
-
-def post_env_write(env):
- for spec in env.new_installs:
- _for_each_enabled(spec, "write")
diff --git a/lib/spack/spack/modules/common.py b/lib/spack/spack/modules/common.py
index 1246403c89..a0b9870f12 100644
--- a/lib/spack/spack/modules/common.py
+++ b/lib/spack/spack/modules/common.py
@@ -43,6 +43,7 @@ from llnl.util.lang import dedupe, memoized
import spack.build_environment
import spack.config
+import spack.deptypes as dt
import spack.environment
import spack.error
import spack.modules.common
@@ -53,6 +54,7 @@ import spack.schema.environment
import spack.spec
import spack.store
import spack.tengine as tengine
+import spack.user_environment
import spack.util.environment
import spack.util.file_permissions as fp
import spack.util.path
@@ -695,28 +697,33 @@ class BaseContext(tengine.Context):
)
spack.config.merge_yaml(
prefix_inspections,
- spack.config.get("modules:%s:prefix_inspections" % self.conf.name, {}),
+ spack.config.get(f"modules:{self.conf.name}:prefix_inspections", {}),
)
- use_view = spack.config.get("modules:%s:use_view" % self.conf.name, False)
+ use_view = spack.config.get(f"modules:{self.conf.name}:use_view", False)
- spec = self.spec.copy() # defensive copy before setting prefix
- if use_view:
- if use_view is True:
- use_view = spack.environment.default_view_name
+ assert isinstance(use_view, (bool, str))
+ if use_view:
env = spack.environment.active_environment()
if not env:
raise spack.environment.SpackEnvironmentViewError(
"Module generation with views requires active environment"
)
- view = env.views[use_view]
+ view_name = spack.environment.default_view_name if use_view is True else use_view
- spec.prefix = view.get_projection_for_spec(spec)
+ if not env.has_view(view_name):
+ raise spack.environment.SpackEnvironmentViewError(
+ f"View {view_name} not found in environment {env.name} when generating modules"
+ )
+
+ view = env.views[view_name]
+ else:
+ view = None
env = spack.util.environment.inspect_path(
- spec.prefix, prefix_inspections, exclude=spack.util.environment.is_system_path
+ self.spec.prefix, prefix_inspections, exclude=spack.util.environment.is_system_path
)
# Let the extendee/dependency modify their extensions/dependencies
@@ -726,13 +733,19 @@ class BaseContext(tengine.Context):
# whole chain of setup_dependent_package has to be followed from leaf to spec.
# So: just run it here, but don't collect env mods.
spack.build_environment.SetupContext(
- spec, context=Context.RUN
+ self.spec, context=Context.RUN
).set_all_package_py_globals()
# Then run setup_dependent_run_environment before setup_run_environment.
- for dep in spec.dependencies(deptype=("link", "run")):
- dep.package.setup_dependent_run_environment(env, spec)
- spec.package.setup_run_environment(env)
+ for dep in self.spec.dependencies(deptype=("link", "run")):
+ dep.package.setup_dependent_run_environment(env, self.spec)
+ self.spec.package.setup_run_environment(env)
+
+ # Project the environment variables from prefix to view if needed
+ if view and self.spec in view:
+ spack.user_environment.project_env_mods(
+ *self.spec.traverse(deptype=dt.LINK | dt.RUN), view=view, env=env
+ )
# Modifications required from modules.yaml
env.extend(self.conf.env)
@@ -754,11 +767,11 @@ class BaseContext(tengine.Context):
msg = "some tokens cannot be expanded in an environment variable name"
_check_tokens_are_valid(x.name, message=msg)
# Transform them
- x.name = spec.format(x.name, transform=transform)
+ x.name = self.spec.format(x.name, transform=transform)
if self.modification_needs_formatting(x):
try:
# Not every command has a value
- x.value = spec.format(x.value)
+ x.value = self.spec.format(x.value)
except AttributeError:
pass
x.name = str(x.name).replace("-", "_")
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index 74ce4d31d1..c9137e6aea 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -2970,51 +2970,51 @@ spack:
assert spec.prefix not in contents
-def test_multiple_modules_post_env_hook(environment_from_manifest, install_mockery, mock_fetch):
+def test_modules_exist_after_env_install(
+ environment_from_manifest, install_mockery, mock_fetch, monkeypatch
+):
+ # Some caching issue
+ monkeypatch.setattr(spack.modules.tcl, "configuration_registry", {})
environment_from_manifest(
"""
spack:
specs:
- - trivial-install-test-package
+ - mpileaks
modules:
default:
enable:: [tcl]
use_view: true
roots:
- tcl: modules
+ tcl: uses_view
full:
enable:: [tcl]
roots:
- tcl: full_modules
+ tcl: without_view
"""
)
with ev.read("test") as e:
install()
-
- spec = e.specs_by_hash[e.concretized_order[0]]
- view_prefix = e.default_view.get_projection_for_spec(spec)
- modules_glob = "%s/modules/**/*/*" % e.path
- modules = glob.glob(modules_glob)
- assert len(modules) == 1
- module = modules[0]
-
- full_modules_glob = "%s/full_modules/**/*/*" % e.path
- full_modules = glob.glob(full_modules_glob)
- assert len(full_modules) == 1
- full_module = full_modules[0]
-
- with open(module, "r") as f:
- contents = f.read()
-
- with open(full_module, "r") as f:
- full_contents = f.read()
-
- assert view_prefix in contents
- assert spec.prefix not in contents
-
- assert view_prefix not in full_contents
- assert spec.prefix in full_contents
+ specs = e.all_specs()
+
+ for module_set in ("uses_view", "without_view"):
+ modules = glob.glob(f"{e.path}/{module_set}/**/*/*")
+ assert len(modules) == len(specs), "Not all modules were generated"
+ for spec in specs:
+ module = next((m for m in modules if os.path.dirname(m).endswith(spec.name)), None)
+ assert module, f"Module for {spec} not found"
+
+ # Now verify that modules have paths pointing into the view instead of the package
+ # prefix if and only if they set use_view to true.
+ with open(module, "r") as f:
+ contents = f.read()
+
+ if module_set == "uses_view":
+ assert e.default_view.get_projection_for_spec(spec) in contents
+ assert spec.prefix not in contents
+ else:
+ assert e.default_view.get_projection_for_spec(spec) not in contents
+ assert spec.prefix in contents
@pytest.mark.regression("24148")
diff --git a/lib/spack/spack/user_environment.py b/lib/spack/spack/user_environment.py
index 32f3c809c1..756b7c09a2 100644
--- a/lib/spack/spack/user_environment.py
+++ b/lib/spack/spack/user_environment.py
@@ -66,6 +66,20 @@ def unconditional_environment_modifications(view):
return env
+def project_env_mods(
+ *specs: spack.spec.Spec, view, env: environment.EnvironmentModifications
+) -> None:
+ """Given a list of environment modifications, project paths changes to the view."""
+ prefix_to_prefix = {s.prefix: view.get_projection_for_spec(s) for s in specs if not s.external}
+ # Avoid empty regex if all external
+ if not prefix_to_prefix:
+ return
+ prefix_regex = re.compile("|".join(re.escape(p) for p in prefix_to_prefix.keys()))
+ for mod in env.env_modifications:
+ if isinstance(mod, environment.NameValueModifier):
+ mod.value = prefix_regex.sub(lambda m: prefix_to_prefix[m.group(0)], mod.value)
+
+
def environment_modifications_for_specs(
*specs: spack.spec.Spec, view=None, set_package_py_globals: bool = True
):
@@ -101,17 +115,6 @@ def environment_modifications_for_specs(
# Apply view projections if any.
if view:
- prefix_to_prefix = {
- s.prefix: view.get_projection_for_spec(s)
- for s in reversed(topo_ordered)
- if not s.external
- }
- # Avoid empty regex if all external
- if not prefix_to_prefix:
- return env
- prefix_regex = re.compile("|".join(re.escape(p) for p in prefix_to_prefix.keys()))
- for mod in env.env_modifications:
- if isinstance(mod, environment.NameValueModifier):
- mod.value = prefix_regex.sub(lambda m: prefix_to_prefix[m.group(0)], mod.value)
+ project_env_mods(*topo_ordered, view=view, env=env)
return env