From 7dae058f913a5851cbecd60322c9f98739eb221e Mon Sep 17 00:00:00 2001 From: Patrick Gartung Date: Thu, 26 Sep 2019 13:04:58 -0500 Subject: Relocate mach-o binaries using macholib on linux. (#12946) Changes deps and rpaths for bins and libs, changes id for libs. --- lib/spack/spack/binary_distribution.py | 7 +- lib/spack/spack/relocate.py | 162 +++++++++++++++------------------ 2 files changed, 74 insertions(+), 95 deletions(-) diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py index 40e3a7c17a..cab0ad21ea 100644 --- a/lib/spack/spack/binary_distribution.py +++ b/lib/spack/spack/binary_distribution.py @@ -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 # is confirmed workdir = os.path.join(tmpdir, os.path.basename(spec.prefix)) + install_tree(workdir, spec.prefix, symlinks=True) # cleanup os.remove(tarfile_path) os.remove(specfile_path) try: - relocate_package(workdir, spec, allow_root) + relocate_package(spec.prefix, spec, allow_root) except Exception as e: - shutil.rmtree(workdir) + shutil.rmtree(spec.prefix) tty.die(e) # Delay creating spec.prefix until verification is complete # and any relocation has been done. - else: - install_tree(workdir, spec.prefix, symlinks=True) finally: shutil.rmtree(tmpdir) diff --git a/lib/spack/spack/relocate.py b/lib/spack/spack/relocate.py index 8873d1d725..2c76166e92 100644 --- a/lib/spack/spack/relocate.py +++ b/lib/spack/spack/relocate.py @@ -41,20 +41,20 @@ class BinaryStringReplacementException(spack.error.SpackError): (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. """ def __init__(self, error): - super(MissingMachotoolsException, self).__init__( + super(MissingMacholibException, self).__init__( "%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" - "Use virtualenv with pip install machotools or\n" - "use spack to install the py-machotools package\n" - "spack install py-machotools\n" - "spack activate py-machotools\n" + "Use virtualenv with pip install macholib or\n" + "use spack to install the py-macholib package\n" + "spack install py-macholib\n" + "spack activate py-macholib\n" "spack load python\n" % error) @@ -275,55 +275,50 @@ def modify_macho_object(cur_path, rpaths, deps, idpath, return -def modify_object_machotools(cur_path, rpaths, deps, idpath, - new_rpaths, new_deps, new_idpath): +def modify_object_macholib(cur_path, old_dir, new_dir): """ Modify MachO binary path_name by replacing old_dir with new_dir or the relative path to spack install root. 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 - using py-machotools + using py-macholib 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'): return try: - import machotools + from macholib.MachO import MachO + from macholib.mach_o import LC_RPATH except ImportError as e: - raise MissingMachotoolsException(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 + raise MissingMacholibException(e) + 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: - import machotools - except ImportError as e: - raise MissingMachotoolsException(e) - idpath = None - rpaths = list() - deps = list() - rewriter = machotools.rewriter_factory(path_name) - if machotools.detect.is_dylib(path_name): - idpath = rewriter.install_name - rpaths = rewriter.rpaths - deps = rewriter.dependencies - return rpaths, deps, idpath + f = open(dll.filename, 'rb+') + for header in dll.headers: + f.seek(0) + dll.write(f) + f.seek(0, 2) + f.flush() + f.close() + except Exception: + pass + + return def strings_contains_installroot(path_name, root_dir): @@ -405,48 +400,41 @@ def relocate_macho_binaries(path_names, old_dir, new_dir, allow_root): """ placeholder = set_placeholder(old_dir) for path_name in path_names: - deps = set() - idpath = None - if platform.system().lower() == 'darwin': - if path_name.endswith('.o'): - continue - else: - rpaths, deps, idpath = macho_get_paths(path_name) - else: - rpaths, deps, idpath = machotools_get_paths(path_name) - # one pass to replace placeholder - (n_rpaths, - n_deps, - n_idpath) = macho_replace_paths(placeholder, - new_dir, - rpaths, - deps, - idpath) - # another pass to replace old_dir - (new_rpaths, - new_deps, - new_idpath) = macho_replace_paths(old_dir, - new_dir, - n_rpaths, - n_deps, - n_idpath) + if path_name.endswith('.o'): + continue + if new_dir == old_dir: + continue if platform.system().lower() == 'darwin': + rpaths, deps, idpath = macho_get_paths(path_name) + # one pass to replace placeholder + (n_rpaths, + n_deps, + n_idpath) = macho_replace_paths(placeholder, + new_dir, + rpaths, + deps, + idpath) + # another pass to replace old_dir + (new_rpaths, + new_deps, + new_idpath) = macho_replace_paths(old_dir, + new_dir, + n_rpaths, + n_deps, + n_idpath) modify_macho_object(path_name, rpaths, deps, idpath, new_rpaths, new_deps, new_idpath) else: - modify_object_machotools(path_name, - rpaths, deps, idpath, - new_rpaths, new_deps, new_idpath) - - if not new_dir == old_dir: - if len(new_dir) <= len(old_dir): - replace_prefix_bin(path_name, old_dir, new_dir) - else: - tty.warn('Cannot do a binary string replacement' - ' with padding for %s' - ' because %s is longer than %s' % - (path_name, new_dir, old_dir)) + modify_object_macholib(path_name, placeholder, new_dir) + modify_object_macholib(path_name, old_dir, new_dir) + if len(new_dir) <= len(old_dir): + replace_prefix_bin(path_name, old_dir, new_dir) + else: + tty.warn('Cannot do a binary string replacement' + ' with padding for %s' + ' because %s is longer than %s' % + (path_name, new_dir, old_dir)) def relocate_elf_binaries(path_names, old_dir, new_dir, allow_root): @@ -498,21 +486,13 @@ def make_macho_binaries_relative(cur_path_names, orig_path_names, old_dir, idpath = None if platform.system().lower() == 'darwin': (rpaths, deps, idpath) = macho_get_paths(cur_path) - else: - (rpaths, deps, idpath) = machotools_get_paths(cur_path) - (new_rpaths, - new_deps, - new_idpath) = macho_make_paths_relative(orig_path, old_dir, - rpaths, deps, idpath) - if platform.system().lower() == 'darwin': + (new_rpaths, + new_deps, + new_idpath) = macho_make_paths_relative(orig_path, old_dir, + rpaths, deps, idpath) modify_macho_object(cur_path, rpaths, deps, 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 not file_is_relocatable(cur_path)): raise InstallRootStringException(cur_path, old_dir) -- cgit v1.2.3-60-g2f50