From: Robin Dunn Date: Wed, 29 Sep 2004 00:11:17 +0000 (+0000) Subject: Beginings of supporting multiple versions installed side-by-side X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/d48c1c640e96d350cf914bf91bd1264026048255 Beginings of supporting multiple versions installed side-by-side git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@29517 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/wxPython/config.py b/wxPython/config.py index 40f4c04660..7a8aca3525 100644 --- a/wxPython/config.py +++ b/wxPython/config.py @@ -27,6 +27,7 @@ from distutils.dir_util import mkpath from distutils.dep_util import newer from distutils.spawn import spawn +import distutils.command.install import distutils.command.install_data import distutils.command.install_headers import distutils.command.clean @@ -121,6 +122,21 @@ UNDEF_NDEBUG = 1 # Python 2.2 on Unix/Linux by default defines NDEBUG, NO_SCRIPTS = 0 # Don't install the tool scripts NO_HEADERS = 0 # Don't install the wxPython *.h and *.i files +INSTALL_MULTIVERSION = 1 # Install the packages such that multiple versions + # can co-exist. When turned on the wx and wxPython + # pacakges will be installed in a versioned subdir + # of site-packages, and a *.pth file will be + # created that adds that dir to the sys.path. In + # addition, a wxselect.py module will be installed + # to site-pacakges that will allow applications to + # choose a specific version if more than one are + # installed. + +FLAVOUR = "" # Optional flavour string to be appended to VERSION + # in MULTIVERSION installs + +INSTALL_WXRC = 0 # Should the Python version of wxrc be installed? + WX_CONFIG = None # Usually you shouldn't need to touch this, but you can set # it to pass an alternate version of wx-config or alternate # flags, eg. as required by the .deb in-tree build. By @@ -218,7 +234,7 @@ for flag in ['BUILD_GLCANVAS', 'BUILD_OGL', 'BUILD_STC', 'BUILD_GIZMOS', 'BUILD_DLLWIDGET', 'BUILD_IEWIN', 'BUILD_ACTIVEX', 'CORE_ONLY', 'PREP_ONLY', 'USE_SWIG', 'UNICODE', 'UNDEF_NDEBUG', 'NO_SCRIPTS', 'NO_HEADERS', 'BUILD_RENAMERS', - 'FULL_DOCS', + 'FULL_DOCS', 'INSTALL_MULTIVERSION', 'INSTALL_WXRC', 'FINAL', 'HYBRID', ]: for x in range(len(sys.argv)): if sys.argv[x].find(flag) == 0: @@ -229,7 +245,8 @@ for flag in ['BUILD_GLCANVAS', 'BUILD_OGL', 'BUILD_STC', # String options for option in ['WX_CONFIG', 'WXDLLVER', 'BUILD_BASE', 'WXPORT', 'SWIG', - 'CONTRIBS_INC', 'WXPY_SRC']: + 'CONTRIBS_INC', 'WXPY_SRC', 'FLAVOUR', + ]: for x in range(len(sys.argv)): if sys.argv[x].find(option) == 0: pos = sys.argv[x].find('=') + 1 @@ -397,6 +414,15 @@ class wx_extra_clean(distutils.command.clean.clean): +class wx_install(distutils.command.install.install): + """ + Turns off install_path_file + """ + def initialize_options(self): + distutils.command.install.install.initialize_options(self) + self.install_path_file = 0 + + class wx_install_headers(distutils.command.install_headers.install_headers): """ Install the header files to the WXPREFIX, with an extra dir per @@ -508,7 +534,7 @@ def adjustCFLAGS(cflags, defines, includes): def adjustLFLAGS(lfags, libdirs, libs): - '''Extrace the -L and -l flags and put them in libdirs and libs as needed''' + '''Extract the -L and -l flags and put them in libdirs and libs as needed''' newLFLAGS = [] for flag in lflags: if flag[:2] == '-L': @@ -520,6 +546,31 @@ def adjustLFLAGS(lfags, libdirs, libs): return newLFLAGS + + +def getExtraPath(shortVer=True, addOpts=False): + """Get the dirname that wxPython will be installed under.""" + + if shortVer: + # short version, just Major.Minor + ep = "wx-%d.%d" % (VER_MAJOR, VER_MINOR) + # plus release if minor is odd + if VER_MINOR % 2 == 1: + ep += ".%d" % VER_RELEASE + else: + # long version, full version + ep = "wx-%d.%d.%d.%d" % (VER_MAJOR, VER_MINOR, VER_RELEASE, VER_SUBREL) + + if addOpts: + ep += "-%s-%s" % (WXPORT, (UNICODE and 'unicode' or 'ansi')) + + if FLAVOUR: + ep += "-" + FLAVOUR + + return ep + + + #---------------------------------------------------------------------- # sanity checks diff --git a/wxPython/setup.py b/wxPython/setup.py index 6e43ec09e3..f8aabe26d7 100755 --- a/wxPython/setup.py +++ b/wxPython/setup.py @@ -647,16 +647,19 @@ if NO_SCRIPTS: else: SCRIPTS = [opj('scripts/helpviewer'), opj('scripts/img2png'), - opj('scripts/img2xpm'), opj('scripts/img2py'), - opj('scripts/xrced'), - opj('scripts/pyshell'), + opj('scripts/img2xpm'), + opj('scripts/pyalacarte'), + opj('scripts/pyalamode'), opj('scripts/pycrust'), + opj('scripts/pyshell'), opj('scripts/pywrap'), opj('scripts/pywrap'), - opj('scripts/pyalacarte'), - opj('scripts/pyalamode'), + opj('scripts/xrced'), ] + if INSTALL_WXRC: + SCRIPTS += [opj('scripts/wxrc')] + DATA_FILES += find_data_files('wx/tools/XRCed', '*.txt', '*.xrc') @@ -676,12 +679,23 @@ else: zip(i_files, ["/wxPython/i_files"]*len(i_files)) + +if INSTALL_MULTIVERSION: + EXTRA_PATH = getExtraPath() + open("src/wx.pth", "w").write(EXTRA_PATH) + CLEANUP.append("src/wx.pth") +else: + EXTRA_PATH = None + + + #---------------------------------------------------------------------- # Do the Setup/Build/Install/Whatever #---------------------------------------------------------------------- if __name__ == "__main__": if not PREP_ONLY: + setup(name = 'wxPython', version = VERSION, description = DESCRIPTION, @@ -716,6 +730,8 @@ if __name__ == "__main__": 'wx.tools.XRCed', ], + extra_path = EXTRA_PATH, + ext_package = PKGDIR, ext_modules = wxpExtensions, @@ -726,12 +742,40 @@ if __name__ == "__main__": data_files = DATA_FILES, headers = HEADERS, - cmdclass = { 'install_data': wx_smart_install_data, + # Override some of the default distutils command classes with my own + cmdclass = { 'install' : wx_install, + 'install_data': wx_smart_install_data, 'install_headers': wx_install_headers, 'clean': wx_extra_clean, }, ) + if INSTALL_MULTIVERSION: + setup(name = 'wxPython-common', + version = VERSION, + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + author = AUTHOR, + author_email = AUTHOR_EMAIL, + url = URL, + download_url = DOWNLOAD_URL, + license = LICENSE, + platforms = PLATFORMS, + classifiers = filter(None, CLASSIFIERS.split("\n")), + keywords = KEYWORDS, + + package_dir = { '': 'wxversion' }, + py_modules = ['wxversion'], + + data_files = [('', ['src/wx.pth'])], + + options = { 'build' : { 'build_base' : BUILD_BASE }, + }, + + cmdclass = { 'install_data': wx_smart_install_data, + }, + ) + #---------------------------------------------------------------------- #---------------------------------------------------------------------- diff --git a/wxPython/wxversion/wxversion.py b/wxPython/wxversion/wxversion.py new file mode 100644 index 0000000000..07c1ec8809 --- /dev/null +++ b/wxPython/wxversion/wxversion.py @@ -0,0 +1,210 @@ +#---------------------------------------------------------------------- +# Name: wxversion +# Purpose: Allows a wxPython program to search for alternate +# installations of the wxPython packages and modify sys.path +# so they will be found when "import wx" is done. +# +# Author: Robin Dunn +# +# Created: 24-Sept-2004 +# RCS-ID: $Id$ +# Copyright: (c) 2004 by Total Control Software +# Licence: wxWindows license +#---------------------------------------------------------------------- + +""" +If you have more than one version of wxPython installed this module +allows your application to choose which version of wxPython will be +imported when it does 'import wx'. You use it like this: + + import wxversion + wxversion.require('2.4') + import wx + +Of course the default wxPython version can also be controlled by +setting PYTHONPATH or by editing the wx.pth path configuration file, +but using wxversion will allow an application to manage the version +selection itself rather than depend on the user to setup the +environment correctly. + +It works by searching the sys.path for directories matching wx-* and +then comparing them to what was passed to the require function. If a +match is found then that path is inserted into sys.path. +""" + +import sys, os, glob, fnmatch + + + + +def require(versions): + """ + Search for a wxPython installation that matches version. + + :param version: Specifies the version to look for, it can either be + a sting or a list of strings. Each string is + compared to the installed wxPythons and the best + match is added to the sys.path, allowing an 'import + wx' to find that version. + + The version string is composed of the dotted + version number (at least 2 of the 4 components) + optionally followed by hyphen ('-') separated + options (wx port, unicode/ansi, flavour, etc.) A + match is determined by how much of the installed + version matches what is given in the version + parameter. If the version number components don't + match then the score is zero, otherwise the score + is increased for every specified optional component + that is specified and that matches. + """ + assert not sys.modules.has_key('wx') and not sys.modules.has_key('wxPython'), \ + "wxversion.require() must be called before wxPython is imported" + + bestMatch = None + bestScore = 0 + if type(versions) == str: + versions = [versions] + + packages = _find_installed() + for pkg in packages: + for ver in versions: + score = pkg.Score(_wxPackageInfo(ver)) + if score > bestScore: + bestMatch = pkg + bestScore = score + + assert bestMatch is not None, \ + "Required version of wxPython not found" + + sys.path.insert(0, bestMatch.pathname) + + + + +_pattern = "wx-[0-9].*" +def _find_installed(): + installed = [] + for pth in sys.path: + + # empty means to look in the current dir + if not pth: + pth = '.' + + # skip it if it's not a package dir + if not os.path.isdir(pth): + continue + + base = os.path.basename(pth) + + # if it's a wx path that's already in the sys.path then skip it + if fnmatch.fnmatchcase(base, _pattern): + continue + + # now look in the dir for matching subdirs + for name in glob.glob(os.path.join(pth, _pattern)): + # make sure it's a directory + if not os.path.isdir(name): + continue + # and has a wx subdir + if not os.path.exists(os.path.join(name, 'wx')): + continue + installed.append(_wxPackageInfo(name, True)) + + installed.sort() + installed.reverse() + return installed + + +class _wxPackageInfo(object): + def __init__(self, pathname, stripFirst=False): + self.pathname = pathname + base = os.path.basename(pathname) + segments = base.split('-') + if stripFirst: + segments = segments[1:] + self.version = tuple([int(x) for x in segments[0].split('.')]) + self.options = segments[1:] + + + def Score(self, other): + score = 0 + # whatever version components given in other must match exactly + if len(self.version) > len(other.version): + v = self.version[:len(other.version)] + else: + v = self.version + if v != other.version: + return 0 + score += 1 + for opt in other.options: + if opt in self.options: + score += 1 + return score + + + # TODO: factor self.options into the sort order? + def __lt__(self, other): + return self.version < other.version + def __gt__(self, other): + return self.version > other.version + def __eq__(self, other): + return self.version == other.version + + + + + +if __name__ == '__main__': + def test(version): + savepath = sys.path[:] + require(version) + print "Asked for %s:\t got: %s" % (version, sys.path[0]) + sys.path = savepath[:] + + + # make some test dirs + names = ['wx-2.4', + 'wx-2.5.2', + 'wx-2.5.2.9-gtk2-unicode', + 'wx-2.5.2.9-gtk-ansi', + 'wx-2.5.1', + 'wx-2.5.2.8-gtk2-unicode', + 'wx-2.5.3'] + for name in names: + d = os.path.join('/tmp', name) + os.mkdir(d) + os.mkdir(os.path.join(d, 'wx')) + + # setup sys.path to see those dirs + sys.path.append('/tmp') + + + # now run some tests + test("2.4") + test("2.5") + test("2.5-gtk2") + test("2.5.2") + test("2.5-ansi") + test("2.5-unicode") + + # There isn't a unicode match for this one, but it will give the best + # available 2.4. Should it give an error instead? I don't think so... + test("2.4-unicode") + + try: + # expecting an error on this one + test("2.6") + except AssertionError: + print "Asked for 2.6:\t got: Assertion" + + # Try asking for multiple versions + test(["2.6", "2.5.3", "2.5.2-gtk2"]) + + # cleanup + for name in names: + d = os.path.join('/tmp', name) + os.rmdir(os.path.join(d, 'wx')) + os.rmdir(d) + +