diff options
author | Peter Scheibel <scheibel1@llnl.gov> | 2021-04-12 02:19:29 -0700 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2021-05-22 11:51:20 -0700 |
commit | f1f94ad31abbcbff14fdbb200e86b81cdf339c82 (patch) | |
tree | 4558144b878dacfd095d637ca62b85d10adb92cc | |
parent | 2655e21bd00ff10ea20b447eb067c6411e52b3e8 (diff) | |
download | spack-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.py | 62 | ||||
-rw-r--r-- | lib/spack/spack/test/build_environment.py | 40 |
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 |