summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/spack/relocate.py152
-rw-r--r--lib/spack/spack/test/packaging.py18
-rw-r--r--lib/spack/spack/test/relocate.py54
3 files changed, 148 insertions, 76 deletions
diff --git a/lib/spack/spack/relocate.py b/lib/spack/spack/relocate.py
index 9f8669f3d4..41770103b0 100644
--- a/lib/spack/spack/relocate.py
+++ b/lib/spack/spack/relocate.py
@@ -97,70 +97,102 @@ def _patchelf():
return exe_path if os.path.exists(exe_path) else None
-def get_existing_elf_rpaths(path_name):
- """
- Return the RPATHS returned by patchelf --print-rpath path_name
- as a list of strings.
- """
+def _elf_rpaths_for(path):
+ """Return the RPATHs for an executable or a library.
- # if we're relocating patchelf itself, use it
+ The RPATHs are obtained by ``patchelf --print-rpath PATH``.
- if path_name.endswith("/bin/patchelf"):
- patchelf = executable.Executable(path_name)
- else:
- patchelf = executable.Executable(_patchelf())
+ Args:
+ path (str): full path to the executable or library
- rpaths = list()
+ Return:
+ RPATHs as a list of strings.
+ """
+ # If we're relocating patchelf itself, use it
+ patchelf_path = path if path.endswith("/bin/patchelf") else _patchelf()
+ patchelf = executable.Executable(patchelf_path)
+
+ output = ''
try:
- output = patchelf('--print-rpath', '%s' %
- path_name, output=str, error=str)
- rpaths = output.rstrip('\n').split(':')
+ output = patchelf('--print-rpath', path, output=str, error=str)
+ output = output.strip('\n')
except executable.ProcessError as e:
- msg = 'patchelf --print-rpath %s produced an error %s' % (path_name, e)
- tty.warn(msg)
- return rpaths
+ msg = 'patchelf --print-rpath {0} produced an error [{1}]'
+ tty.warn(msg.format(path, str(e)))
+ return output.split(':') if output else []
-def get_relative_elf_rpaths(path_name, orig_layout_root, orig_rpaths):
- """
- Replaces orig rpath with relative path from dirname(path_name) if an rpath
- in orig_rpaths contains orig_layout_root. Prefixes $ORIGIN
- to relative paths and returns replacement rpaths.
- """
- rel_rpaths = []
- for rpath in orig_rpaths:
- if re.match(orig_layout_root, rpath):
- rel = os.path.relpath(rpath, start=os.path.dirname(path_name))
- rel_rpaths.append(os.path.join('$ORIGIN', '%s' % rel))
- else:
- rel_rpaths.append(rpath)
- return rel_rpaths
+def _make_relative(reference_file, path_root, paths):
+ """Return a list where any path in ``paths`` that starts with
+ ``path_root`` is made relative to the directory in which the
+ reference file is stored.
-def get_normalized_elf_rpaths(orig_path_name, rel_rpaths):
- """
- Normalize the relative rpaths with respect to the original path name
- of the file. If the rpath starts with $ORIGIN replace $ORIGIN with the
- dirname of the original path name and then normalize the rpath.
- A dictionary mapping relativized rpaths to normalized rpaths is returned.
- """
- norm_rpaths = list()
- for rpath in rel_rpaths:
- if rpath.startswith('$ORIGIN'):
- sub = re.sub(re.escape('$ORIGIN'),
- os.path.dirname(orig_path_name),
- rpath)
- norm = os.path.normpath(sub)
- norm_rpaths.append(norm)
- else:
- norm_rpaths.append(rpath)
- return norm_rpaths
+ After a path is made relative it is prefixed with the ``$ORIGIN``
+ string.
+ Args:
+ reference_file (str): file from which the reference directory
+ is computed
+ path_root (str): root of the relative paths
+ paths: paths to be examined
-def set_placeholder(dirname):
+ Returns:
+ List of relative paths
"""
- return string of @'s with same length
+ # Check prerequisites of the function
+ msg = "{0} is not a file".format(reference_file)
+ assert os.path.isfile(reference_file), msg
+
+ start_directory = os.path.dirname(reference_file)
+ pattern = re.compile(path_root)
+ relative_paths = []
+
+ for path in paths:
+ if pattern.match(path):
+ rel = os.path.relpath(path, start=start_directory)
+ path = os.path.join('$ORIGIN', rel)
+
+ relative_paths.append(path)
+
+ return relative_paths
+
+
+def _normalize_relative_paths(start_path, relative_paths):
+ """Normalize the relative paths with respect to the original path name
+ of the file (``start_path``).
+
+ The paths that are passed to this function existed or were relevant
+ on another filesystem, so os.path.abspath cannot be used.
+
+ A relative path may contain the signifier $ORIGIN. Assuming that
+ ``start_path`` is absolute, this implies that the relative path
+ (relative to start_path) should be replaced with an absolute path.
+
+ Args:
+ start_path (str): path from which the starting directory
+ is extracted
+ relative_paths (str): list of relative paths as obtained by a
+ call to :ref:`_make_relative`
+
+ Returns:
+ List of normalized paths
"""
+ normalized_paths = []
+ pattern = re.compile(re.escape('$ORIGIN'))
+ start_directory = os.path.dirname(start_path)
+
+ for path in relative_paths:
+ if path.startswith('$ORIGIN'):
+ sub = pattern.sub(start_directory, path)
+ path = os.path.normpath(sub)
+ normalized_paths.append(path)
+
+ return normalized_paths
+
+
+def _placeholder(dirname):
+ """String of of @'s with same length of the argument"""
return '@' * len(dirname)
@@ -592,7 +624,7 @@ def relocate_elf_binaries(path_names, old_layout_root, new_layout_root,
rpath was in the old layout root, i.e. system paths are not replaced.
"""
for path_name in path_names:
- orig_rpaths = get_existing_elf_rpaths(path_name)
+ orig_rpaths = _elf_rpaths_for(path_name)
new_rpaths = list()
if rel:
# get the file path in the old_prefix
@@ -600,14 +632,14 @@ def relocate_elf_binaries(path_names, old_layout_root, new_layout_root,
path_name)
# get the normalized rpaths in the old prefix using the file path
# in the orig prefix
- orig_norm_rpaths = get_normalized_elf_rpaths(orig_path_name,
+ orig_norm_rpaths = _normalize_relative_paths(orig_path_name,
orig_rpaths)
# get the normalize rpaths in the new prefix
norm_rpaths = elf_find_paths(orig_norm_rpaths, old_layout_root,
prefix_to_prefix)
# get the relativized rpaths in the new prefix
- new_rpaths = get_relative_elf_rpaths(path_name, new_layout_root,
- norm_rpaths)
+ new_rpaths = _make_relative(path_name, new_layout_root,
+ norm_rpaths)
modify_elf_object(path_name, new_rpaths)
else:
new_rpaths = elf_find_paths(orig_rpaths, old_layout_root,
@@ -652,10 +684,10 @@ def make_elf_binaries_relative(cur_path_names, orig_path_names,
Replace old RPATHs with paths relative to old_dir in binary files
"""
for cur_path, orig_path in zip(cur_path_names, orig_path_names):
- orig_rpaths = get_existing_elf_rpaths(cur_path)
+ orig_rpaths = _elf_rpaths_for(cur_path)
if orig_rpaths:
- new_rpaths = get_relative_elf_rpaths(orig_path, old_layout_root,
- orig_rpaths)
+ new_rpaths = _make_relative(orig_path, old_layout_root,
+ orig_rpaths)
modify_elf_object(cur_path, new_rpaths)
@@ -679,7 +711,7 @@ def relocate_links(linknames, old_layout_root, new_layout_root,
link target is create by replacing the old install prefix with the new
install prefix.
"""
- placeholder = set_placeholder(old_layout_root)
+ placeholder = _placeholder(old_layout_root)
link_names = [os.path.join(new_install_prefix, linkname)
for linkname in linknames]
for link_name in link_names:
@@ -810,7 +842,7 @@ def file_is_relocatable(file, paths_to_relocate=None):
if platform.system().lower() == 'linux':
if m_subtype == 'x-executable' or m_subtype == 'x-sharedlib':
- rpaths = ':'.join(get_existing_elf_rpaths(file))
+ rpaths = ':'.join(_elf_rpaths_for(file))
set_of_strings.discard(rpaths)
if platform.system().lower() == 'darwin':
if m_subtype == 'x-mach-binary':
diff --git a/lib/spack/spack/test/packaging.py b/lib/spack/spack/test/packaging.py
index 39da7c3ae5..67c9e52875 100644
--- a/lib/spack/spack/test/packaging.py
+++ b/lib/spack/spack/test/packaging.py
@@ -25,11 +25,9 @@ from spack.paths import mock_gpg_keys_path
from spack.fetch_strategy import URLFetchStrategy, FetchStrategyComposite
from spack.relocate import needs_binary_relocation, needs_text_relocation
from spack.relocate import relocate_text, relocate_links
-from spack.relocate import get_relative_elf_rpaths
-from spack.relocate import get_normalized_elf_rpaths
from spack.relocate import macho_make_paths_relative
from spack.relocate import macho_make_paths_normal
-from spack.relocate import set_placeholder, macho_find_paths
+from spack.relocate import _placeholder, macho_find_paths
from spack.relocate import file_is_relocatable
@@ -228,7 +226,7 @@ def test_relocate_links(tmpdir):
old_install_prefix = os.path.join(
'%s' % old_layout_root, 'debian6', 'test')
old_binname = os.path.join(old_install_prefix, 'binfile')
- placeholder = set_placeholder(old_layout_root)
+ placeholder = _placeholder(old_layout_root)
re.sub(old_layout_root, placeholder, old_binname)
filenames = ['link.ln', 'outsideprefix.ln']
new_layout_root = os.path.join(
@@ -561,15 +559,3 @@ def test_macho_make_paths():
'/Users/Shared/spack/pkgB/libB.dylib',
'/usr/local/lib/libloco.dylib':
'/usr/local/lib/libloco.dylib'}
-
-
-def test_elf_paths():
- out = get_relative_elf_rpaths(
- '/usr/bin/test', '/usr',
- ('/usr/lib', '/usr/lib64', '/opt/local/lib'))
- assert out == ['$ORIGIN/../lib', '$ORIGIN/../lib64', '/opt/local/lib']
-
- out = get_normalized_elf_rpaths(
- '/usr/bin/test',
- ['$ORIGIN/../lib', '$ORIGIN/../lib64', '/opt/local/lib'])
- assert out == ['/usr/lib', '/usr/lib64', '/opt/local/lib']
diff --git a/lib/spack/spack/test/relocate.py b/lib/spack/spack/test/relocate.py
index 0a9e9f7f0a..a2bba52f40 100644
--- a/lib/spack/spack/test/relocate.py
+++ b/lib/spack/spack/test/relocate.py
@@ -89,6 +89,20 @@ def expected_patchelf_path(request, mutable_database, monkeypatch):
return expected_path
+@pytest.fixture()
+def mock_patchelf(tmpdir):
+ import jinja2
+
+ def _factory(output):
+ f = tmpdir.mkdir('bin').join('patchelf')
+ t = jinja2.Template('#!/bin/bash\n{{ output }}\n')
+ f.write(t.render(output=output))
+ f.chmod(0o755)
+ return str(f)
+
+ return _factory
+
+
@pytest.mark.requires_executables(
'/usr/bin/gcc', 'patchelf', 'strings', 'file'
)
@@ -140,3 +154,43 @@ def test_file_is_relocatable_errors(tmpdir):
def test_search_patchelf(expected_patchelf_path):
current = spack.relocate._patchelf()
assert current == expected_patchelf_path
+
+
+@pytest.mark.parametrize('patchelf_behavior,expected', [
+ ('echo ', []),
+ ('echo /opt/foo/lib:/opt/foo/lib64', ['/opt/foo/lib', '/opt/foo/lib64']),
+ ('exit 1', [])
+])
+def test_existing_rpaths(patchelf_behavior, expected, mock_patchelf):
+ # Here we are mocking an executable that is always called "patchelf"
+ # because that will skip the part where we try to build patchelf
+ # by ourselves. The executable will output some rpaths like
+ # `patchelf --print-rpath` would.
+ path = mock_patchelf(patchelf_behavior)
+ rpaths = spack.relocate._elf_rpaths_for(path)
+ assert rpaths == expected
+
+
+@pytest.mark.parametrize('start_path,path_root,paths,expected', [
+ ('/usr/bin/test', '/usr', ['/usr/lib', '/usr/lib64', '/opt/local/lib'],
+ ['$ORIGIN/../lib', '$ORIGIN/../lib64', '/opt/local/lib'])
+])
+def test_make_relative_paths(start_path, path_root, paths, expected):
+ relatives = spack.relocate._make_relative(start_path, path_root, paths)
+ assert relatives == expected
+
+
+@pytest.mark.parametrize('start_path,relative_paths,expected', [
+ # $ORIGIN will be replaced with os.path.dirname('usr/bin/test')
+ # and then normalized
+ ('/usr/bin/test',
+ ['$ORIGIN/../lib', '$ORIGIN/../lib64', '/opt/local/lib'],
+ ['/usr/lib', '/usr/lib64', '/opt/local/lib']),
+ # Relative path without $ORIGIN
+ ('/usr/bin/test', ['../local/lib'], ['../local/lib']),
+])
+def test_normalize_relative_paths(start_path, relative_paths, expected):
+ normalized = spack.relocate._normalize_relative_paths(
+ start_path, relative_paths
+ )
+ assert normalized == expected