Skip to content

Instantly share code, notes, and snippets.

@linusyang
Last active August 29, 2015 14:13
Show Gist options
  • Save linusyang/13338b73b204a702a10f to your computer and use it in GitHub Desktop.
Save linusyang/13338b73b204a702a10f to your computer and use it in GitHub Desktop.
Fix ghc rpath issue when dynamically linking
#!/usr/bin/env python
'''
Fix the rpath issue when cabal (ghc) links dynamically on Linux and OS X.
Put this script in your .cabal/bin for execution.
'''
import os
import re
import sys
import platform
from subprocess import Popen, PIPE
def is_linux():
return platform.system() == 'Linux'
def replace_path(orig_path, libname, fpath, libpath):
found = False
for root, directories, files in os.walk(libpath):
for d in directories:
if d.find(libname) != -1:
new_path = os.path.join(root, d)
if is_linux():
return new_path
try:
process = Popen(['install_name_tool', '-rpath', orig_path, new_path, fpath], stdout=PIPE, stderr=PIPE)
result = process.communicate()[0]
if process.returncode != 0:
print "failed to fix @rpath for %s (%s): %s" % (fpath, libname, result)
else:
print "fix @rpath for %s (%s)" % (fpath, libname)
except Exception, e:
print "Warning: run install_name_tool failed: %s" % str(e)
found = True
break
if found:
break
def check_link_elf(fpath, libpath):
try:
process = Popen(['readelf', '-d', fpath], stdout=PIPE, stderr=PIPE)
lpaths = process.communicate()[0]
except Exception, e:
print "Warning: run readelf failed: %s" % str(e)
return
matches = re.search(r'Library rpath: \[(.+)\]', lpaths)
if matches:
orig_paths = matches.group(1).split(':')
libnames = []
for idx, orig_path in enumerate(orig_paths):
if orig_path.startswith('/tmp'):
lib_match = re.search(r'/tmp/.*/([^/]+)/dist/build', lpaths)
if not lib_match:
continue
libname = lib_match.group(1)
new_path = replace_path(orig_path, libname, fpath, libpath)
orig_paths[idx] = new_path
libnames.append(libname)
if libnames:
try:
process = Popen(['patchelf', '--set-rpath', ':'.join(orig_paths), fpath], stdout=PIPE, stderr=PIPE)
result = process.communicate()[0]
libnames_str = ', '.join(libnames)
if process.returncode != 0:
print "failed to fix rpath for %s (%s): %s" % (fpath, libnames_str, result)
else:
print "fix rpath for %s (%s)" % (fpath, libnames_str)
except Exception, e:
print "Warning: run patchelf failed: %s" % str(e)
def check_link(fpath, libpath):
if is_linux():
check_link_elf(fpath, libpath)
return
lpaths = None
try:
process = Popen(['otool', '-l', fpath], stdout=PIPE, stderr=PIPE)
lpaths = process.communicate()[0]
except Exception, e:
print "Warning: run otool failed: %s" % str(e)
return
matches = re.search(r'/private/var/folders/.*/([^/]+)/dist/build', lpaths)
if matches:
orig_path = matches.group(0)
libname = matches.group(1)
replace_path(orig_path, libname, fpath, libpath)
def fix_library(fpath, libpath, libshort):
if is_linux():
return
lpaths = None
try:
process = Popen(['otool', '-l', fpath], stdout=PIPE, stderr=PIPE)
lpaths = process.communicate()[0]
except Exception, e:
print "Warning: run otool failed: %s" % str(e)
return
matches = re.search('path (/.*/(%s-?[0-9\\.]*)) ' % libshort, lpaths)
if matches:
orig_path = matches.group(1)
libname = matches.group(2)
replace_path(orig_path, libname, fpath, libpath)
def main():
current_dir = os.path.dirname(os.path.realpath(__file__))
script_name = os.path.basename(os.path.realpath(__file__))
libpath = os.path.realpath(os.path.join(current_dir, '../lib'))
for name in os.listdir(current_dir):
fpath = os.path.join(current_dir, name)
if fpath != script_name and os.path.isfile(fpath) and os.access(fpath, os.X_OK):
if len(sys.argv) > 1:
libs = sys.argv[1:]
for lib in libs:
fix_library(fpath, libpath, lib)
else:
check_link(fpath, libpath)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment