[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:
parent
c63f680d2a
commit
ebc871abbf
10 changed files with 313 additions and 207 deletions
|
@ -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``.
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
for orig_bytes, new_bytes in byte_prefixes.items():
|
||||||
original_data_len = len(data)
|
original_data_len = len(data)
|
||||||
pat = re.compile(old_dir.encode('utf-8'))
|
# Skip this hassle if not found
|
||||||
if not pat.search(data):
|
if orig_bytes not in data:
|
||||||
return
|
continue
|
||||||
ndata = pat.sub(replace, data)
|
# We only care about this problem if we are about to replace
|
||||||
if not len(ndata) == original_data_len:
|
length_compatible = len(new_bytes) <= len(orig_bytes)
|
||||||
|
if not length_compatible:
|
||||||
|
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(
|
raise BinaryStringReplacementError(
|
||||||
filename, original_data_len, len(ndata))
|
filename, original_data_len, len(data))
|
||||||
f.write(ndata)
|
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:
|
Args:
|
||||||
files (list): text files to be relocated
|
files (list): Text files to be relocated
|
||||||
orig_layout_root (str): original layout root
|
prefixes (OrderedDict): String prefixes which need to be changed
|
||||||
new_layout_root (str): new layout root
|
concurrency (int): Preferred degree of parallelism
|
||||||
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
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# 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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
35
var/spack/repos/builtin.mock/packages/old-sbang/package.py
Normal file
35
var/spack/repos/builtin.mock/packages/old-sbang/package.py
Normal 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)
|
Loading…
Reference in a new issue