aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Brett <matthew.brett@gmail.com>2012-11-04 01:12:06 -0700
committerMatthew Brett <matthew.brett@gmail.com>2012-11-06 11:59:31 -0800
commit2339adcd24ef7a0ea08ada8940ed0c0533ba7bf4 (patch)
tree9f641823ee3d669b1f3f172bd739b050a20e8d55
parentfe817653050b450f4d91a39b48623f757f91aca4 (diff)
FIX: add support for rpath patching in OSX
The code adds rpaths to allow local relative libraries to get loaded at link time, for linux only, using the patchelf binary. This commit adds some routines to do the equivalent thing in OSX, using the ``otool`` and ``install_name_tool`` binaries.
-rw-r--r--pyside_postinstall.py183
1 files changed, 166 insertions, 17 deletions
diff --git a/pyside_postinstall.py b/pyside_postinstall.py
index 7cc0ab22e..1d162ac91 100644
--- a/pyside_postinstall.py
+++ b/pyside_postinstall.py
@@ -5,6 +5,10 @@
# This file is based on pywin32_postinstall.py file from pywin32 project
import os, sys, traceback, shutil, fnmatch, stat
+from os.path import dirname, abspath
+from subprocess import Popen, PIPE
+import re
+
try:
# When this script is run from inside the bdist_wininst installer,
@@ -23,7 +27,7 @@ def install():
if sys.platform == "win32":
install_win32()
else:
- install_linux()
+ install_posix()
def filter_match(name, patterns):
for pattern in patterns:
@@ -46,7 +50,138 @@ def set_exec(name):
print("Setting exec for '%s' (mode %o => %o)" % (name, mode, new_mode))
os.chmod(name, new_mode)
-def install_linux():
+
+def back_tick(cmd, ret_err=False):
+ """ Run command `cmd`, return stdout, or stdout, stderr if `ret_err`
+
+ Roughly equivalent to ``check_output`` in Python 2.7
+
+ Parameters
+ ----------
+ cmd : str
+ command to execute
+ ret_err : bool, optional
+ If True, return stderr in addition to stdout. If False, just return
+ stdout
+
+ Returns
+ -------
+ out : str or tuple
+ If `ret_err` is False, return stripped string containing stdout from
+ `cmd`. If `ret_err` is True, return tuple of (stdout, stderr) where
+ ``stdout`` is the stripped stdout, and ``stderr`` is the stripped
+ stderr.
+
+ Raises
+ ------
+ Raises RuntimeError if command returns non-zero exit code
+ """
+ proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
+ out, err = proc.communicate()
+ retcode = proc.returncode
+ if retcode is None:
+ proc.terminate()
+ raise RuntimeError(cmd + ' process did not terminate')
+ if retcode != 0:
+ raise RuntimeError(cmd + ' process returned code %d' % retcode)
+ out = out.strip()
+ if not ret_err:
+ return out
+ return out, err.strip()
+
+
+OSX_OUTNAME_RE = re.compile(r'\(compatibility version [\d.]+, current version '
+ '[\d.]+\)')
+
+def osx_get_install_names(libpath):
+ """ Get OSX library install names from library `libpath` using ``otool``
+
+ Parameters
+ ----------
+ libpath : str
+ path to library
+
+ Returns
+ -------
+ install_names : list of str
+ install names in library `libpath`
+ """
+ out = back_tick('otool -L ' + libpath)
+ libs = [line for line in out.split('\n')][1:]
+ return [OSX_OUTNAME_RE.sub('', lib).strip() for lib in libs]
+
+
+OSX_RPATH_RE = re.compile(r"path (.+) \(offset \d+\)")
+
+def osx_get_rpaths(libpath):
+ """ Get rpaths from library `libpath` using ``otool``
+
+ Parameters
+ ----------
+ libpath : str
+ path to library
+
+ Returns
+ -------
+ rpaths : list of str
+ rpath values stored in ``libpath``
+
+ Notes
+ -----
+ See ``man dyld`` for more information on rpaths in libraries
+ """
+ lines = back_tick('otool -l ' + libpath).split('\n')
+ ctr = 0
+ rpaths = []
+ while ctr < len(lines):
+ line = lines[ctr].strip()
+ if line != 'cmd LC_RPATH':
+ ctr += 1
+ continue
+ assert lines[ctr + 1].strip().startswith('cmdsize')
+ rpath_line = lines[ctr + 2].strip()
+ match = OSX_RPATH_RE.match(rpath_line)
+ if match is None:
+ raise RuntimeError('Unexpected path line: ' + rpath_line)
+ rpaths.append(match.groups()[0])
+ ctr += 3
+ return rpaths
+
+
+def localize_libpaths(libpath, local_libs, enc_path=None):
+ """ Set rpaths and install names to load local dynamic libs at run time
+
+ Use ``install_name_tool`` to set relative install names in `libpath` (as
+ named in `local_libs` to be relative to `enc_path`. The default for
+ `enc_path` is the directory containing `libpath`.
+
+ Parameters
+ ----------
+ libpath : str
+ path to library for which to set install names and rpaths
+ local_libs : sequence of str
+ library (install) names that should be considered relative paths
+ enc_path : str, optional
+ path that does or will contain the `libpath` library, and to which the
+ `local_libs` are relative. Defaults to current directory containing
+ `libpath`.
+ """
+ if enc_path is None:
+ enc_path = abspath(dirname(libpath))
+ install_names = osx_get_install_names(libpath)
+ need_rpath = False
+ for install_name in install_names:
+ if install_name[0] in '/@':
+ continue
+ back_tick('install_name_tool -change %s @rpath/%s %s' %
+ (install_name, install_name, libpath))
+ need_rpath = True
+ if need_rpath and enc_path not in osx_get_rpaths(libpath):
+ back_tick('install_name_tool -add_rpath %s %s' %
+ (enc_path, libpath))
+
+
+def install_posix():
# Try to find PySide package
try:
import PySide
@@ -56,27 +191,40 @@ def install_linux():
pyside_path = os.path.abspath(os.path.dirname(PySide.__file__))
print("PySide package found in %s..." % pyside_path)
+ executables = ['shiboken']
+ if sys.platform == 'linux2':
+ executables.append('patchelf')
+ patchelf_path = os.path.join(pyside_path, "patchelf")
+ from distutils.spawn import spawn
+
+ def rpath_cmd(pyside_path, srcpath):
+ cmd = [patchelf_path, '--set-rpath', pyside_path, srcpath]
+ spawn(cmd, search_path=False, verbose=1)
+
+ pyside_libs = [lib for lib in os.listdir(pyside_path) if filter_match(
+ lib, ["Qt*.so", "phonon.so", "shiboken"])]
+ elif sys.platform == 'darwin':
+ pyside_libs = [lib for lib in os.listdir(pyside_path) if filter_match(
+ lib, ["*.so", "*.dylib", "shiboken"])]
+
+ def rpath_cmd(pyside_path, srcpath):
+ localize_libpaths(srcpath, pyside_libs, pyside_path)
+
+ else:
+ raise RuntimeError('Not configured for platform ' +
+ sys.platform)
+
# Set exec mode on executables
- for elf in ["patchelf", "shiboken"]:
- elfpath = os.path.join(pyside_path, elf)
- set_exec(elfpath)
+ for executable in executables:
+ execpath = os.path.join(pyside_path, executable)
+ set_exec(execpath)
# Update rpath in PySide libs
- from distutils.spawn import spawn
- patchelf_path = os.path.join(pyside_path, "patchelf")
- for srcname in os.listdir(pyside_path):
+ for srcname in pyside_libs:
if os.path.isdir(srcname):
continue
- if not filter_match(srcname, ["Qt*.so", "phonon.so", "shiboken", "shiboken.so"]):
- continue
srcpath = os.path.join(pyside_path, srcname)
- cmd = [
- patchelf_path,
- "--set-rpath",
- pyside_path,
- srcpath,
- ]
- spawn(cmd, search_path=0, verbose=1)
+ rpath_cmd(pyside_path, srcpath)
print("Patched rpath in %s to %s." % (srcpath, pyside_path))
# Check PySide installation status
@@ -87,6 +235,7 @@ def install_linux():
except ImportError:
print("The PySide package not installed: %s" % traceback.print_exception(*sys.exc_info()))
+
def install_win32():
# Try to find PySide package
try: