From 9e70e1ba0eb6582e745929bc31e5c2a71e71ed8a Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Wed, 18 Apr 2012 03:33:07 +0000 Subject: [PATCH] Set install names using @loader_path in the extension modules and the wx libs so they can be loaded from the same folder and they can be included in the wx package dir. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71226 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- .gitignore | 1 + build.py | 54 +++++++++++++++++++-------------- buildtools/config.py | 58 ++++++++++++++++++++++++++++++++++-- packaging/README.txt | 23 +++++++------- packaging/phoenix_environ.sh | 1 - 5 files changed, 99 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 9a435aa2..d21bc056 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,5 @@ /wx/*.pi /wx/*.so /wx/*.pyd +/wx/*.dylib /wx/locale diff --git a/build.py b/build.py index eef02f41..a8cfc3f8 100755 --- a/build.py +++ b/build.py @@ -20,7 +20,8 @@ import urllib2 from distutils.dep_util import newer, newer_group from buildtools.config import Config, msg, opj, posixjoin, loadETG, etg2sip, findCmd, \ - phoenixDir, wxDir, copyIfNewer + phoenixDir, wxDir, copyIfNewer, copyFile, \ + macFixDependencyInstallName, macSetLoaderNames import buildtools.version as version @@ -360,21 +361,6 @@ def delFiles(fileList, verbose=True): os.remove(afile) -def macFixDependencyInstallName(destdir, prefix, extension, buildDir): - print("**** macFixDependencyInstallName(%s, %s, %s, %s)" % (destdir, prefix, extension, buildDir)) - pwd = os.getcwd() - os.chdir(destdir+prefix+'/lib') - dylibs = glob.glob('*.dylib') - for lib in dylibs: - #cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ - # (destdir+prefix,lib, prefix,lib, extension) - cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ - (buildDir,lib, prefix,lib, extension) - print(cmd) - os.system(cmd) - os.chdir(pwd) - - def getSipCmd(): # Returns the sip command to use, checking for an explicit version and @@ -863,15 +849,27 @@ def copyWxDlls(options): dlls += glob.glob(os.path.join(msw.dllDir, "wx*%sud_*.pdb" % ver)) for dll in dlls: - shutil.copyfile(dll, posixjoin(phoenixDir(), cfg.PKGDIR, os.path.basename(dll))) + copyIfNewer(dll, posixjoin(phoenixDir(), cfg.PKGDIR, os.path.basename(dll)), verbose=True) + elif isDarwin: + # Copy the wxWidgets dylibs + cfg = Config() + wxlibdir = os.path.join(getBuildDir(options), "lib") + dlls = glob.glob(wxlibdir + '/*.dylib') + for dll in dlls: + copyIfNewer(dll, posixjoin(phoenixDir(), cfg.PKGDIR, os.path.basename(dll)), verbose=True) + + # Now use install_name_tool to change the extension modules to look + # in the same folder for the wx libs, instead of the build dir. Also + # change the wx libs the same way. + macSetLoaderNames(glob.glob(opj(phoenixDir(), cfg.PKGDIR, '*.so')) + + glob.glob(opj(phoenixDir(), cfg.PKGDIR, '*.dylib'))) + def setup_py(options, args): cmdTimer = CommandTimer('setup_py') - copyWxDlls(options) - BUILD_DIR = getBuildDir(options) DESTDIR = options.installdir PREFIX = options.prefix @@ -922,6 +920,8 @@ def setup_py(options, args): (build_mode, " ".join(build_options), options.extra_setup) runcmd(command) + copyWxDlls(options) + # Do an install? if options.install: # only add the --prefix flag if we have an explicit request to do @@ -937,6 +937,8 @@ def setup_py(options, args): (WXPY_PREFIX, " ".join(build_options), options.extra_setup) runcmd(command) + # NOTE: Can probably get rid of this if we keep the code that is + # setting @loader_path in the install names... if isDarwin and DESTDIR: # Now that we are finished with the build fix the ids and # names in the wx .dylibs @@ -962,8 +964,6 @@ def waf_py(options, args): cmdTimer = CommandTimer('waf_py') waf = getWafCmd() - copyWxDlls(options) - BUILD_DIR = getBuildDir(options) DESTDIR = options.installdir PREFIX = options.prefix @@ -990,6 +990,13 @@ def waf_py(options, args): cmd = '%s %s %s configure build %s' % (PYTHON, waf, ' '.join(build_options), options.extra_waf) runcmd(cmd) + copyWxDlls(options) + + # Do an install? + if options.install: + pass # TODO... + + print("\n------------ BUILD FINISHED ------------") print("To run the wxPython demo:") print(" - Set your PYTHONPATH variable to %s." % phoenixDir()) @@ -1037,7 +1044,7 @@ def clean_py(options, args): deleteIfExists(build_base) deleteIfExists('build_waf') # make this smarter later, or just use 'build' for waf too files = list() - for wc in ['*.py', '*.pyc', '*.so', '*.pyd', '*.pdb', '*.pi']: + for wc in ['*.py', '*.pyc', '*.so', '*.dylib', '*.pyd', '*.pdb', '*.pi']: files += glob.glob(opj(cfg.PKGDIR, wc)) if isWindows: msw = getMSWSettings(options) @@ -1212,9 +1219,9 @@ def bdist(options, args): from ftplib import FTP ftp = FTP(parser.get("FTP", "host")) ftp.login(parser.get("FTP", "user"), parser.get("FTP", "pass")) - f = open(tarfilename, 'rb') ftp_dir = parser.get("FTP", "dir") old_files = ftp.nlst(ftp_dir) + old_files.sort() to_delete = [] for afile in old_files: @@ -1226,6 +1233,7 @@ def bdist(options, args): ftp.delete("%s/%s" % (ftp_dir, to_delete[i])) ftp_path = '%s/%s' % (ftp_dir, os.path.basename(tarfilename)) print("Uploading package (this may take some time)...") + f = open(tarfilename, 'rb') ftp.storbinary('STOR %s' % ftp_path, f) ftp.close() diff --git a/buildtools/config.py b/buildtools/config.py index 850c0552..f9e3205d 100644 --- a/buildtools/config.py +++ b/buildtools/config.py @@ -524,7 +524,7 @@ def Config(*args, **kw): def msg(text): if not runSilently: - print text + print(text) def opj(*args): @@ -616,11 +616,28 @@ def wxDir(): return WXWIN -def copyIfNewer(src, dest): +def copyFile(src, dest, verbose=False): + """ + Copy file from src to dest, preserving permission bits, etc. If src is a + symlink then dest will be a symlink as well instead of just copying the + linked file's contents to a new file. + """ + if verbose: + msg('copying %s --> %s' % (src, dest)) + if os.path.islink(src): + if os.path.exists(dest): + os.unlink(dest) + linkto = os.readlink(src) + os.symlink(linkto, dest) + else: + shutil.copy2(src, dest) + + +def copyIfNewer(src, dest, verbose=False): if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(src)) if newer(src, dest): - shutil.copy(src, dest) + copyFile(src, dest, verbose) def writeIfChanged(filename, text): @@ -641,3 +658,38 @@ def writeIfChanged(filename, text): f = codecs.open(filename, 'w', 'utf-8') f.write(text.encode('utf-8')) f.close() + + +# TODO: we might be able to get rid of this when the install code is updated... +def macFixDependencyInstallName(destdir, prefix, extension, buildDir): + print("**** macFixDependencyInstallName(%s, %s, %s, %s)" % (destdir, prefix, extension, buildDir)) + pwd = os.getcwd() + os.chdir(destdir+prefix+'/lib') + dylibs = glob.glob('*.dylib') + for lib in dylibs: + #cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ + # (destdir+prefix,lib, prefix,lib, extension) + cmd = 'install_name_tool -change %s/lib/%s %s/lib/%s %s' % \ + (buildDir,lib, prefix,lib, extension) + print(cmd) + os.system(cmd) + os.chdir(pwd) + + +def macSetLoaderNames(filenames): + """ + Scan the list of dynamically loaded files for each file in filenames, + replacing the path for the wxWidgets libraries with "@loader_path" + """ + for filename in filenames: + if not os.path.isfile(filename): + continue + for line in os.popen('otool -L %s' % filename, 'r').readlines(): # -arch all ?? + if line.startswith('\t') and 'libwx_' in line: + line = line.strip() + endPos = line.rfind(' (') + curName = line[:endPos] + newName = '@loader_path/' + os.path.basename(curName) + cmd = 'install_name_tool -change %s %s %s' % (curName, newName, filename) + os.system(cmd) + diff --git a/packaging/README.txt b/packaging/README.txt index b9fab06f..8105a040 100644 --- a/packaging/README.txt +++ b/packaging/README.txt @@ -44,15 +44,18 @@ nothing extra should be needed because the system will automatically look for the DLLs in the same folder that the extension modules are located in. -For Unix-like systems like Linux or OSX the locations that are -searched for the dynamic libraries can be controlled by setting -environment variables, DYLD_LIBRARY_PATH for OSX or LD_LIBRARY_PATH -for the others. Basically you just need to set that variable to the -path of the wx package, for example if you're on a Mac and currently -in the folder where this README is located, then you can do something -like this:: +For Mac OSX there should also not be anything extra needed to help Phoenix +find the wxWidgets dynamic libraries because the install names have been +modified to use @loader_path so they can find the libraries in the same +folder as the extension modules. - export DYLD_LIBRARY_PATH=`pwd`/wx +For Unix-like systems like Linux the locations that are searched for the +dynamic libraries can be controlled by setting the LD_LIBRARY_PATH +environment variable. Basically you just need to set that variable to the +path of the wx package, for example if you're in the folder where this README +is located, then you can do something like this:: + + export LD_LIBRARY_PATH=`pwd`/wx The phoenix_environ.sh shell script included with this build can help you do that, just be sure to use the "source" command so the variables @@ -60,6 +63,4 @@ in the current shell's environment will be modified. It is also possible to embed the path that the dynamic library should be loaded from directly into the extension module. For now at least -this is left as an exercise for the reader. For OSX look at the man -pages for the otool and install_name_tool commands. For the other -unix-like platforms look at chrpath. +this is left as an exercise for the reader. Look for the chrpath tool. \ No newline at end of file diff --git a/packaging/phoenix_environ.sh b/packaging/phoenix_environ.sh index af644c88..35793418 100644 --- a/packaging/phoenix_environ.sh +++ b/packaging/phoenix_environ.sh @@ -1,4 +1,3 @@ DIR=$(cd $(dirname "$BASH_SOURCE") && pwd) export PYTHONPATH=$DIR -export DYLD_LIBRARY_PATH=$DIR/wx export LD_LIBRARY_PATH=$DIR/wx