[WIP] relocate.py: parallelize test replacement logic (#19690)

* sbang pushed back to callers;
star moved to util.lang

* updated unit test

* sbang test moved; local tests pass

Co-authored-by: Nathan Hanford <hanford1@llnl.gov>
This commit is contained in:
Nathan Hanford 2021-01-20 09:17:47 -08:00 committed by GitHub
parent c63f680d2a
commit ebc871abbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 313 additions and 207 deletions

View file

@ -673,6 +673,13 @@ def uniq(sequence):
return uniq_list return uniq_list
def star(func):
"""Unpacks arguments for use with Multiprocessing mapping functions"""
def _wrapper(args):
return func(*args)
return _wrapper
class Devnull(object): class Devnull(object):
"""Null stream with less overhead than ``os.devnull``. """Null stream with less overhead than ``os.devnull``.

View file

@ -12,6 +12,7 @@
import tempfile import tempfile
import hashlib import hashlib
import glob import glob
from ordereddict_backport import OrderedDict
from contextlib import closing from contextlib import closing
import ruamel.yaml as yaml import ruamel.yaml as yaml
@ -1105,11 +1106,26 @@ def relocate_package(spec, allow_root):
new_deps = spack.build_environment.get_rpath_deps(spec.package) new_deps = spack.build_environment.get_rpath_deps(spec.package)
for d in new_deps: for d in new_deps:
hash_to_prefix[d.format('{hash}')] = str(d.prefix) hash_to_prefix[d.format('{hash}')] = str(d.prefix)
prefix_to_prefix = dict() # Spurious replacements (e.g. sbang) will cause issues with binaries
# For example, the new sbang can be longer than the old one.
# Hence 2 dictionaries are maintained here.
prefix_to_prefix_text = OrderedDict({})
prefix_to_prefix_bin = OrderedDict({})
prefix_to_prefix_text[old_prefix] = new_prefix
prefix_to_prefix_bin[old_prefix] = new_prefix
prefix_to_prefix_text[old_layout_root] = new_layout_root
prefix_to_prefix_bin[old_layout_root] = new_layout_root
for orig_prefix, hash in prefix_to_hash.items(): for orig_prefix, hash in prefix_to_hash.items():
prefix_to_prefix[orig_prefix] = hash_to_prefix.get(hash, None) prefix_to_prefix_text[orig_prefix] = hash_to_prefix.get(hash, None)
prefix_to_prefix[old_prefix] = new_prefix prefix_to_prefix_bin[orig_prefix] = hash_to_prefix.get(hash, None)
prefix_to_prefix[old_layout_root] = new_layout_root # This is vestigial code for the *old* location of sbang. Previously,
# sbang was a bash script, and it lived in the spack prefix. It is
# now a POSIX script that lives in the install prefix. Old packages
# will have the old sbang location in their shebangs.
import spack.hooks.sbang as sbang
orig_sbang = '#!/bin/bash {0}/bin/sbang'.format(old_spack_prefix)
new_sbang = sbang.sbang_shebang_line()
prefix_to_prefix_text[orig_sbang] = new_sbang
tty.debug("Relocating package from", tty.debug("Relocating package from",
"%s to %s." % (old_layout_root, new_layout_root)) "%s to %s." % (old_layout_root, new_layout_root))
@ -1137,15 +1153,14 @@ def is_backup_file(file):
relocate.relocate_macho_binaries(files_to_relocate, relocate.relocate_macho_binaries(files_to_relocate,
old_layout_root, old_layout_root,
new_layout_root, new_layout_root,
prefix_to_prefix, rel, prefix_to_prefix_bin, rel,
old_prefix, old_prefix,
new_prefix) new_prefix)
if 'elf' in platform.binary_formats: if 'elf' in platform.binary_formats:
relocate.relocate_elf_binaries(files_to_relocate, relocate.relocate_elf_binaries(files_to_relocate,
old_layout_root, old_layout_root,
new_layout_root, new_layout_root,
prefix_to_prefix, rel, prefix_to_prefix_bin, rel,
old_prefix, old_prefix,
new_prefix) new_prefix)
# Relocate links to the new install prefix # Relocate links to the new install prefix
@ -1156,12 +1171,7 @@ def is_backup_file(file):
# For all buildcaches # For all buildcaches
# relocate the install prefixes in text files including dependencies # relocate the install prefixes in text files including dependencies
relocate.relocate_text(text_names, relocate.relocate_text(text_names, prefix_to_prefix_text)
old_layout_root, new_layout_root,
old_prefix, new_prefix,
old_spack_prefix,
new_spack_prefix,
prefix_to_prefix)
paths_to_relocate = [old_prefix, old_layout_root] paths_to_relocate = [old_prefix, old_layout_root]
paths_to_relocate.extend(prefix_to_hash.keys()) paths_to_relocate.extend(prefix_to_hash.keys())
@ -1171,22 +1181,13 @@ def is_backup_file(file):
map(lambda filename: os.path.join(workdir, filename), map(lambda filename: os.path.join(workdir, filename),
buildinfo['relocate_binaries']))) buildinfo['relocate_binaries'])))
# relocate the install prefixes in binary files including dependencies # relocate the install prefixes in binary files including dependencies
relocate.relocate_text_bin(files_to_relocate, relocate.relocate_text_bin(files_to_relocate, prefix_to_prefix_bin)
old_prefix, new_prefix,
old_spack_prefix,
new_spack_prefix,
prefix_to_prefix)
# If we are installing back to the same location # If we are installing back to the same location
# relocate the sbang location if the spack directory changed # relocate the sbang location if the spack directory changed
else: else:
if old_spack_prefix != new_spack_prefix: if old_spack_prefix != new_spack_prefix:
relocate.relocate_text(text_names, relocate.relocate_text(text_names, prefix_to_prefix_text)
old_layout_root, new_layout_root,
old_prefix, new_prefix,
old_spack_prefix,
new_spack_prefix,
prefix_to_prefix)
def extract_tarball(spec, filename, allow_root=False, unsigned=False, def extract_tarball(spec, filename, allow_root=False, unsigned=False,

View file

@ -8,6 +8,7 @@
import re import re
import shutil import shutil
import sys import sys
from ordereddict_backport import OrderedDict
from llnl.util.link_tree import LinkTree, MergeConflictError from llnl.util.link_tree import LinkTree, MergeConflictError
from llnl.util import tty from llnl.util import tty
@ -65,32 +66,35 @@ def view_copy(src, dst, view, spec=None):
# Not metadata, we have to relocate it # Not metadata, we have to relocate it
# Get information on where to relocate from/to # Get information on where to relocate from/to
prefix_to_projection = dict(
(dep.prefix, view.get_projection_for_spec(dep)) # This is vestigial code for the *old* location of sbang. Previously,
for dep in spec.traverse() # sbang was a bash script, and it lived in the spack prefix. It is
) # now a POSIX script that lives in the install prefix. Old packages
# will have the old sbang location in their shebangs.
# TODO: Not sure which one to use...
import spack.hooks.sbang as sbang
orig_sbang = '#!/bin/bash {0}/bin/sbang'.format(spack.paths.spack_root)
new_sbang = sbang.sbang_shebang_line()
prefix_to_projection = OrderedDict({
spec.prefix: view.get_projection_for_spec(spec),
spack.paths.spack_root: view._root})
for dep in spec.traverse():
prefix_to_projection[dep.prefix] = \
view.get_projection_for_spec(dep)
if spack.relocate.is_binary(dst): if spack.relocate.is_binary(dst):
# relocate binaries
spack.relocate.relocate_text_bin( spack.relocate.relocate_text_bin(
binaries=[dst], binaries=[dst],
orig_install_prefix=spec.prefix, prefixes=prefix_to_projection
new_install_prefix=view.get_projection_for_spec(spec),
orig_spack=spack.paths.spack_root,
new_spack=view._root,
new_prefixes=prefix_to_projection
) )
else: else:
# relocate text prefix_to_projection[spack.store.layout.root] = view._root
prefix_to_projection[orig_sbang] = new_sbang
spack.relocate.relocate_text( spack.relocate.relocate_text(
files=[dst], files=[dst],
orig_layout_root=spack.store.layout.root, prefixes=prefix_to_projection
new_layout_root=view._root,
orig_install_prefix=spec.prefix,
new_install_prefix=view.get_projection_for_spec(spec),
orig_spack=spack.paths.spack_root,
new_spack=view._root,
new_prefixes=prefix_to_projection
) )

View file

@ -6,6 +6,8 @@
import platform import platform
import re import re
import shutil import shutil
import multiprocessing.pool
from ordereddict_backport import OrderedDict
import llnl.util.lang import llnl.util.lang
import llnl.util.tty as tty import llnl.util.tty as tty
@ -449,36 +451,26 @@ def needs_text_relocation(m_type, m_subtype):
return m_type == 'text' return m_type == 'text'
def _replace_prefix_text(filename, old_dir, new_dir): def _replace_prefix_text(filename, compiled_prefixes):
"""Replace all the occurrences of the old install prefix with a """Replace all the occurrences of the old install prefix with a
new install prefix in text files that are utf-8 encoded. new install prefix in text files that are utf-8 encoded.
Args: Args:
filename (str): target text file (utf-8 encoded) filename (str): target text file (utf-8 encoded)
old_dir (str): directory to be searched in the file compiled_prefixes (OrderedDict): OrderedDictionary where the keys are
new_dir (str): substitute for the old directory precompiled regex of the old prefixes and the values are the new
prefixes (uft-8 encoded)
""" """
# TODO: cache regexes globally to speedup computation
with open(filename, 'rb+') as f: with open(filename, 'rb+') as f:
data = f.read() data = f.read()
f.seek(0) f.seek(0)
# Replace old_dir with new_dir if it appears at the beginning of a path for orig_prefix_rexp, new_bytes in compiled_prefixes.items():
# Negative lookbehind for a character legal in a path data = orig_prefix_rexp.sub(new_bytes, data)
# Then a match group for any characters legal in a compiler flag f.write(data)
# Then old_dir
# Then characters legal in a path
# Ensures we only match the old_dir if it's precedeed by a flag or by
# characters not legal in a path, but not if it's preceeded by other
# components of a path.
old_bytes = old_dir.encode('utf-8')
pat = b'(?<![\\w\\-_/])([\\w\\-_]*?)%s([\\w\\-_/]*)' % old_bytes
repl = b'\\1%s\\2' % new_dir.encode('utf-8')
ndata = re.sub(pat, repl, data)
f.write(ndata)
f.truncate() f.truncate()
def _replace_prefix_bin(filename, old_dir, new_dir): def _replace_prefix_bin(filename, byte_prefixes):
"""Replace all the occurrences of the old install prefix with a """Replace all the occurrences of the old install prefix with a
new install prefix in binary files. new install prefix in binary files.
@ -487,33 +479,34 @@ def _replace_prefix_bin(filename, old_dir, new_dir):
Args: Args:
filename (str): target binary file filename (str): target binary file
old_dir (str): directory to be searched in the file byte_prefixes (OrderedDict): OrderedDictionary where the keys are
new_dir (str): substitute for the old directory precompiled regex of the old prefixes and the values are the new
prefixes (uft-8 encoded)
""" """
def replace(match):
occurances = match.group().count(old_dir.encode('utf-8'))
olen = len(old_dir.encode('utf-8'))
nlen = len(new_dir.encode('utf-8'))
padding = (olen - nlen) * occurances
if padding < 0:
return data
return match.group().replace(
old_dir.encode('utf-8'),
os.sep.encode('utf-8') * padding + new_dir.encode('utf-8')
)
with open(filename, 'rb+') as f: with open(filename, 'rb+') as f:
data = f.read() data = f.read()
f.seek(0) f.seek(0)
original_data_len = len(data) for orig_bytes, new_bytes in byte_prefixes.items():
pat = re.compile(old_dir.encode('utf-8')) original_data_len = len(data)
if not pat.search(data): # Skip this hassle if not found
return if orig_bytes not in data:
ndata = pat.sub(replace, data) continue
if not len(ndata) == original_data_len: # We only care about this problem if we are about to replace
raise BinaryStringReplacementError( length_compatible = len(new_bytes) <= len(orig_bytes)
filename, original_data_len, len(ndata)) if not length_compatible:
f.write(ndata) raise BinaryTextReplaceError(orig_bytes, new_bytes)
pad_length = len(orig_bytes) - len(new_bytes)
padding = os.sep * pad_length
padding = padding.encode('utf-8')
data = data.replace(orig_bytes, new_bytes + padding)
# Really needs to be the same length
if not len(data) == original_data_len:
print('Length of pad:', pad_length, 'should be', len(padding))
print(new_bytes, 'was to replace', orig_bytes)
raise BinaryStringReplacementError(
filename, original_data_len, len(data))
f.write(data)
f.truncate() f.truncate()
@ -786,86 +779,88 @@ def relocate_links(links, orig_layout_root,
tty.warn(msg.format(link_target, abs_link, new_install_prefix)) tty.warn(msg.format(link_target, abs_link, new_install_prefix))
def relocate_text( def relocate_text(files, prefixes, concurrency=32):
files, orig_layout_root, new_layout_root, orig_install_prefix, """Relocate text file from the original installation prefix to the
new_install_prefix, orig_spack, new_spack, new_prefixes new prefix.
):
"""Relocate text file from the original ``install_tree`` to the new one.
This also handles relocating Spack's sbang scripts to point at the Relocation also affects the the path in Spack's sbang script.
new install tree.
Args:
files (list): text files to be relocated
orig_layout_root (str): original layout root
new_layout_root (str): new layout root
orig_install_prefix (str): install prefix of the original installation
new_install_prefix (str): install prefix where we want to relocate
orig_spack (str): path to the original Spack
new_spack (str): path to the new Spack
new_prefixes (dict): dictionary that maps the original prefixes to
where they should be relocated
Args:
files (list): Text files to be relocated
prefixes (OrderedDict): String prefixes which need to be changed
concurrency (int): Preferred degree of parallelism
""" """
# TODO: reduce the number of arguments (8 seems too much)
# This is vestigial code for the *old* location of sbang. Previously, # This now needs to be handled by the caller in all cases
# sbang was a bash script, and it lived in the spack prefix. It is # orig_sbang = '#!/bin/bash {0}/bin/sbang'.format(orig_spack)
# now a POSIX script that lives in the install prefix. Old packages # new_sbang = '#!/bin/bash {0}/bin/sbang'.format(new_spack)
# will have the old sbang location in their shebangs.
import spack.hooks.sbang as sbang compiled_prefixes = OrderedDict({})
orig_sbang = '#!/bin/bash {0}/bin/sbang'.format(orig_spack)
new_sbang = sbang.sbang_shebang_line() for orig_prefix, new_prefix in prefixes.items():
if orig_prefix != new_prefix:
orig_bytes = orig_prefix.encode('utf-8')
orig_prefix_rexp = re.compile(
b'(?<![\\w\\-_/])([\\w\\-_]*?)%s([\\w\\-_/]*)' % orig_bytes)
new_bytes = b'\\1%s\\2' % new_prefix.encode('utf-8')
compiled_prefixes[orig_prefix_rexp] = new_bytes
# Do relocations on text that refers to the install tree # Do relocations on text that refers to the install tree
# multiprocesing.ThreadPool.map requires single argument
args = []
for filename in files: for filename in files:
_replace_prefix_text(filename, orig_install_prefix, new_install_prefix) args.append((filename, compiled_prefixes))
for orig_dep_prefix, new_dep_prefix in new_prefixes.items():
_replace_prefix_text(filename, orig_dep_prefix, new_dep_prefix)
_replace_prefix_text(filename, orig_layout_root, new_layout_root)
# Point old packages at the new sbang location. Packages that tp = multiprocessing.pool.ThreadPool(processes=concurrency)
# already use the new sbang location will already have been try:
# handled by the prior call to _replace_prefix_text tp.map(llnl.util.lang.star(_replace_prefix_text), args)
_replace_prefix_text(filename, orig_sbang, new_sbang) finally:
tp.terminate()
tp.join()
def relocate_text_bin( def relocate_text_bin(binaries, prefixes, concurrency=32):
binaries, orig_install_prefix, new_install_prefix,
orig_spack, new_spack, new_prefixes
):
"""Replace null terminated path strings hard coded into binaries. """Replace null terminated path strings hard coded into binaries.
The new install prefix must be shorter than the original one. The new install prefix must be shorter than the original one.
Args: Args:
binaries (list): binaries to be relocated binaries (list): binaries to be relocated
orig_install_prefix (str): install prefix of the original installation prefixes (OrderedDict): String prefixes which need to be changed.
new_install_prefix (str): install prefix where we want to relocate concurrency (int): Desired degree of parallelism.
orig_spack (str): path to the original Spack
new_spack (str): path to the new Spack
new_prefixes (dict): dictionary that maps the original prefixes to
where they should be relocated
Raises: Raises:
BinaryTextReplaceError: when the new path in longer than the old path BinaryTextReplaceError: when the new path is longer than the old path
""" """
# Raise if the new install prefix is longer than the byte_prefixes = OrderedDict({})
# original one, since it means we can't change the original
# binary to relocate it for orig_prefix, new_prefix in prefixes.items():
new_prefix_is_shorter = len(new_install_prefix) <= len(orig_install_prefix) if orig_prefix != new_prefix:
if not new_prefix_is_shorter and len(binaries) > 0: if isinstance(orig_prefix, bytes):
raise BinaryTextReplaceError(orig_install_prefix, new_install_prefix) orig_bytes = orig_prefix
else:
orig_bytes = orig_prefix.encode('utf-8')
if isinstance(new_prefix, bytes):
new_bytes = new_prefix
else:
new_bytes = new_prefix.encode('utf-8')
byte_prefixes[orig_bytes] = new_bytes
# Do relocations on text in binaries that refers to the install tree
# multiprocesing.ThreadPool.map requires single argument
args = []
for binary in binaries: for binary in binaries:
for old_dep_prefix, new_dep_prefix in new_prefixes.items(): args.append((binary, byte_prefixes))
if len(new_dep_prefix) <= len(old_dep_prefix):
_replace_prefix_bin(binary, old_dep_prefix, new_dep_prefix)
_replace_prefix_bin(binary, orig_install_prefix, new_install_prefix)
# Note: Replacement of spack directory should not be done. This causes tp = multiprocessing.pool.ThreadPool(processes=concurrency)
# an incorrect replacement path in the case where the install root is a
# subdirectory of the spack directory. try:
tp.map(llnl.util.lang.star(_replace_prefix_bin), args)
finally:
tp.terminate()
tp.join()
def is_relocatable(spec): def is_relocatable(spec):

View file

@ -19,6 +19,7 @@
import spack.cmd.install as install import spack.cmd.install as install
import spack.cmd.uninstall as uninstall import spack.cmd.uninstall as uninstall
import spack.cmd.mirror as mirror import spack.cmd.mirror as mirror
import spack.hooks.sbang as sbang
from spack.main import SpackCommand from spack.main import SpackCommand
import spack.mirror import spack.mirror
import spack.util.gpg import spack.util.gpg
@ -80,6 +81,15 @@ def mirror_directory_rel(session_mirror_rel):
yield(session_mirror_rel) yield(session_mirror_rel)
@pytest.fixture(scope='function')
def function_mirror(tmpdir):
mirror_dir = str(tmpdir.join('mirror'))
mirror_cmd('add', '--scope', 'site', 'test-mirror-func',
'file://%s' % mirror_dir)
yield mirror_dir
mirror_cmd('rm', '--scope=site', 'test-mirror-func')
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def config_directory(tmpdir_factory): def config_directory(tmpdir_factory):
tmpdir = tmpdir_factory.mktemp('test_configs') tmpdir = tmpdir_factory.mktemp('test_configs')
@ -671,3 +681,76 @@ def mock_list_url(url, recursive=False):
err = capfd.readouterr()[1] err = capfd.readouterr()[1]
expect = 'Encountered problem listing packages at {0}'.format(test_url) expect = 'Encountered problem listing packages at {0}'.format(test_url)
assert expect in err assert expect in err
@pytest.mark.usefixtures('mock_fetch')
def test_update_sbang(tmpdir, install_mockery, function_mirror):
"""
Test the creation and installation of buildcaches with default rpaths
into the non-default directory layout scheme, triggering an update of the
sbang.
"""
# Save the original store and layout before we touch ANYTHING.
real_store = spack.store.store
real_layout = spack.store.layout
# Concretize a package with some old-fashioned sbang lines.
sspec = Spec('old-sbang')
sspec.concretize()
# Need a fake mirror with *function* scope.
mirror_dir = function_mirror
# Assumes all commands will concretize sspec the same way.
install_cmd('--no-cache', sspec.name)
# Create a buildcache with the installed spec.
buildcache_cmd('create', '-u', '-a', '-d', mirror_dir,
'/%s' % sspec.dag_hash())
# Need to force an update of the buildcache index
buildcache_cmd('update-index', '-d', 'file://%s' % mirror_dir)
# Uninstall the original package.
uninstall_cmd('-y', '/%s' % sspec.dag_hash())
try:
# New install tree locations...
# Too fine-grained to do be done in a fixture
spack.store.store = spack.store.Store(str(tmpdir.join('newtree')))
spack.store.layout = YamlDirectoryLayout(str(tmpdir.join('newtree')),
path_scheme=ndef_install_path_scheme) # noqa: E501
# Install package from buildcache
buildcache_cmd('install', '-a', '-u', '-f', sspec.name)
# Continue blowing away caches
bindist.clear_spec_cache()
spack.stage.purge()
# test that the sbang was updated by the move
sbang_style_1_expected = '''{0}
#!/usr/bin/env python
{1}
'''.format(sbang.sbang_shebang_line(), sspec.prefix.bin)
sbang_style_2_expected = '''{0}
#!/usr/bin/env python
{1}
'''.format(sbang.sbang_shebang_line(), sspec.prefix.bin)
installed_script_style_1_path = sspec.prefix.bin.join('sbang-style-1.sh')
assert sbang_style_1_expected == \
open(str(installed_script_style_1_path)).read()
installed_script_style_2_path = sspec.prefix.bin.join('sbang-style-2.sh')
assert sbang_style_2_expected == \
open(str(installed_script_style_2_path)).read()
uninstall_cmd('-y', '/%s' % sspec.dag_hash())
finally:
spack.store.store = real_store
spack.store.layout = real_layout

View file

@ -655,6 +655,36 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
store_path.join('.spack-db').chmod(mode=0o755, rec=1) store_path.join('.spack-db').chmod(mode=0o755, rec=1)
@pytest.fixture(scope='function')
def mutable_mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
_store_dir_and_cache):
"""Creates a read-only mock database with some packages installed note
that the ref count for dyninst here will be 3, as it's recycled
across each install.
This does not actually activate the store for use by Spack -- see the
``database`` fixture for that.
"""
store_path, store_cache = _store_dir_and_cache
store = spack.store.Store(str(store_path))
# If the cache does not exist populate the store and create it
if not os.path.exists(str(store_cache.join('.spack-db'))):
with use_configuration(mock_configuration):
with use_store(store):
with use_repo(mock_repo_path):
_populate(store.db)
store_path.copy(store_cache, mode=True, stat=True)
# Make the DB filesystem read-only to ensure we can't modify entries
store_path.join('.spack-db').chmod(mode=0o555, rec=1)
yield store
store_path.join('.spack-db').chmod(mode=0o755, rec=1)
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def database(mock_store, mock_packages, config, monkeypatch): def database(mock_store, mock_packages, config, monkeypatch):
"""This activates the mock store, packages, AND config.""" """This activates the mock store, packages, AND config."""

View file

@ -196,10 +196,8 @@ def test_relocate_text(tmpdir):
script.close() script.close()
filenames = [filename] filenames = [filename]
new_dir = '/opt/rh/devtoolset/' new_dir = '/opt/rh/devtoolset/'
relocate_text(filenames, old_dir, new_dir, # Singleton dict doesn't matter if Ordered
old_dir, new_dir, relocate_text(filenames, {old_dir: new_dir})
old_dir, new_dir,
{old_dir: new_dir})
with open(filename, "r")as script: with open(filename, "r")as script:
for line in script: for line in script:
assert(new_dir in line) assert(new_dir in line)

View file

@ -12,7 +12,6 @@
import pytest import pytest
import spack.architecture import spack.architecture
import spack.concretize import spack.concretize
import spack.hooks.sbang as sbang
import spack.paths import spack.paths
import spack.relocate import spack.relocate
import spack.spec import spack.spec
@ -281,7 +280,7 @@ def test_replace_prefix_bin(hello_world):
executable = hello_world(rpaths=['/usr/lib', '/usr/lib64']) executable = hello_world(rpaths=['/usr/lib', '/usr/lib64'])
# Relocate the RPATHs # Relocate the RPATHs
spack.relocate._replace_prefix_bin(str(executable), '/usr', '/foo') spack.relocate._replace_prefix_bin(str(executable), {b'/usr': b'/foo'})
# Some compilers add rpaths so ensure changes included in final result # Some compilers add rpaths so ensure changes included in final result
assert '/foo/lib:/foo/lib64' in rpaths_for(executable) assert '/foo/lib:/foo/lib64' in rpaths_for(executable)
@ -382,11 +381,12 @@ def test_relocate_text_bin(hello_world, copy_binary, tmpdir):
assert not text_in_bin(str(new_binary.dirpath()), new_binary) assert not text_in_bin(str(new_binary.dirpath()), new_binary)
# Check this call succeed # Check this call succeed
orig_path_bytes = str(orig_binary.dirpath()).encode('utf-8')
new_path_bytes = str(new_binary.dirpath()).encode('utf-8')
spack.relocate.relocate_text_bin( spack.relocate.relocate_text_bin(
[str(new_binary)], [str(new_binary)],
str(orig_binary.dirpath()), str(new_binary.dirpath()), {orig_path_bytes: new_path_bytes}
spack.paths.spack_root, spack.paths.spack_root,
{str(orig_binary.dirpath()): str(new_binary.dirpath())}
) )
# Check original directory is not there anymore and it was # Check original directory is not there anymore and it was
@ -395,55 +395,13 @@ def test_relocate_text_bin(hello_world, copy_binary, tmpdir):
assert text_in_bin(str(new_binary.dirpath()), new_binary) assert text_in_bin(str(new_binary.dirpath()), new_binary)
def test_relocate_text_bin_raise_if_new_prefix_is_longer(): def test_relocate_text_bin_raise_if_new_prefix_is_longer(tmpdir):
short_prefix = '/short' short_prefix = b'/short'
long_prefix = '/much/longer' long_prefix = b'/much/longer'
fpath = str(tmpdir.join('fakebin'))
with open(fpath, 'w') as f:
f.write('/short')
with pytest.raises(spack.relocate.BinaryTextReplaceError): with pytest.raises(spack.relocate.BinaryTextReplaceError):
spack.relocate.relocate_text_bin( spack.relocate.relocate_text_bin(
['item'], short_prefix, long_prefix, None, None, None [fpath], {short_prefix: long_prefix}
) )
@pytest.mark.parametrize("sbang_line", [
"#!/bin/bash /path/to/orig/spack/bin/sbang",
"#!/bin/sh /orig/layout/root/bin/sbang"
])
def test_relocate_text_old_sbang(tmpdir, sbang_line):
"""Ensure that old and new sbang styles are relocated."""
old_install_prefix = "/orig/layout/root/orig/install/prefix"
new_install_prefix = os.path.join(
spack.store.layout.root, "new", "install", "prefix"
)
# input file with an sbang line
original = """\
{0}
#!/usr/bin/env python
/orig/layout/root/orig/install/prefix
""".format(sbang_line)
# expected relocation
expected = """\
{0}
#!/usr/bin/env python
{1}
""".format(sbang.sbang_shebang_line(), new_install_prefix)
path = tmpdir.ensure("path", "to", "file")
with path.open("w") as f:
f.write(original)
spack.relocate.relocate_text(
[str(path)],
"/orig/layout/root", spack.store.layout.root,
old_install_prefix, new_install_prefix,
"/path/to/orig/spack", spack.paths.spack_root,
{
old_install_prefix: new_install_prefix
}
)
assert expected == open(str(path)).read()

View file

@ -41,6 +41,7 @@ class HTMLParseError(Exception):
import spack.util.crypto import spack.util.crypto
import spack.util.s3 as s3_util import spack.util.s3 as s3_util
import spack.util.url as url_util import spack.util.url as url_util
import llnl.util.lang
from spack.util.compression import ALLOWED_ARCHIVE_TYPES from spack.util.compression import ALLOWED_ARCHIVE_TYPES
@ -424,12 +425,6 @@ def _spider(url, collect_nested):
return pages, links, subcalls return pages, links, subcalls
# TODO: Needed until we drop support for Python 2.X
def star(func):
def _wrapper(args):
return func(*args)
return _wrapper
if isinstance(root_urls, six.string_types): if isinstance(root_urls, six.string_types):
root_urls = [root_urls] root_urls = [root_urls]
@ -450,7 +445,7 @@ def _wrapper(args):
tty.debug("SPIDER: [depth={0}, max_depth={1}, urls={2}]".format( tty.debug("SPIDER: [depth={0}, max_depth={1}, urls={2}]".format(
current_depth, depth, len(spider_args)) current_depth, depth, len(spider_args))
) )
results = tp.map(star(_spider), spider_args) results = tp.map(llnl.util.lang.star(_spider), spider_args)
spider_args = [] spider_args = []
collect = current_depth < depth collect = current_depth < depth
for sub_pages, sub_links, sub_spider_args in results: for sub_pages, sub_links, sub_spider_args in results:

View file

@ -0,0 +1,35 @@
# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack import *
class OldSbang(Package):
"""Toy package for testing the old sbang replacement problem"""
homepage = "https://www.example.com"
url = "https://www.example.com/old-sbang.tar.gz"
version('1.0.0', '0123456789abcdef0123456789abcdef')
def install(self, spec, prefix):
mkdirp(prefix.bin)
sbang_style_1 = '''#!/bin/bash {0}/bin/sbang
#!/usr/bin/env python
{1}
'''.format(spack.paths.prefix, prefix.bin)
sbang_style_2 = '''#!/bin/sh {0}/bin/sbang
#!/usr/bin/env python
{1}
'''.format(spack.store.unpadded_root, prefix.bin)
with open('%s/sbang-style-1.sh' % self.prefix.bin, 'w') as f:
f.write(sbang_style_1)
with open('%s/sbang-style-2.sh' % self.prefix.bin, 'w') as f:
f.write(sbang_style_2)