Skip to content

Instantly share code, notes, and snippets.

@vabock
Created April 3, 2016 08:34
Show Gist options
  • Save vabock/66f110018ea80b52621159a2cd50d456 to your computer and use it in GitHub Desktop.
Save vabock/66f110018ea80b52621159a2cd50d456 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# Postinstall script for PySide
#
# TODO:
# This file can be removed after OSX support
# is implemented in pyside_build.update_rpath()
import os, sys, traceback, shutil, fnmatch, stat
from os.path import dirname, abspath
from subprocess import Popen, PIPE
import re
def filter_match(name, patterns):
for pattern in patterns:
if pattern is None:
continue
if fnmatch.fnmatch(name, pattern):
return True
return False
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()
if not isinstance(out, str):
# python 3
out = out.decode()
err = err.decode()
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\n*** %s' %
(retcode, err))
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 osx_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 post_install_osx():
# Try to find PySide package
try:
import PySide
except ImportError:
print("The PySide package not found: %s" % traceback.print_exception(*sys.exc_info()))
return
pyside_path = os.path.abspath(os.path.dirname(PySide.__file__))
print("PySide package found in %s..." % pyside_path)
pyside_libs = [lib for lib in os.listdir(pyside_path) if filter_match(
lib, ["*.so", "*.dylib", "shiboken"])]
# Update rpath in PySide libs
for srcname in pyside_libs:
srcpath = os.path.join(pyside_path, srcname)
if os.path.isdir(srcpath):
continue
if not os.path.exists(srcpath):
continue
osx_localize_libpaths(srcpath, pyside_libs, pyside_path)
print("Patched rpath in %s to %s." % (srcpath, pyside_path))
# Check PySide installation status
try:
from PySide import QtCore
print("PySide package successfully installed in %s..." % \
os.path.abspath(os.path.dirname(QtCore.__file__)))
except ImportError:
print("The PySide package not installed: %s" % traceback.print_exception(*sys.exc_info()))
if __name__ == '__main__':
if sys.platform == "darwin":
post_install_osx()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment