Relocate mach-o binaries using macholib on linux. (#12946)

Changes deps and rpaths for bins and libs, changes id for libs.
This commit is contained in:
Patrick Gartung 2019-09-26 13:04:58 -05:00 committed by GitHub
parent 950338aa92
commit 7dae058f91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 95 deletions

View File

@ -573,20 +573,19 @@ def extract_tarball(spec, filename, allow_root=False, unsigned=False,
# so the pathname should be the same now that the directory layout # so the pathname should be the same now that the directory layout
# is confirmed # is confirmed
workdir = os.path.join(tmpdir, os.path.basename(spec.prefix)) workdir = os.path.join(tmpdir, os.path.basename(spec.prefix))
install_tree(workdir, spec.prefix, symlinks=True)
# cleanup # cleanup
os.remove(tarfile_path) os.remove(tarfile_path)
os.remove(specfile_path) os.remove(specfile_path)
try: try:
relocate_package(workdir, spec, allow_root) relocate_package(spec.prefix, spec, allow_root)
except Exception as e: except Exception as e:
shutil.rmtree(workdir) shutil.rmtree(spec.prefix)
tty.die(e) tty.die(e)
# Delay creating spec.prefix until verification is complete # Delay creating spec.prefix until verification is complete
# and any relocation has been done. # and any relocation has been done.
else:
install_tree(workdir, spec.prefix, symlinks=True)
finally: finally:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)

View File

@ -41,20 +41,20 @@ def __init__(self, file_path, old_len, new_len):
(file_path, old_len, new_len)) (file_path, old_len, new_len))
class MissingMachotoolsException(spack.error.SpackError): class MissingMacholibException(spack.error.SpackError):
""" """
Raised when the size of the file changes after binary path substitution. Raised when the size of the file changes after binary path substitution.
""" """
def __init__(self, error): def __init__(self, error):
super(MissingMachotoolsException, self).__init__( super(MissingMacholibException, self).__init__(
"%s\n" "%s\n"
"Python package machotools needs to be avaiable to list\n" "Python package macholib needs to be avaiable to list\n"
"and modify a mach-o binary's rpaths, deps and id.\n" "and modify a mach-o binary's rpaths, deps and id.\n"
"Use virtualenv with pip install machotools or\n" "Use virtualenv with pip install macholib or\n"
"use spack to install the py-machotools package\n" "use spack to install the py-macholib package\n"
"spack install py-machotools\n" "spack install py-macholib\n"
"spack activate py-machotools\n" "spack activate py-macholib\n"
"spack load python\n" "spack load python\n"
% error) % error)
@ -275,55 +275,50 @@ def modify_macho_object(cur_path, rpaths, deps, idpath,
return return
def modify_object_machotools(cur_path, rpaths, deps, idpath, def modify_object_macholib(cur_path, old_dir, new_dir):
new_rpaths, new_deps, new_idpath):
""" """
Modify MachO binary path_name by replacing old_dir with new_dir Modify MachO binary path_name by replacing old_dir with new_dir
or the relative path to spack install root. or the relative path to spack install root.
The old install dir in LC_ID_DYLIB is replaced with the new install dir The old install dir in LC_ID_DYLIB is replaced with the new install dir
using py-machotools using py-macholib
The old install dir in LC_LOAD_DYLIB is replaced with the new install dir The old install dir in LC_LOAD_DYLIB is replaced with the new install dir
using py-machotools using py-macholib
The old install dir in LC_RPATH is replaced with the new install dir using The old install dir in LC_RPATH is replaced with the new install dir using
using py-machotools using py-macholib
""" """
if cur_path.endswith('.o'): if cur_path.endswith('.o'):
return return
try: try:
import machotools from macholib.MachO import MachO
from macholib.mach_o import LC_RPATH
except ImportError as e: except ImportError as e:
raise MissingMachotoolsException(e) raise MissingMacholibException(e)
rewriter = machotools.rewriter_factory(cur_path)
if machotools.detect.is_dylib(cur_path):
if not new_idpath == idpath:
rewriter.install_name = new_idpath
for orig, new in zip(deps, new_deps):
if not orig == new:
rewriter.change_dependency(orig, new)
rewriter.commit()
return
def match_func(cpath):
return str(cpath).replace(old_dir, new_dir)
dll = MachO(cur_path)
dll.rewriteLoadCommands(match_func)
for header in dll.headers:
for (idx, (lc, cmd, data)) in enumerate(header.commands):
if lc.cmd == LC_RPATH:
rpath = data
rpath = rpath.strip('\x00')
new_rpath = match_func(rpath)
header.rewriteDataForCommand(idx, new_rpath)
def machotools_get_paths(path_name):
"""
Examines the output of otool -l path_name for these three fields:
LC_ID_DYLIB, LC_LOAD_DYLIB, LC_RPATH and parses out the rpaths,
dependiencies and library id.
Returns these values.
"""
try: try:
import machotools f = open(dll.filename, 'rb+')
except ImportError as e: for header in dll.headers:
raise MissingMachotoolsException(e) f.seek(0)
idpath = None dll.write(f)
rpaths = list() f.seek(0, 2)
deps = list() f.flush()
rewriter = machotools.rewriter_factory(path_name) f.close()
if machotools.detect.is_dylib(path_name): except Exception:
idpath = rewriter.install_name pass
rpaths = rewriter.rpaths
deps = rewriter.dependencies return
return rpaths, deps, idpath
def strings_contains_installroot(path_name, root_dir): def strings_contains_installroot(path_name, root_dir):
@ -405,15 +400,12 @@ def relocate_macho_binaries(path_names, old_dir, new_dir, allow_root):
""" """
placeholder = set_placeholder(old_dir) placeholder = set_placeholder(old_dir)
for path_name in path_names: for path_name in path_names:
deps = set()
idpath = None
if platform.system().lower() == 'darwin':
if path_name.endswith('.o'): if path_name.endswith('.o'):
continue continue
else: if new_dir == old_dir:
continue
if platform.system().lower() == 'darwin':
rpaths, deps, idpath = macho_get_paths(path_name) rpaths, deps, idpath = macho_get_paths(path_name)
else:
rpaths, deps, idpath = machotools_get_paths(path_name)
# one pass to replace placeholder # one pass to replace placeholder
(n_rpaths, (n_rpaths,
n_deps, n_deps,
@ -430,16 +422,12 @@ def relocate_macho_binaries(path_names, old_dir, new_dir, allow_root):
n_rpaths, n_rpaths,
n_deps, n_deps,
n_idpath) n_idpath)
if platform.system().lower() == 'darwin':
modify_macho_object(path_name, modify_macho_object(path_name,
rpaths, deps, idpath, rpaths, deps, idpath,
new_rpaths, new_deps, new_idpath) new_rpaths, new_deps, new_idpath)
else: else:
modify_object_machotools(path_name, modify_object_macholib(path_name, placeholder, new_dir)
rpaths, deps, idpath, modify_object_macholib(path_name, old_dir, new_dir)
new_rpaths, new_deps, new_idpath)
if not new_dir == old_dir:
if len(new_dir) <= len(old_dir): if len(new_dir) <= len(old_dir):
replace_prefix_bin(path_name, old_dir, new_dir) replace_prefix_bin(path_name, old_dir, new_dir)
else: else:
@ -498,21 +486,13 @@ def make_macho_binaries_relative(cur_path_names, orig_path_names, old_dir,
idpath = None idpath = None
if platform.system().lower() == 'darwin': if platform.system().lower() == 'darwin':
(rpaths, deps, idpath) = macho_get_paths(cur_path) (rpaths, deps, idpath) = macho_get_paths(cur_path)
else:
(rpaths, deps, idpath) = machotools_get_paths(cur_path)
(new_rpaths, (new_rpaths,
new_deps, new_deps,
new_idpath) = macho_make_paths_relative(orig_path, old_dir, new_idpath) = macho_make_paths_relative(orig_path, old_dir,
rpaths, deps, idpath) rpaths, deps, idpath)
if platform.system().lower() == 'darwin':
modify_macho_object(cur_path, modify_macho_object(cur_path,
rpaths, deps, idpath, rpaths, deps, idpath,
new_rpaths, new_deps, new_idpath) new_rpaths, new_deps, new_idpath)
else:
modify_object_machotools(cur_path,
rpaths, deps, idpath,
new_rpaths, new_deps, new_idpath)
if (not allow_root and if (not allow_root and
not file_is_relocatable(cur_path)): not file_is_relocatable(cur_path)):
raise InstallRootStringException(cur_path, old_dir) raise InstallRootStringException(cur_path, old_dir)