From 2cf788b1a118c3ce630e0ae5e850641ea71d0c02 Mon Sep 17 00:00:00 2001 From: Roman Lacko Date: Sun, 23 Nov 2014 23:09:07 +0100 Subject: Remove the need to use the post-install script on Linux platform by patching the rpath at build time + exclude patchelf executable from binary distribution --- pyside_postinstall.py | 170 +++++--------------------------------------------- setup.py | 68 ++++++++++++++------ utils.py | 10 ++- 3 files changed, 69 insertions(+), 179 deletions(-) diff --git a/pyside_postinstall.py b/pyside_postinstall.py index 146d313b9..9230a55ad 100644 --- a/pyside_postinstall.py +++ b/pyside_postinstall.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # Postinstall script for PySide # -# Generates the qt.conf file -# -# This file is based on pywin32_postinstall.py file from pywin32 project +# 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 @@ -11,25 +11,6 @@ from subprocess import Popen, PIPE import re -try: - # When this script is run from inside the bdist_wininst installer, - # file_created() and directory_created() are additional builtin - # functions which write lines to Python23\pyside-install.log. This is - # a list of actions for the uninstaller, the format is inspired by what - # the Wise installer also creates. - file_created - is_bdist_wininst = True -except NameError: - is_bdist_wininst = False # we know what it is not - but not what it is :) - def file_created(file): - pass - -def install(): - if sys.platform == "win32": - install_win32() - else: - install_posix() - def filter_match(name, patterns): for pattern in patterns: if pattern is None: @@ -38,19 +19,6 @@ def filter_match(name, patterns): return True return False -def set_exec(name): - mode = os.stat(name).st_mode - new_mode = mode - if new_mode & stat.S_IRUSR: - new_mode = new_mode | stat.S_IXUSR - if new_mode & stat.S_IRGRP: - new_mode = new_mode | stat.S_IXGRP - if new_mode & stat.S_IROTH: - new_mode = new_mode | stat.S_IXOTH - if (mode != new_mode): - print("Setting exec for '%s' (mode %o => %o)" % (name, mode, new_mode)) - os.chmod(name, new_mode) - def back_tick(cmd, ret_err=False): """ Run command `cmd`, return stdout, or stdout, stderr if `ret_err` @@ -152,7 +120,7 @@ def osx_get_rpaths(libpath): return rpaths -def localize_libpaths(libpath, local_libs, enc_path=None): +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 @@ -185,7 +153,7 @@ def localize_libpaths(libpath, local_libs, enc_path=None): (enc_path, libpath)) -def install_posix(): +def post_install_osx(): # Try to find PySide package try: import PySide @@ -195,40 +163,17 @@ def install_posix(): pyside_path = os.path.abspath(os.path.dirname(PySide.__file__)) print("PySide package found in %s..." % pyside_path) - executables = ['shiboken'] - if sys.platform.startswith('linux'): - 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 executable in executables: - execpath = os.path.join(pyside_path, executable) - set_exec(execpath) + 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: - if os.path.isdir(srcname): - continue srcpath = os.path.join(pyside_path, srcname) - rpath_cmd(pyside_path, srcpath) + 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 @@ -240,93 +185,6 @@ def install_posix(): print("The PySide package not installed: %s" % traceback.print_exception(*sys.exc_info())) -def install_win32(): - # Try to find PySide package - try: - from PySide import QtCore - except ImportError: - print("The PySide package not found: %s" % traceback.print_exception(*sys.exc_info())) - return - pyside_path = os.path.dirname(QtCore.__file__) - pyside_path = pyside_path.replace("\\", "/") - pyside_path = pyside_path.replace("lib/site-packages", "Lib/site-packages") - print("PySide package found in %s..." % pyside_path) - - # Since version 1.2.0 there is no need to run post install procedure on win32 - from PySide import __version_info__ as pyside_version_info - if pyside_version_info >= (1,2,0): - return - - if is_bdist_wininst: - # Run from inside the bdist_wininst installer - import distutils.sysconfig - exec_prefix = distutils.sysconfig.get_config_var("exec_prefix") - else: - # Run manually - exec_prefix = os.path.dirname(sys.executable) - - # Generate qt.conf - qtconf_path = os.path.join(exec_prefix, "qt.conf") - print("Generating file %s..." % qtconf_path) - f = open(qtconf_path, 'wt') - file_created(qtconf_path) - f.write("""[Paths] -Prefix = %(pyside_prefix)s -Binaries = %(pyside_prefix)s -Plugins = %(pyside_prefix)s/plugins -Translations = %(pyside_prefix)s/translations -""" % { "pyside_prefix": pyside_path }) - print("The PySide extensions were successfully installed.") - - # Install OpenSSL libs - for dll in ["libeay32.dll", "ssleay32.dll"]: - dest_path = os.path.join(exec_prefix, dll) - src_path = os.path.join(pyside_path, "openssl", dll) - if not os.path.exists(dest_path) and os.path.exists(src_path): - shutil.copy(src_path, dest_path) - file_created(dest_path) - print("Installed %s to %s." % (dll, exec_prefix)) - -def uninstall(): - print("The PySide extensions were successfully uninstalled.") - -def usage(): - msg = \ -"""%s: A post-install script for the PySide extensions. - -This should be run automatically after installation, but if it fails you -can run it again with a '-install' parameter, to ensure the environment -is setup correctly. -""" - print(msg.strip() % os.path.basename(sys.argv[0])) - -# NOTE: If this script is run from inside the bdist_wininst created -# binary installer or uninstaller, the command line args are either -# '-install' or '-remove'. - -# Important: From inside the binary installer this script MUST NOT -# call sys.exit() or raise SystemExit, otherwise not only this script -# but also the installer will terminate! (Is there a way to prevent -# this from the bdist_wininst C code?) - if __name__ == '__main__': - if len(sys.argv)==1: - usage() - sys.exit(1) - - arg_index = 1 - while arg_index < len(sys.argv): - arg = sys.argv[arg_index] - if arg == "-install": - install() - elif arg == "-remove": - # bdist_msi calls us before uninstall, so we can undo what we - # previously did. Sadly, bdist_wininst calls us *after*, so - # we can't do much at all. - if not is_bdist_wininst: - uninstall() - else: - print("Unknown option: %s" % arg) - usage() - sys.exit(0) - arg_index += 1 + if sys.platform == "darwin": + post_install_osx() diff --git a/setup.py b/setup.py index 942cf644a..442aaefb2 100644 --- a/setup.py +++ b/setup.py @@ -102,6 +102,7 @@ from utils import option_value from utils import update_env_path from utils import init_msvc_env from utils import regenerate_qt_resources +from utils import filter_match # Declare options OPTION_DEBUG = has_option("debug") @@ -233,11 +234,13 @@ for pkg in ["pyside_package/PySide", "pyside_package/pysideuic"]: pkg_dir = os.path.join(script_dir, pkg) os.makedirs(pkg_dir) +# TODO: +# This class can be removed after OSX support +# is implemented in pyside_build.update_rpath() class pyside_install(_install): def run(self): _install.run(self) - # Custom script we run at the end of installing - this is the same script - # run by bdist_wininst + # Custom script we run at the end of installing # If self.root has a value, it means we are being "installed" into # some other directory than Python itself (eg, into a temp directory # for bdist_wininst to use) - in which case we must *not* run our @@ -250,7 +253,6 @@ class pyside_install(_install): cmd = [ os.path.abspath(sys.executable), filename, - "-install" ] run_process(cmd) @@ -650,12 +652,8 @@ class pyside_build(_build): return self.prepare_packages_posix(vars) def prepare_packages_posix(self, vars): + executables = [] if sys.platform.startswith('linux'): - # patchelf -> PySide/patchelf - copyfile( - "{script_dir}/patchelf", - "{dist_dir}/PySide/patchelf", - vars=vars) so_ext = '.so' so_star = so_ext + '.*' elif sys.platform == 'darwin': @@ -694,7 +692,7 @@ class pyside_build(_build): "{dist_dir}/PySide/scripts/uic.py", force=False, vars=vars) # /bin/* -> PySide/ - copydir( + executables.extend(copydir( "{install_dir}/bin/", "{dist_dir}/PySide", filter=[ @@ -702,7 +700,7 @@ class pyside_build(_build): "pyside-rcc", "shiboken", ], - recursive=False, vars=vars) + recursive=False, vars=vars)) # /lib/lib* -> PySide/ copydir( "{install_dir}/lib/", @@ -740,7 +738,7 @@ class pyside_build(_build): if sys.platform == 'darwin': raise RuntimeError('--standalone not yet supported for OSX') # /bin/* -> /PySide - copydir("{qt_bin_dir}", "{dist_dir}/PySide", + executables.extend(copydir("{qt_bin_dir}", "{dist_dir}/PySide", filter=[ "designer", "linguist", @@ -748,7 +746,7 @@ class pyside_build(_build): "lupdate", "lconvert", ], - recursive=False, vars=vars) + recursive=False, vars=vars)) # /lib/* -> /PySide copydir("{qt_lib_dir}", "{dist_dir}/PySide", filter=[ @@ -769,6 +767,9 @@ class pyside_build(_build): copydir("{qt_translations_dir}", "{dist_dir}/PySide/translations", filter=["*.qm"], vars=vars) + # Update rpath to $ORIGIN + if sys.platform.startswith('linux'): + self.update_rpath("{dist_dir}/PySide".format(**vars), executables) def prepare_packages_win32(self, vars): pdbs = ['*.pdb'] if self.debug or self.build_type == 'RelWithDebInfo' else [] @@ -923,6 +924,41 @@ class pyside_build(_build): "{dist_dir}/PySide/pyside-python{py_version}{dbgPostfix}.pdb", vars=vars) + def update_rpath(self, package_path, executables): + if sys.platform.startswith('linux'): + pyside_libs = [lib for lib in os.listdir(package_path) if filter_match( + lib, ["*.so", "*.so.*"])] + + patchelf_path = os.path.join(self.script_dir, "patchelf") + + def rpath_cmd(srcpath): + cmd = [patchelf_path, '--set-rpath', '$ORIGIN/', srcpath] + if run_process(cmd) != 0: + raise RuntimeError("Error patching rpath in " + srcpath) + + # TODO: Add support for OSX platform + #elif sys.platform == 'darwin': + # pyside_libs = [lib for lib in os.listdir(package_path) if filter_match( + # lib, ["*.so", "*.dylib"])] + # def rpath_cmd(srcpath): + # utils.osx_localize_libpaths(srcpath, pyside_libs, None) + + else: + raise RuntimeError('Not configured for platform ' + + sys.platform) + + pyside_libs.extend(executables) + + # Update rpath in PySide libs + for srcname in pyside_libs: + srcpath = os.path.join(package_path, srcname) + if os.path.isdir(srcpath): + continue + if not os.path.exists(srcpath): + continue + rpath_cmd(srcpath) + print("Patched rpath to '$ORIGIN/' in %s." % (srcpath)) + try: with open(os.path.join(script_dir, 'README.rst')) as f: @@ -938,14 +974,6 @@ setup( version = __version__, description = ("Python bindings for the Qt cross-platform application and UI framework"), long_description = README + "\n\n" + CHANGES, - options = { - "bdist_wininst": { - "install_script": "pyside_postinstall.py", - }, - "bdist_msi": { - "install_script": "pyside_postinstall.py", - }, - }, scripts = [ "pyside_postinstall.py" ], diff --git a/utils.py b/utils.py index bf93579a8..7b40bb2ac 100644 --- a/utils.py +++ b/utils.py @@ -233,6 +233,8 @@ def copyfile(src, dst, force=True, vars=None): log.info("Copying file %s to %s." % (src, dst)) shutil.copy2(src, dst) + + return dst def makefile(dst, content=None, vars=None): @@ -269,13 +271,14 @@ def copydir(src, dst, filter=None, ignore=None, force=True, if not os.path.exists(src) and not force: log.info("**Skiping copy tree %s to %s. Source does not exists. filter=%s. ignore=%s." % \ (src, dst, filter, ignore)) - return + return [] log.info("Copying tree %s to %s. filter=%s. ignore=%s." % \ (src, dst, filter, ignore)) names = os.listdir(src) + results = [] errors = [] for name in names: srcname = os.path.join(src, name) @@ -283,14 +286,14 @@ def copydir(src, dst, filter=None, ignore=None, force=True, try: if os.path.isdir(srcname): if recursive: - copydir(srcname, dstname, filter, ignore, force, recursive, vars) + results.extend(copydir(srcname, dstname, filter, ignore, force, recursive, vars)) else: if (filter is not None and not filter_match(name, filter)) or \ (ignore is not None and filter_match(name, ignore)): continue if not os.path.exists(dst): os.makedirs(dst) - copyfile(srcname, dstname, True, vars) + results.append(copyfile(srcname, dstname, True, vars)) # catch the Error from the recursive copytree so that we can # continue with other files except shutil.Error as err: @@ -308,6 +311,7 @@ def copydir(src, dst, filter=None, ignore=None, force=True, errors.extend((src, dst, str(why))) if errors: raise EnvironmentError(errors) + return results def rmtree(dirname): -- cgit v1.2.3