Created
May 20, 2020 22:56
-
-
Save luca-heltai/d52952a8c6334a1c5374bd96fb165c08 to your computer and use it in GitHub Desktop.
Relocate libraries utility for Mac OS X
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
## --------------------------------------------------------------------- | |
## | |
## Copyright (C) 2020 by the deal.II authors | |
## | |
## This file is part of the deal.II library. | |
## | |
## The deal.II library is free software; you can use it, redistribute | |
## it, and/or modify it under the terms of the GNU Lesser General | |
## Public License as published by the Free Software Foundation; either | |
## version 2.1 of the License, or (at your option) any later version. | |
## The full text of the license can be found in the file LICENSE.md at | |
## the top level directory of deal.II. | |
## | |
## --------------------------------------------------------------------- | |
## This script runs install_name_tool on each library under basedir (first argument | |
## on command line) and makes sure that all libraries are installed with absolute | |
## id name. This makes Mac OS X happy about not using DYLD_LIBRARY_PATH, which is generally | |
## a bad thing. All dependencies are scanned as well, and if a dependency happens to be in | |
## the same sub path, then all references to it are also changed so that full paths are | |
## used. | |
## | |
## Typical usage: download a binary library that should go under /usr/local/somename, | |
## unpack it somewhere else, and run this script in the new directory. A pickle binary | |
## file is left in the new directory, so that if you move the entire directory somewhere | |
## else, and run the script again, we only need to replace directory names in a pre-filled | |
## dictionary, instead of looking for all binary files. | |
import os | |
import sys | |
import pickle | |
from subprocess import check_output as run | |
basedir = os.getcwd() | |
if len(sys.argv) > 1: | |
basedir = sys.argv[1] | |
# basedir = '/Applications/deal.II.app/Contents/Resources/' | |
pickle_file = os.path.join(basedir,'dependencies.pickle') | |
exclude = ['.git', '.spack', 'include', 'pkgconfig', 'cmake', 'share', 'etc'] | |
def myrun(command): | |
return run(command).decode(errors='ignore') | |
## BinaryFile | |
# | |
# Stores the name of a library, its install name, its location and all its dependencies, as given by `otool -L` | |
class BinaryFile(object): | |
file_type = None | |
location = None | |
install_name = None | |
name = None | |
deps = None | |
arch = None | |
def __str__(self): | |
mystr = "" | |
mystr += "Name :"+str(self.name) | |
mystr +="\nFile Type :"+str(self.file_type) | |
mystr +="\nInstall name:"+str(self.install_name) | |
mystr +="\nLocation :"+str(self.location) | |
mystr +="\nDeps :"+str(self.deps) | |
mystr +="\nArch :"+str(self.arch) | |
return mystr | |
def file_info(filename): | |
f = os.path.basename(filename) | |
d = os.path.dirname(filename) | |
file_result = myrun(["file", filename]).split() | |
a = BinaryFile() | |
a.file_type = file_result[-2] | |
a.arch = file_result[-1] | |
a.location = filename | |
a.name = f | |
if os.path.isfile(filename) and not os.path.islink(filename) and a.file_type == 'library': | |
a.install_name = myrun(["otool", "-D", filename]).split('\n')[1] | |
long_deps = myrun(["otool", "-L", filename]).split('\n') | |
a.deps = [dep.partition(' ')[0][1::] for dep in long_deps[2:-1]] | |
return a | |
elif file_result[1] == 'Mach-O' and not os.path.islink(filename) and a.file_type == 'executable': | |
long_deps = myrun(["otool", "-L", a.location]).split('\n') | |
a.deps = [dep.partition(' ')[0][1::] for dep in long_deps[1:-1]] | |
return a | |
return 'None' | |
def fix_install_names(lib): | |
if lib == 'None': | |
return lib | |
if lib.file_type == 'library' and lib.location != lib.install_name: | |
try: | |
run(["install_name_tool",'-id',lib.location, lib.location]) | |
print(lib.install_name + " --> " + lib.location) | |
lib.install_name = lib.location | |
except: | |
print("Failed to changed id on ",lib.name," from ",lib.install_name," to ",lib.location) | |
print( "(",lib.name,")") | |
return 'None' | |
return lib | |
# Walk the current tree, and extract all binary files as a dictionary, fixing install name in the meantime | |
def get_binary_files(exclude_list): | |
binaries = [] | |
for root, dirs, files in os.walk(basedir): | |
for d in exclude_list: | |
if d in dirs: | |
dirs.remove(d) | |
print("Processing dir: ", root) | |
for f in files: | |
filename = os.path.join(root, f) | |
info = fix_install_names(file_info(filename)) | |
if info != 'None': | |
binaries += [fix_install_names(info)] | |
return {file.name+"_"+file.arch: file for file in binaries} | |
def fix_dependencies(this_lib, binary_files): | |
all_lib_keys = [os.path.basename(dep)+'_'+this_lib.arch for dep in this_lib.deps] | |
my_deps_keys_and_locations = [(key, location) for key,location in zip(all_lib_keys,this_lib.deps) if key in binary_files] | |
my_deps_relocations = [(location, binary_files[key].location) for key,location in my_deps_keys_and_locations] | |
to_be_fixed = [(old, new) for (old,new) in my_deps_relocations if old != new] | |
command = ['install_name_tool'] | |
for old, new in to_be_fixed: | |
command += ['-change', old, new] | |
try: | |
if len(command) != 1: | |
command += [this_lib.location] | |
print('Processing', this_lib.name) | |
print('======================') | |
print(" ".join(command)) | |
print('======================\n\n') | |
run(command) | |
except: | |
print('\n\n*********************************************') | |
print('Last command failed!') | |
print(" ".join(command)) | |
print('*********************************************\n\n') | |
return file_info(this_lib.location) | |
try: | |
binary_files, pickled_basedir = pickle.load(open(pickle_file, 'rb')) | |
print('Found pickle file: ', pickle_file) | |
print('Pickled base dir : ', pickled_basedir) | |
except: | |
print('Regenerating binary files list (WARNING!!! This may take a LONG time.)') | |
binary_files = get_binary_files(exclude) | |
pickled_basedir = basedir | |
# If someone moved the directory somewhere else, replace all occurences of pickled_basedir with basedir | |
# and adjust install names of libraries | |
if pickled_basedir != basedir: | |
print('Relocating all libraries from', pickled_basedir, 'to', basedir) | |
for key, lib in binary_files.items(): | |
lib.location.replace(pickled_basedir, basedir, 1) | |
binary_files[key] = fix_install_names(lib) | |
# Now fix also all dependencies | |
for key, lib in binary_files.items(): | |
binary_files[key] = fix_dependencies(lib, binary_files) | |
# And dump the new binary files | |
pickle.dump((binary_files, basedir), open(pickle_file, 'wb')) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment