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:
		@@ -3,18 +3,14 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
					# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from contextlib import contextmanager
 | 
					 | 
				
			||||||
from typing import Callable
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from llnl.util.lang import nullcontext
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import spack.build_environment
 | 
					import spack.build_environment
 | 
				
			||||||
import spack.config
 | 
					import spack.config
 | 
				
			||||||
import spack.error
 | 
					import spack.error
 | 
				
			||||||
import spack.spec
 | 
					import spack.spec
 | 
				
			||||||
import spack.util.environment as environment
 | 
					import spack.util.environment as environment
 | 
				
			||||||
import spack.util.prefix as prefix
 | 
					 | 
				
			||||||
from spack import traverse
 | 
					from spack import traverse
 | 
				
			||||||
from spack.context import Context
 | 
					from spack.context import Context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,22 +66,6 @@ def unconditional_environment_modifications(view):
 | 
				
			|||||||
    return env
 | 
					    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(
 | 
					def environment_modifications_for_specs(
 | 
				
			||||||
    *specs: spack.spec.Spec, view=None, set_package_py_globals: bool = True
 | 
					    *specs: spack.spec.Spec, view=None, set_package_py_globals: bool = True
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
@@ -102,16 +82,12 @@ def environment_modifications_for_specs(
 | 
				
			|||||||
            been built on a different but compatible OS)
 | 
					            been built on a different but compatible OS)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    env = environment.EnvironmentModifications()
 | 
					    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")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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)
 | 
					    # Static environment changes (prefix inspections)
 | 
				
			||||||
        for s in reversed(list(topo_ordered)):
 | 
					    for s in reversed(topo_ordered):
 | 
				
			||||||
        static = environment.inspect_path(
 | 
					        static = environment.inspect_path(
 | 
				
			||||||
            s.prefix, prefix_inspections(s.platform), exclude=environment.is_system_path
 | 
					            s.prefix, prefix_inspections(s.platform), exclude=environment.is_system_path
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@@ -121,7 +97,21 @@ def environment_modifications_for_specs(
 | 
				
			|||||||
    setup_context = spack.build_environment.SetupContext(*specs, context=Context.RUN)
 | 
					    setup_context = spack.build_environment.SetupContext(*specs, context=Context.RUN)
 | 
				
			||||||
    if set_package_py_globals:
 | 
					    if set_package_py_globals:
 | 
				
			||||||
        setup_context.set_all_package_py_globals()
 | 
					        setup_context.set_all_package_py_globals()
 | 
				
			||||||
        dynamic = setup_context.get_env_modifications()
 | 
					    env.extend(setup_context.get_env_modifications())
 | 
				
			||||||
        env.extend(dynamic)
 | 
					
 | 
				
			||||||
 | 
					    # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return env
 | 
					    return env
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user