]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wxversion/wxversion.py
   1 #---------------------------------------------------------------------- 
   3 # Purpose:     Allows a wxPython program to search for alternate  
   4 #              installations of the wxPython packages and modify sys.path 
   5 #              so they will be found when "import wx" is done. 
   9 # Created:     24-Sept-2004 
  11 # Copyright:   (c) 2004 by Total Control Software 
  12 # Licence:     wxWindows license 
  13 #---------------------------------------------------------------------- 
  16 If you have more than one version of wxPython installed this module 
  17 allows your application to choose which version of wxPython will be 
  18 imported when it does 'import wx'.  The main function of this module 
  19 is `select` and you use it like this:: 
  22     wxversion.select('2.4') 
  25 Or additional build options can also be selected, although they will 
  26 not be required if they are not installed, like this:: 
  29     wxversion.select('2.5.3-unicode') 
  32 Or you can require an exact match on the build options like this:: 
  35     wxversion.select('2.5.3-unicode', optionsRequired=True) 
  38 Finally you can also specify a collection of versions that are allowed 
  39 by your application, like this:: 
  42     wxversion.select(['2.5.4', '2.5.5', '2.6']) 
  46 Of course the default wxPython version can also be controlled by 
  47 setting PYTHONPATH or by editing the wx.pth path configuration file, 
  48 but using wxversion will allow an application to manage the version 
  49 selection itself rather than depend on the user to setup the 
  50 environment correctly. 
  52 It works by searching the sys.path for directories matching wx-* and 
  53 then comparing them to what was passed to the select function.  If a 
  54 match is found then that path is inserted into sys.path. 
  56 NOTE: If you are making a 'bundle' of your application with a tool 
  57 like py2exe then you should *not* use the wxversion module since it 
  58 looks at the filesystem for the directories on sys.path, it will fail 
  59 in a bundled environment.  Instead you should simply ensure that the 
  60 version of wxPython that you want is found by default on the sys.path 
  61 when making the bundled version by setting PYTHONPATH.  Then that 
  62 version will be included in your bundle and your app will work as 
  63 expected.  Py2exe and the others usually have a way to tell at runtime 
  64 if they are running from a bundle or running raw, so you can check 
  65 that and only use wxversion if needed.  For example, for py2exe:: 
  67     if not hasattr(sys, 'frozen'): 
  69         wxversion.select('2.5') 
  72 More documentation on wxversion and multi-version installs can be 
  73 found at: http://wiki.wxpython.org/index.cgi/MultiVersionInstalls 
  77 import re
, sys
, os
, glob
, fnmatch
 
  81 class VersionError(Exception): 
  84 #---------------------------------------------------------------------- 
  86 def select(versions
, optionsRequired
=False): 
  88     Search for a wxPython installation that matches version.  If one 
  89     is found then sys.path is modified so that version will be 
  90     imported with a 'import wx', otherwise a VersionError exception is 
  91     raised.  This funciton should only be caled once at the begining 
  92     of the application before wxPython is imported. 
  94         :param versions: Specifies the version to look for, it can 
  95             either be a string or a list of strings.  Each string is 
  96             compared to the installed wxPythons and the best match is 
  97             inserted into the sys.path, allowing an 'import wx' to 
 100             The version string is composed of the dotted version 
 101             number (at least 2 of the 4 components) optionally 
 102             followed by hyphen ('-') separated options (wx port, 
 103             unicode/ansi, flavour, etc.)  A match is determined by how 
 104             much of the installed version matches what is given in the 
 105             version parameter.  If the version number components don't 
 106             match then the score is zero, otherwise the score is 
 107             increased for every specified optional component that is 
 108             specified and that matches. 
 110             Please note, however, that it is possible for a match to 
 111             be selected that doesn't exactly match the versions 
 112             requested.  The only component that is required to be 
 113             matched is the version number.  If you need to require a 
 114             match on the other components as well, then please use the 
 115             optional ``optionsRequired`` parameter described next. 
 117         :param optionsRequired: Allows you to specify that the other 
 118              components of the version string (such as the port name 
 119              or character type) are also required to be present for an 
 120              installed version to be considered a match.  Using this 
 121              parameter allows you to change the selection from a soft, 
 122              as close as possible match to a hard, exact match. 
 125     if type(versions
) == str: 
 126         versions 
= [versions
] 
 129     if _selected 
is not None: 
 130         # A version was previously selected, ensure that it matches 
 133             if _selected
.Score(_wxPackageInfo(ver
), optionsRequired
) > 0: 
 135         # otherwise, raise an exception 
 136         raise VersionError("A previously selected wx version does not match the new request.") 
 138     # If we get here then this is the first time wxversion is used,  
 139     # ensure that wxPython hasn't been imported yet. 
 140     if sys
.modules
.has_key('wx') or sys
.modules
.has_key('wxPython'): 
 141         raise VersionError("wxversion.select() must be called before wxPython is imported") 
 143     # Look for a matching version and manipulate the sys.path as 
 144     # needed to allow it to be imported. 
 145     installed 
= _find_installed(True) 
 146     bestMatch 
= _get_best_match(installed
, versions
, optionsRequired
) 
 148     if bestMatch 
is None: 
 149         raise VersionError("Requested version of wxPython not found") 
 151     sys
.path
.insert(0, bestMatch
.pathname
) 
 153     path64 
= re
.sub('/lib/','/lib64/',bestMatch
.pathname
) 
 154     if os
.path
.isdir(path64
): 
 155         sys
.path
.insert(0, path64
) 
 156     _selected 
= bestMatch
 
 158 #---------------------------------------------------------------------- 
 160 UPDATE_URL 
= "http://wxPython.org/" 
 161 #UPDATE_URL = "http://sourceforge.net/project/showfiles.php?group_id=10718" 
 165 def ensureMinimal(minVersion
, optionsRequired
=False): 
 167     Checks to see if the default version of wxPython is greater-than 
 168     or equal to `minVersion`.  If not then it will try to find an 
 169     installed version that is >= minVersion.  If none are available 
 170     then a message is displayed that will inform the user and will 
 171     offer to open their web browser to the wxPython downloads page, 
 172     and will then exit the application. 
 174     assert type(minVersion
) == str 
 176     # ensure that wxPython hasn't been imported yet. 
 177     if sys
.modules
.has_key('wx') or sys
.modules
.has_key('wxPython'): 
 178         raise VersionError("wxversion.ensureMinimal() must be called before wxPython is imported") 
 181     minv 
= _wxPackageInfo(minVersion
) 
 183     # check the default version first 
 184     defaultPath 
= _find_default() 
 186         defv 
= _wxPackageInfo(defaultPath
, True) 
 187         if defv 
>= minv 
and minv
.CheckOptions(defv
, optionsRequired
): 
 190     # if still no match then check look at all installed versions 
 191     if bestMatch 
is None: 
 192         installed 
= _find_installed() 
 193         # The list is in reverse sorted order, so find the first 
 194         # one that is big enough and optionally matches the 
 196         for inst 
in installed
: 
 197             if inst 
>= minv 
and minv
.CheckOptions(inst
, optionsRequired
): 
 201     # if still no match then prompt the user 
 202     if bestMatch 
is None: 
 203         if _EM_DEBUG
: # We'll do it this way just for the test code below 
 204             raise VersionError("Requested version of wxPython not found") 
 206         import wx
, webbrowser
 
 207         versions 
= "\n".join(["      "+ver 
for ver 
in getInstalled()]) 
 208         app 
= wx
.PySimpleApp() 
 209         result 
= wx
.MessageBox("This application requires a version of wxPython " 
 210                                "greater than or equal to %s, but a matching version " 
 212                                "You currently have these version(s) installed:\n%s\n\n" 
 213                                "Would you like to download a new version of wxPython?\n" 
 214                                % (minVersion
, versions
), 
 215                       "wxPython Upgrade Needed", style
=wx
.YES_NO
) 
 217             webbrowser
.open(UPDATE_URL
) 
 221     sys
.path
.insert(0, bestMatch
.pathname
) 
 223     path64 
= re
.sub('/lib/','/lib64/',bestMatch
.pathname
) 
 224     if os
.path
.isdir(path64
): 
 225         sys
.path
.insert(0, path64
) 
 227     _selected 
= bestMatch
 
 230 #---------------------------------------------------------------------- 
 232 def checkInstalled(versions
, optionsRequired
=False): 
 234     Check if there is a version of wxPython installed that matches one 
 235     of the versions given.  Returns True if so, False if not.  This 
 236     can be used to determine if calling `select` will succeed or not. 
 238         :param versions: Same as in `select`, either a string or a list 
 239             of strings specifying the version(s) to check for. 
 241         :param optionsRequired: Same as in `select`. 
 244     if type(versions
) == str: 
 245         versions 
= [versions
] 
 246     installed 
= _find_installed() 
 247     bestMatch 
= _get_best_match(installed
, versions
, optionsRequired
) 
 248     return bestMatch 
is not None 
 250 #---------------------------------------------------------------------- 
 254     Returns a list of strings representing the installed wxPython 
 255     versions that are found on the system. 
 257     installed 
= _find_installed() 
 258     return [os
.path
.basename(p
.pathname
)[3:] for p 
in installed
] 
 262 #---------------------------------------------------------------------- 
 265 def _get_best_match(installed
, versions
, optionsRequired
): 
 268     for pkg 
in installed
: 
 270             score 
= pkg
.Score(_wxPackageInfo(ver
), optionsRequired
) 
 271             if score 
> bestScore
: 
 277 _pattern 
= "wx-[0-9].*" 
 278 def _find_installed(removeExisting
=False): 
 283         # empty means to look in the current dir 
 287         # skip it if it's not a package dir 
 288         if not os
.path
.isdir(pth
): 
 291         base 
= os
.path
.basename(pth
) 
 293         # if it's a wx path that's already in the sys.path then mark 
 294         # it for removal and then skip it 
 295         if fnmatch
.fnmatchcase(base
, _pattern
): 
 299         # now look in the dir for matching subdirs 
 300         for name 
in glob
.glob(os
.path
.join(pth
, _pattern
)): 
 301             # make sure it's a directory 
 302             if not os
.path
.isdir(name
): 
 304             # and has a wx subdir 
 305             if not os
.path
.exists(os
.path
.join(name
, 'wx')): 
 307             installed
.append(_wxPackageInfo(name
, True)) 
 311             del sys
.path
[sys
.path
.index(rem
)] 
 318 # Scan the sys.path looking for either a directory matching _pattern, 
 322         # empty means to look in the current dir 
 326         # skip it if it's not a package dir 
 327         if not os
.path
.isdir(pth
): 
 330         # does it match the pattern? 
 331         base 
= os
.path
.basename(pth
) 
 332         if fnmatch
.fnmatchcase(base
, _pattern
): 
 338         if not os
.path
.isdir(pth
): 
 340         if os
.path
.exists(os
.path
.join(pth
, 'wx.pth')): 
 341             base 
= open(os
.path
.join(pth
, 'wx.pth')).read() 
 342             return os
.path
.join(pth
, base
) 
 347 class _wxPackageInfo(object): 
 348     def __init__(self
, pathname
, stripFirst
=False): 
 349         self
.pathname 
= pathname
 
 350         base 
= os
.path
.basename(pathname
) 
 351         segments 
= base
.split('-') 
 353             segments 
= segments
[1:] 
 354         self
.version 
= tuple([int(x
) for x 
in segments
[0].split('.')]) 
 355         self
.options 
= segments
[1:] 
 358     def Score(self
, other
, optionsRequired
): 
 361         # whatever number of version components given in other must 
 363         minlen 
= min(len(self
.version
), len(other
.version
)) 
 364         if self
.version
[:minlen
] != other
.version
[:minlen
]: 
 368         # check for matching options, if optionsRequired then the 
 369         # options are not optional ;-) 
 370         for opt 
in other
.options
: 
 371             if opt 
in self
.options
: 
 373             elif optionsRequired
: 
 379     def CheckOptions(self
, other
, optionsRequired
): 
 380         # if options are not required then this always succeeds 
 381         if not optionsRequired
: 
 383         # otherwise, if we have any option not present in other, then 
 385         for opt 
in self
.options
: 
 386             if opt 
not in other
.options
: 
 392     def __lt__(self
, other
): 
 393         return self
.version 
< other
.version 
or \
 
 394                (self
.version 
== other
.version 
and self
.options 
< other
.options
) 
 395     def __le__(self
, other
): 
 396         return self
.version 
<= other
.version 
or \
 
 397                (self
.version 
== other
.version 
and self
.options 
<= other
.options
) 
 399     def __gt__(self
, other
): 
 400         return self
.version 
> other
.version 
or \
 
 401                (self
.version 
== other
.version 
and self
.options 
> other
.options
) 
 402     def __ge__(self
, other
): 
 403         return self
.version 
>= other
.version 
or \
 
 404                (self
.version 
== other
.version 
and self
.options 
>= other
.options
) 
 406     def __eq__(self
, other
): 
 407         return self
.version 
== other
.version 
and self
.options 
== other
.options
 
 411 #---------------------------------------------------------------------- 
 413 if __name__ 
== '__main__': 
 416     #ensureMinimal('2.5') 
 417     #pprint.pprint(sys.path) 
 421     def test(version
, optionsRequired
=False): 
 423         savepath 
= sys
.path
[:] 
 426         select(version
, optionsRequired
) 
 427         print "Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0]) 
 430         sys
.path 
= savepath
[:] 
 435     def testEM(version
, optionsRequired
=False): 
 437         savepath 
= sys
.path
[:] 
 440         ensureMinimal(version
, optionsRequired
) 
 441         print "EM: Asked for %s, (%s):\t got: %s" % (version
, optionsRequired
, sys
.path
[0]) 
 444         sys
.path 
= savepath
[:] 
 449     # make some test dirs 
 450     names 
= ['wx-2.4-gtk-ansi', 
 451              'wx-2.5.2-gtk2-unicode', 
 453              'wx-2.6-gtk2-unicode', 
 456              'wx-2.7.1-gtk2-ansi', 
 459         d 
= os
.path
.join('/tmp', name
) 
 461         os
.mkdir(os
.path
.join(d
, 'wx')) 
 463     # setup sys.path to see those dirs 
 464     sys
.path
.append('/tmp') 
 468     pprint
.pprint( getInstalled()) 
 469     print checkInstalled("2.4") 
 470     print checkInstalled("2.5-unicode") 
 471     print checkInstalled("2.99-bogus") 
 472     print "Current sys.path:" 
 473     pprint
.pprint(sys
.path
) 
 484     test(["2.6-unicode", "2.7-unicode"]) 
 486     test(["2.6-unicode", "2.7-unicode"], optionsRequired
=True) 
 490     # There isn't a unicode match for this one, but it will give the best 
 491     # available 2.4.  Should it give an error instead?  I don't think so... 
 494     # Try asking for multiple versions 
 495     test(["2.5.2", "2.5.3", "2.6"]) 
 498         # expecting an error on this one 
 500     except VersionError
, e
: 
 501         print "Asked for 2.9:\t got Exception:", e 
 
 503     # check for exception when incompatible versions are requested 
 507     except VersionError
, e
: 
 508         print "Asked for incompatible versions, got Exception:", e 
 
 512     testEM("2.6-unicode") 
 513     testEM("2.6-unicode", True) 
 516     except VersionError
, e
: 
 517         print "EM: Asked for 2.9:\t got Exception:", e 
 
 521         d 
= os
.path
.join('/tmp', name
) 
 522         os
.rmdir(os
.path
.join(d
, 'wx'))