Skip to content

Instantly share code, notes, and snippets.

@luca-heltai
Created May 20, 2020 22:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luca-heltai/d52952a8c6334a1c5374bd96fb165c08 to your computer and use it in GitHub Desktop.
Save luca-heltai/d52952a8c6334a1c5374bd96fb165c08 to your computer and use it in GitHub Desktop.
Relocate libraries utility for Mac OS X
#!/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