environment_modifications_for_specs: do not mutate spec.prefix (#41737)
Sometimes env variables computed in `setup_run_environment` depend on tests w.r.t. files in `spec.prefix`, but Spack temporarily projects `spec.prefix` to the view. This is problematic for two reasons: 1. Some packages iterate over `<prefix>/bin`: they expect only the current package's executables, but find all linked in the view, leading to false positives. 2. Some packages test for `os.path.islink(...)`, which is always true in a view `gcc` is an example that does both. This PR lets Spack compute the environment modifications using the original prefix, and projects to the view afterwards
This commit is contained in:
parent
494d3f9002
commit
ec2729706b
@ -3,18 +3,14 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import Callable
|
||||
|
||||
from llnl.util.lang import nullcontext
|
||||
|
||||
import spack.build_environment
|
||||
import spack.config
|
||||
import spack.error
|
||||
import spack.spec
|
||||
import spack.util.environment as environment
|
||||
import spack.util.prefix as prefix
|
||||
from spack import traverse
|
||||
from spack.context import Context
|
||||
|
||||
@ -70,22 +66,6 @@ def unconditional_environment_modifications(view):
|
||||
return env
|
||||
|
||||
|
||||
@contextmanager
|
||||
def projected_prefix(*specs: spack.spec.Spec, projection: Callable[[spack.spec.Spec], str]):
|
||||
"""Temporarily replace every Spec's prefix with projection(s)"""
|
||||
prefixes = dict()
|
||||
for s in traverse.traverse_nodes(specs, key=lambda s: s.dag_hash()):
|
||||
if s.external:
|
||||
continue
|
||||
prefixes[s.dag_hash()] = s.prefix
|
||||
s.prefix = prefix.Prefix(projection(s))
|
||||
|
||||
yield
|
||||
|
||||
for s in traverse.traverse_nodes(specs, key=lambda s: s.dag_hash()):
|
||||
s.prefix = prefixes.get(s.dag_hash(), s.prefix)
|
||||
|
||||
|
||||
def environment_modifications_for_specs(
|
||||
*specs: spack.spec.Spec, view=None, set_package_py_globals: bool = True
|
||||
):
|
||||
@ -102,26 +82,36 @@ def environment_modifications_for_specs(
|
||||
been built on a different but compatible OS)
|
||||
"""
|
||||
env = environment.EnvironmentModifications()
|
||||
topo_ordered = traverse.traverse_nodes(specs, root=True, deptype=("run", "link"), order="topo")
|
||||
topo_ordered = list(
|
||||
traverse.traverse_nodes(specs, root=True, deptype=("run", "link"), order="topo")
|
||||
)
|
||||
|
||||
# Static environment changes (prefix inspections)
|
||||
for s in reversed(topo_ordered):
|
||||
static = environment.inspect_path(
|
||||
s.prefix, prefix_inspections(s.platform), exclude=environment.is_system_path
|
||||
)
|
||||
env.extend(static)
|
||||
|
||||
# Dynamic environment changes (setup_run_environment etc)
|
||||
setup_context = spack.build_environment.SetupContext(*specs, context=Context.RUN)
|
||||
if set_package_py_globals:
|
||||
setup_context.set_all_package_py_globals()
|
||||
env.extend(setup_context.get_env_modifications())
|
||||
|
||||
# Apply view projections if any.
|
||||
if view:
|
||||
maybe_projected = projected_prefix(*specs, projection=view.get_projection_for_spec)
|
||||
else:
|
||||
maybe_projected = nullcontext()
|
||||
|
||||
with maybe_projected:
|
||||
# Static environment changes (prefix inspections)
|
||||
for s in reversed(list(topo_ordered)):
|
||||
static = environment.inspect_path(
|
||||
s.prefix, prefix_inspections(s.platform), exclude=environment.is_system_path
|
||||
)
|
||||
env.extend(static)
|
||||
|
||||
# Dynamic environment changes (setup_run_environment etc)
|
||||
setup_context = spack.build_environment.SetupContext(*specs, context=Context.RUN)
|
||||
if set_package_py_globals:
|
||||
setup_context.set_all_package_py_globals()
|
||||
dynamic = setup_context.get_env_modifications()
|
||||
env.extend(dynamic)
|
||||
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)
|
||||
|
||||
return env
|
||||
|
Loading…
Reference in New Issue
Block a user