From ec2729706b235f96fe481bf6333fbcc7732e28c1 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Tue, 19 Dec 2023 23:33:16 +0100 Subject: 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 `/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 --- lib/spack/spack/user_environment.py | 72 ++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 41 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/user_environment.py b/lib/spack/spack/user_environment.py index 6e1c798e51..895e7b1203 100644 --- a/lib/spack/spack/user_environment.py +++ b/lib/spack/spack/user_environment.py @@ -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 -- cgit v1.2.3-60-g2f50