summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Scheibel <scheibel1@llnl.gov>2021-04-12 02:19:29 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2021-05-22 11:51:20 -0700
commitf1f94ad31abbcbff14fdbb200e86b81cdf339c82 (patch)
tree4558144b878dacfd095d637ca62b85d10adb92cc
parent2655e21bd00ff10ea20b447eb067c6411e52b3e8 (diff)
downloadspack-f1f94ad31abbcbff14fdbb200e86b81cdf339c82.tar.gz
spack-f1f94ad31abbcbff14fdbb200e86b81cdf339c82.tar.bz2
spack-f1f94ad31abbcbff14fdbb200e86b81cdf339c82.tar.xz
spack-f1f94ad31abbcbff14fdbb200e86b81cdf339c82.zip
Externals with merged prefixes (#22653)
We remove system paths from search variables like PATH and from -L options because they may contain many packages and could interfere with Spack-built packages. External packages may be installed to prefixes that are not actually system paths but are still "merged" in the sense that many other packages are installed there. To avoid conflicts, this PR places all external packages at the end of search paths.
-rw-r--r--lib/spack/spack/build_environment.py62
-rw-r--r--lib/spack/spack/test/build_environment.py40
2 files changed, 85 insertions, 17 deletions
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 48ce594a4b..3cf02dcbb4 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -302,6 +302,19 @@ def set_compiler_environment_variables(pkg, env):
return env
+def _place_externals_last(spec_container):
+ """
+ For a (possibly unordered) container of specs, return an ordered list
+ where all external specs are at the end of the list. External packages
+ may be installed in merged prefixes with other packages, and so
+ they should be deprioritized for any search order (i.e. in PATH, or
+ for a set of -L entries in a compiler invocation).
+ """
+ first = list(x for x in spec_container if not x.external)
+ second = list(x for x in spec_container if x.external)
+ return first + second
+
+
def set_build_environment_variables(pkg, env, dirty):
"""Ensure a clean install environment when we build packages.
@@ -319,6 +332,29 @@ def set_build_environment_variables(pkg, env, dirty):
link_deps = set(pkg.spec.traverse(root=False, deptype=('link')))
build_link_deps = build_deps | link_deps
rpath_deps = get_rpath_deps(pkg)
+ # This includes all build dependencies and any other dependencies that
+ # should be added to PATH (e.g. supporting executables run by build
+ # dependencies)
+ build_and_supporting_deps = set()
+ for build_dep in build_deps:
+ build_and_supporting_deps.update(build_dep.traverse(deptype='run'))
+
+ # Establish an arbitrary but fixed ordering of specs so that resulting
+ # environment variable values are stable
+ def _order(specs):
+ return sorted(specs, key=lambda x: x.name)
+
+ # External packages may be installed in a prefix which contains many other
+ # package installs. To avoid having those installations override
+ # Spack-installed packages, they are placed at the end of search paths.
+ # System prefixes are removed entirely later on since they are already
+ # searched.
+ build_deps = _place_externals_last(_order(build_deps))
+ link_deps = _place_externals_last(_order(link_deps))
+ build_link_deps = _place_externals_last(_order(build_link_deps))
+ rpath_deps = _place_externals_last(_order(rpath_deps))
+ build_and_supporting_deps = _place_externals_last(
+ _order(build_and_supporting_deps))
link_dirs = []
include_dirs = []
@@ -365,21 +401,10 @@ def set_build_environment_variables(pkg, env, dirty):
env.set(SPACK_INCLUDE_DIRS, ':'.join(include_dirs))
env.set(SPACK_RPATH_DIRS, ':'.join(rpath_dirs))
- build_prefixes = [dep.prefix for dep in build_deps]
- build_link_prefixes = [dep.prefix for dep in build_link_deps]
-
- # add run-time dependencies of direct build-time dependencies:
- for build_dep in build_deps:
- for run_dep in build_dep.traverse(deptype='run'):
- build_prefixes.append(run_dep.prefix)
-
- # Filter out system paths: ['/', '/usr', '/usr/local']
- # These paths can be introduced into the build when an external package
- # is added as a dependency. The problem with these paths is that they often
- # contain hundreds of other packages installed in the same directory.
- # If these paths come first, they can overshadow Spack installations.
- build_prefixes = filter_system_paths(build_prefixes)
- build_link_prefixes = filter_system_paths(build_link_prefixes)
+ build_and_supporting_prefixes = filter_system_paths(
+ x.prefix for x in build_and_supporting_deps)
+ build_link_prefixes = filter_system_paths(
+ x.prefix for x in build_link_deps)
# Add dependencies to CMAKE_PREFIX_PATH
env.set_path('CMAKE_PREFIX_PATH', build_link_prefixes)
@@ -394,7 +419,10 @@ def set_build_environment_variables(pkg, env, dirty):
env.set('SPACK_COMPILER_EXTRA_RPATHS', extra_rpaths)
# Add bin directories from dependencies to the PATH for the build.
- for prefix in build_prefixes:
+ # These directories are added to the beginning of the search path, and in
+ # the order given by 'build_and_supporting_prefixes' (the iteration order
+ # is reversed because each entry is prepended)
+ for prefix in reversed(build_and_supporting_prefixes):
for dirname in ['bin', 'bin64']:
bin_dir = os.path.join(prefix, dirname)
if os.path.isdir(bin_dir):
@@ -439,7 +467,7 @@ def set_build_environment_variables(pkg, env, dirty):
env.set(SPACK_CCACHE_BINARY, ccache)
# Add any pkgconfig directories to PKG_CONFIG_PATH
- for prefix in build_link_prefixes:
+ for prefix in reversed(build_link_prefixes):
for directory in ('lib', 'lib64', 'share'):
pcdir = os.path.join(prefix, directory, 'pkgconfig')
if os.path.isdir(pcdir):
diff --git a/lib/spack/spack/test/build_environment.py b/lib/spack/spack/test/build_environment.py
index a7a55af0ca..b374a7c8ce 100644
--- a/lib/spack/spack/test/build_environment.py
+++ b/lib/spack/spack/test/build_environment.py
@@ -11,6 +11,7 @@ import pytest
import spack.build_environment
import spack.config
import spack.spec
+import spack.util.spack_yaml as syaml
from spack.paths import build_env_path
from spack.build_environment import dso_suffix, _static_to_shared_library
from spack.util.executable import Executable
@@ -299,6 +300,45 @@ def test_set_build_environment_variables(
delattr(dep_pkg, 'libs')
+def test_external_prefixes_last(mutable_config, mock_packages, working_env,
+ monkeypatch):
+ # Sanity check: under normal circumstances paths associated with
+ # dt-diamond-left would appear first. We'll mark it as external in
+ # the test to check if the associated paths are placed last.
+ assert 'dt-diamond-left' < 'dt-diamond-right'
+
+ cfg_data = syaml.load_config("""\
+dt-diamond-left:
+ externals:
+ - spec: dt-diamond-left@1.0
+ prefix: /fake/path1
+ buildable: false
+""")
+ spack.config.set("packages", cfg_data)
+ top = spack.spec.Spec('dt-diamond').concretized()
+
+ def _trust_me_its_a_dir(path):
+ return True
+ monkeypatch.setattr(
+ os.path, 'isdir', _trust_me_its_a_dir
+ )
+
+ env_mods = EnvironmentModifications()
+ spack.build_environment.set_build_environment_variables(
+ top.package, env_mods, False)
+
+ env_mods.apply_modifications()
+ link_dir_var = os.environ['SPACK_LINK_DIRS']
+ link_dirs = link_dir_var.split(':')
+ external_lib_paths = set(['/fake/path1/lib', '/fake/path1/lib64'])
+ # The external lib paths should be the last two entries of the list and
+ # should not appear anywhere before the last two entries
+ assert (set(os.path.normpath(x) for x in link_dirs[-2:]) ==
+ external_lib_paths)
+ assert not (set(os.path.normpath(x) for x in link_dirs[:-2]) &
+ external_lib_paths)
+
+
def test_parallel_false_is_not_propagating(config, mock_packages):
class AttributeHolder(object):
pass