Windows rpath support (#31930)
Add a post-install step which runs (only) on Windows to modify an install prefix, adding symlinks to all dependency libraries. Windows does not have the same concept of RPATHs as Linux, but when resolving symbols will check the local directory for dependency libraries; by placing a symlink to each dependency library in the directory with the library that needs it, the package can then use all Spack-built dependencies. Note: * This collects dependency libraries based on Package.rpath, which includes only direct link dependencies * There is no examination of libraries to check what dependencies they require, so all libraries of dependencies are symlinked into any directory of the package which contains libraries
This commit is contained in:
@@ -84,6 +84,9 @@
|
||||
#: queue invariants).
|
||||
STATUS_REMOVED = "removed"
|
||||
|
||||
is_windows = sys.platform == "win32"
|
||||
is_osx = sys.platform == "darwin"
|
||||
|
||||
|
||||
class InstallAction(object):
|
||||
#: Don't perform an install
|
||||
@@ -165,7 +168,9 @@ def _do_fake_install(pkg):
|
||||
if not pkg.name.startswith("lib"):
|
||||
library = "lib" + library
|
||||
|
||||
dso_suffix = ".dylib" if sys.platform == "darwin" else ".so"
|
||||
plat_shared = ".dll" if is_windows else ".so"
|
||||
plat_static = ".lib" if is_windows else ".a"
|
||||
dso_suffix = ".dylib" if is_osx else plat_shared
|
||||
|
||||
# Install fake command
|
||||
fs.mkdirp(pkg.prefix.bin)
|
||||
@@ -180,7 +185,7 @@ def _do_fake_install(pkg):
|
||||
|
||||
# Install fake shared and static libraries
|
||||
fs.mkdirp(pkg.prefix.lib)
|
||||
for suffix in [dso_suffix, ".a"]:
|
||||
for suffix in [dso_suffix, plat_static]:
|
||||
fs.touch(os.path.join(pkg.prefix.lib, library + suffix))
|
||||
|
||||
# Install fake man page
|
||||
@@ -1214,7 +1219,10 @@ def _install_task(self, task):
|
||||
spack.package_base.PackageBase._verbose = spack.build_environment.start_build_process(
|
||||
pkg, build_process, install_args
|
||||
)
|
||||
|
||||
# Currently this is how RPATH-like behavior is achieved on Windows, after install
|
||||
# establish runtime linkage via Windows Runtime link object
|
||||
# Note: this is a no-op on non Windows platforms
|
||||
pkg.windows_establish_runtime_linkage()
|
||||
# Note: PARENT of the build process adds the new package to
|
||||
# the database, so that we don't need to re-read from file.
|
||||
spack.store.db.add(pkg.spec, spack.store.layout, explicit=explicit)
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
_spack_configure_argsfile = "spack-configure-args.txt"
|
||||
|
||||
|
||||
is_windows = sys.platform == "win32"
|
||||
|
||||
|
||||
def preferred_version(pkg):
|
||||
"""
|
||||
Returns a sorted list of the preferred versions of the package.
|
||||
@@ -182,6 +185,30 @@ def copy(self):
|
||||
return other
|
||||
|
||||
|
||||
class WindowsRPathMeta(object):
|
||||
"""Collection of functionality surrounding Windows RPATH specific features
|
||||
|
||||
This is essentially meaningless for all other platforms
|
||||
due to their use of RPATH. All methods within this class are no-ops on
|
||||
non Windows. Packages can customize and manipulate this class as
|
||||
they would a genuine RPATH, i.e. adding directories that contain
|
||||
runtime library dependencies"""
|
||||
|
||||
def add_search_paths(self, *path):
|
||||
"""Add additional rpaths that are not implicitly included in the search
|
||||
scheme
|
||||
"""
|
||||
self.win_rpath.include_additional_link_paths(*path)
|
||||
|
||||
def windows_establish_runtime_linkage(self):
|
||||
"""Establish RPATH on Windows
|
||||
|
||||
Performs symlinking to incorporate rpath dependencies to Windows runtime search paths
|
||||
"""
|
||||
if is_windows:
|
||||
self.win_rpath.establish_link()
|
||||
|
||||
|
||||
#: Registers which are the detectable packages, by repo and package name
|
||||
#: Need a pass of package repositories to be filled.
|
||||
detectable_packages = collections.defaultdict(list)
|
||||
@@ -221,7 +248,7 @@ def to_windows_exe(exe):
|
||||
plat_exe = []
|
||||
if hasattr(cls, "executables"):
|
||||
for exe in cls.executables:
|
||||
if sys.platform == "win32":
|
||||
if is_windows:
|
||||
exe = to_windows_exe(exe)
|
||||
plat_exe.append(exe)
|
||||
return plat_exe
|
||||
@@ -513,7 +540,7 @@ def test_log_pathname(test_stage, spec):
|
||||
return os.path.join(test_stage, "test-{0}-out.txt".format(TestSuite.test_pkg_id(spec)))
|
||||
|
||||
|
||||
class PackageBase(six.with_metaclass(PackageMeta, PackageViewMixin, object)):
|
||||
class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewMixin, object)):
|
||||
"""This is the superclass for all spack packages.
|
||||
|
||||
***The Package class***
|
||||
@@ -753,6 +780,8 @@ def __init__(self, spec):
|
||||
# Set up timing variables
|
||||
self._fetch_time = 0.0
|
||||
|
||||
self.win_rpath = fsys.WindowsSimulatedRPath(self)
|
||||
|
||||
if self.is_extension:
|
||||
pkg_cls = spack.repo.path.get_pkg_class(self.extendee_spec.name)
|
||||
pkg_cls(self.extendee_spec)._check_extendable()
|
||||
@@ -2754,6 +2783,8 @@ def rpath(self):
|
||||
deps = self.spec.dependencies(deptype="link")
|
||||
rpaths.extend(d.prefix.lib for d in deps if os.path.isdir(d.prefix.lib))
|
||||
rpaths.extend(d.prefix.lib64 for d in deps if os.path.isdir(d.prefix.lib64))
|
||||
if is_windows:
|
||||
rpaths.extend(d.prefix.bin for d in deps if os.path.isdir(d.prefix.bin))
|
||||
return rpaths
|
||||
|
||||
@property
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
import llnl.util.lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import mkdirp, remove_linked_tree, working_dir
|
||||
from llnl.util.filesystem import copy_tree, mkdirp, remove_linked_tree, working_dir
|
||||
|
||||
import spack.binary_distribution
|
||||
import spack.caches
|
||||
@@ -803,7 +803,7 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration_scopes, _store
|
||||
with spack.store.use_store(str(store_path)) as store:
|
||||
with spack.repo.use_repositories(mock_repo_path):
|
||||
_populate(store.db)
|
||||
store_path.copy(store_cache, mode=True, stat=True)
|
||||
copy_tree(str(store_path), str(store_cache))
|
||||
|
||||
# Make the DB filesystem read-only to ensure we can't modify entries
|
||||
store_path.join(".spack-db").chmod(mode=0o555, rec=1)
|
||||
@@ -844,7 +844,7 @@ def mutable_database(database_mutable_config, _store_dir_and_cache):
|
||||
# Restore the initial state by copying the content of the cache back into
|
||||
# the store and making the database read-only
|
||||
store_path.remove(rec=1)
|
||||
store_cache.copy(store_path, mode=True, stat=True)
|
||||
copy_tree(str(store_cache), str(store_path))
|
||||
store_path.join(".spack-db").chmod(mode=0o555, rec=1)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -85,12 +86,11 @@ def test_pkg_attributes(install_mockery, mock_fetch, monkeypatch):
|
||||
# assert baz_headers.basenames == ['baz.h']
|
||||
assert baz_headers.directories == [spec["baz"].home.include]
|
||||
|
||||
if "platform=windows" in spec:
|
||||
lib_suffix = ".lib"
|
||||
elif "platform=darwin" in spec:
|
||||
lib_suffix = ".so"
|
||||
if sys.platform == "win32":
|
||||
lib_suffix = ".dll"
|
||||
elif sys.platform == "darwin":
|
||||
lib_suffix = ".dylib"
|
||||
else:
|
||||
lib_suffix = ".so"
|
||||
|
||||
foo_libs = spec[foo].libs
|
||||
assert foo_libs.basenames == ["libFoo" + lib_suffix]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import fnmatch
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import six
|
||||
@@ -19,18 +20,30 @@
|
||||
|
||||
import spack.paths
|
||||
|
||||
is_windows = sys.platform == "win32"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def library_list():
|
||||
"""Returns an instance of LibraryList."""
|
||||
# Test all valid extensions: ['.a', '.dylib', '.so']
|
||||
libs = [
|
||||
"/dir1/liblapack.a",
|
||||
"/dir2/libpython3.6.dylib", # name may contain periods
|
||||
"/dir1/libblas.a",
|
||||
"/dir3/libz.so",
|
||||
"libmpi.so.20.10.1", # shared object libraries may be versioned
|
||||
]
|
||||
libs = (
|
||||
[
|
||||
"/dir1/liblapack.a",
|
||||
"/dir2/libpython3.6.dylib", # name may contain periods
|
||||
"/dir1/libblas.a",
|
||||
"/dir3/libz.so",
|
||||
"libmpi.so.20.10.1", # shared object libraries may be versioned
|
||||
]
|
||||
if not is_windows
|
||||
else [
|
||||
"/dir1/liblapack.lib",
|
||||
"/dir2/libpython3.6.dll",
|
||||
"/dir1/libblas.lib",
|
||||
"/dir3/libz.dll",
|
||||
"libmpi.dll.20.10.1",
|
||||
]
|
||||
)
|
||||
|
||||
return LibraryList(libs)
|
||||
|
||||
@@ -52,6 +65,16 @@ def header_list():
|
||||
return h
|
||||
|
||||
|
||||
# TODO: Remove below when llnl.util.filesystem.find_libraries becomes spec aware
|
||||
plat_static_ext = "lib" if is_windows else "a"
|
||||
|
||||
|
||||
plat_shared_ext = "dll" if is_windows else "so"
|
||||
|
||||
|
||||
plat_apple_shared_ext = "dll" if is_windows else "dylib"
|
||||
|
||||
|
||||
class TestLibraryList(object):
|
||||
def test_repr(self, library_list):
|
||||
x = eval(repr(library_list))
|
||||
@@ -62,11 +85,11 @@ def test_joined_and_str(self, library_list):
|
||||
s1 = library_list.joined()
|
||||
expected = " ".join(
|
||||
[
|
||||
"/dir1/liblapack.a",
|
||||
"/dir2/libpython3.6.dylib",
|
||||
"/dir1/libblas.a",
|
||||
"/dir3/libz.so",
|
||||
"libmpi.so.20.10.1",
|
||||
"/dir1/liblapack.%s" % plat_static_ext,
|
||||
"/dir2/libpython3.6.%s" % plat_apple_shared_ext,
|
||||
"/dir1/libblas.%s" % plat_static_ext,
|
||||
"/dir3/libz.%s" % plat_shared_ext,
|
||||
"libmpi.%s.20.10.1" % plat_shared_ext,
|
||||
]
|
||||
)
|
||||
assert s1 == expected
|
||||
@@ -77,11 +100,11 @@ def test_joined_and_str(self, library_list):
|
||||
s3 = library_list.joined(";")
|
||||
expected = ";".join(
|
||||
[
|
||||
"/dir1/liblapack.a",
|
||||
"/dir2/libpython3.6.dylib",
|
||||
"/dir1/libblas.a",
|
||||
"/dir3/libz.so",
|
||||
"libmpi.so.20.10.1",
|
||||
"/dir1/liblapack.%s" % plat_static_ext,
|
||||
"/dir2/libpython3.6.%s" % plat_apple_shared_ext,
|
||||
"/dir1/libblas.%s" % plat_static_ext,
|
||||
"/dir3/libz.%s" % plat_shared_ext,
|
||||
"libmpi.%s.20.10.1" % plat_shared_ext,
|
||||
]
|
||||
)
|
||||
assert s3 == expected
|
||||
@@ -117,7 +140,7 @@ def test_paths_manipulation(self, library_list):
|
||||
|
||||
def test_get_item(self, library_list):
|
||||
a = library_list[0]
|
||||
assert a == "/dir1/liblapack.a"
|
||||
assert a == "/dir1/liblapack.%s" % plat_static_ext
|
||||
|
||||
b = library_list[:]
|
||||
assert type(b) == type(library_list)
|
||||
@@ -126,9 +149,9 @@ def test_get_item(self, library_list):
|
||||
|
||||
def test_add(self, library_list):
|
||||
pylist = [
|
||||
"/dir1/liblapack.a", # removed from the final list
|
||||
"/dir2/libmpi.so",
|
||||
"/dir4/libnew.a",
|
||||
"/dir1/liblapack.%s" % plat_static_ext, # removed from the final list
|
||||
"/dir2/libmpi.%s" % plat_shared_ext,
|
||||
"/dir4/libnew.%s" % plat_static_ext,
|
||||
]
|
||||
another = LibraryList(pylist)
|
||||
both = library_list + another
|
||||
|
||||
Reference in New Issue
Block a user