]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wxversion/wxversion.py
EP_ADD_OPTS defaults to true
[wxWidgets.git] / wxPython / wxversion / wxversion.py
CommitLineData
d48c1c64
RD
1#----------------------------------------------------------------------
2# Name: wxversion
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.
6#
7# Author: Robin Dunn
8#
9# Created: 24-Sept-2004
10# RCS-ID: $Id$
11# Copyright: (c) 2004 by Total Control Software
12# Licence: wxWindows license
13#----------------------------------------------------------------------
14
15"""
16If you have more than one version of wxPython installed this module
17allows your application to choose which version of wxPython will be
5029118c 18imported when it does 'import wx'. You use it like this::
d48c1c64
RD
19
20 import wxversion
5029118c
RD
21 wxversion.select('2.4')
22 import wx
23
24Or additional build options can also be selected, like this::
25
26 import wxversion
27 wxversion.select('2.5.3-unicode')
d48c1c64
RD
28 import wx
29
30Of course the default wxPython version can also be controlled by
31setting PYTHONPATH or by editing the wx.pth path configuration file,
32but using wxversion will allow an application to manage the version
33selection itself rather than depend on the user to setup the
34environment correctly.
35
36It works by searching the sys.path for directories matching wx-* and
37then comparing them to what was passed to the require function. If a
38match is found then that path is inserted into sys.path.
5029118c
RD
39
40NOTE: If you are making a 'bundle' of your application with a tool
41like py2exe then you should *not* use the wxversion module since it
42looks at filesystem for the directories on sys.path, it will fail in a
43bundled environment. Instead you should simply ensure that the
44version of wxPython that you want is found by default on the sys.path
45when making the bundled version by setting PYTHONPATH. Then that
46version will be included in your bundle and your app will work as
47expected. Py2exe and the others usually have a way to tell at runtime
48if they are running from a bundle or running raw, so you can check
49that and only use wxversion if needed. For example, for py2exe::
50
51 if not hasattr(sys, 'frozen'):
52 import wxversion
53 wxversion.select('2.5')
54 import wx
55
d48c1c64
RD
56"""
57
58import sys, os, glob, fnmatch
59
60
4f60dce5 61_selected = None
5029118c 62class VersionError(Exception):
4f60dce5 63 pass
d48c1c64 64
5029118c 65#----------------------------------------------------------------------
d48c1c64 66
5029118c 67def select(versions):
d48c1c64 68 """
5029118c
RD
69 Search for a wxPython installation that matches version. If one
70 is found then sys.path is modified so that version will be
71 imported with a 'import wx', otherwise a VersionError exception is
72 raised. This funciton should only be caled once at the begining
73 of the application before wxPython is imported.
d48c1c64 74
93ba536a
RD
75 :param version: Specifies the version to look for, it can
76 either be a string or a list of strings. Each
77 string is compared to the installed wxPythons
78 and the best match is inserted into the
79 sys.path, allowing an 'import wx' to find that
80 version.
d48c1c64
RD
81
82 The version string is composed of the dotted
83 version number (at least 2 of the 4 components)
84 optionally followed by hyphen ('-') separated
85 options (wx port, unicode/ansi, flavour, etc.) A
86 match is determined by how much of the installed
87 version matches what is given in the version
88 parameter. If the version number components don't
89 match then the score is zero, otherwise the score
90 is increased for every specified optional component
91 that is specified and that matches.
92 """
d48c1c64
RD
93 if type(versions) == str:
94 versions = [versions]
4f60dce5
RD
95
96 global _selected
97 if _selected is not None:
98 # A version was previously selected, ensure that it matches
99 # this new request
100 for ver in versions:
101 if _selected.Score(_wxPackageInfo(ver)) > 0:
102 return
103 # otherwise, raise an exception
5029118c 104 raise VersionError("A previously selected wx version does not match the new request.")
4f60dce5
RD
105
106 # If we get here then this is the first time wxversion is used.
107 # Ensure that wxPython hasn't been imported yet.
108 if sys.modules.has_key('wx') or sys.modules.has_key('wxPython'):
5029118c 109 raise VersionError("wxversion.require() must be called before wxPython is imported")
4f60dce5
RD
110
111 # Look for a matching version and manipulate the sys.path as
112 # needed to allow it to be imported.
5029118c
RD
113 packages = _find_installed(True)
114 bestMatch = _get_best_match(packages, versions)
115
116 if bestMatch is None:
117 raise VersionError("Requested version of wxPython not found")
118
119 sys.path.insert(0, bestMatch.pathname)
120 _selected = bestMatch
121
122#----------------------------------------------------------------------
123
124def checkInstalled(versions):
125 """
126 Check if there is a version of wxPython installed that matches one
127 of the versions given. Returns True if so, False if not. This
128 can be used to determine if calling `select` will succeed or not.
129
130 :param version: Same as in `select`, either a string or a list
131 of strings specifying the version(s) to check
132 for.
133 """
134
135 if type(versions) == str:
136 versions = [versions]
d48c1c64 137 packages = _find_installed()
5029118c
RD
138 bestMatch = _get_best_match(packages, versions)
139 return bestMatch is not None
140
141#----------------------------------------------------------------------
142
143def getInstalled():
144 """
145 Returns a list of strings representing the installed wxPython
146 versions that are found on the system.
147 """
148 packages = _find_installed()
149 return [os.path.basename(p.pathname)[3:] for p in packages]
150
151
152
153#----------------------------------------------------------------------
154# private helpers...
155
156def _get_best_match(packages, versions):
157 bestMatch = None
158 bestScore = 0
d48c1c64
RD
159 for pkg in packages:
160 for ver in versions:
161 score = pkg.Score(_wxPackageInfo(ver))
162 if score > bestScore:
163 bestMatch = pkg
164 bestScore = score
5029118c 165 return bestMatch
d48c1c64
RD
166
167
168_pattern = "wx-[0-9].*"
5029118c 169def _find_installed(removeExisting=False):
d48c1c64 170 installed = []
17f3e530 171 toRemove = []
d48c1c64
RD
172 for pth in sys.path:
173
174 # empty means to look in the current dir
175 if not pth:
176 pth = '.'
177
178 # skip it if it's not a package dir
179 if not os.path.isdir(pth):
180 continue
181
182 base = os.path.basename(pth)
183
17f3e530
RD
184 # if it's a wx path that's already in the sys.path then mark
185 # it for removal and then skip it
d48c1c64 186 if fnmatch.fnmatchcase(base, _pattern):
17f3e530 187 toRemove.append(pth)
d48c1c64
RD
188 continue
189
190 # now look in the dir for matching subdirs
191 for name in glob.glob(os.path.join(pth, _pattern)):
192 # make sure it's a directory
193 if not os.path.isdir(name):
194 continue
195 # and has a wx subdir
196 if not os.path.exists(os.path.join(name, 'wx')):
197 continue
198 installed.append(_wxPackageInfo(name, True))
199
5029118c
RD
200 if removeExisting:
201 for rem in toRemove:
202 del sys.path[sys.path.index(rem)]
17f3e530 203
d48c1c64
RD
204 installed.sort()
205 installed.reverse()
206 return installed
207
208
209class _wxPackageInfo(object):
210 def __init__(self, pathname, stripFirst=False):
211 self.pathname = pathname
212 base = os.path.basename(pathname)
213 segments = base.split('-')
214 if stripFirst:
215 segments = segments[1:]
216 self.version = tuple([int(x) for x in segments[0].split('.')])
217 self.options = segments[1:]
218
219
220 def Score(self, other):
221 score = 0
5029118c 222
d48c1c64 223 # whatever version components given in other must match exactly
5029118c
RD
224 minlen = min(len(self.version), len(other.version))
225 if self.version[:minlen] != other.version[:minlen]:
226 return 0
227 score += 1 # todo: should it be +=minlen to reward longer matches?
228
d48c1c64
RD
229 for opt in other.options:
230 if opt in self.options:
231 score += 1
232 return score
233
234
235 # TODO: factor self.options into the sort order?
236 def __lt__(self, other):
237 return self.version < other.version
238 def __gt__(self, other):
239 return self.version > other.version
240 def __eq__(self, other):
241 return self.version == other.version
242
243
244
5029118c 245#----------------------------------------------------------------------
d48c1c64
RD
246
247if __name__ == '__main__':
17f3e530 248 import pprint
d48c1c64 249 def test(version):
4f60dce5 250 # setup
d48c1c64 251 savepath = sys.path[:]
4f60dce5
RD
252
253 #test
5029118c 254 select(version)
d48c1c64 255 print "Asked for %s:\t got: %s" % (version, sys.path[0])
4f60dce5
RD
256 #pprint.pprint(sys.path)
257 #print
258
259 # reset
d48c1c64 260 sys.path = savepath[:]
4f60dce5
RD
261 global _selected
262 _selected = None
d48c1c64
RD
263
264
265 # make some test dirs
266 names = ['wx-2.4',
267 'wx-2.5.2',
268 'wx-2.5.2.9-gtk2-unicode',
269 'wx-2.5.2.9-gtk-ansi',
270 'wx-2.5.1',
271 'wx-2.5.2.8-gtk2-unicode',
272 'wx-2.5.3']
273 for name in names:
274 d = os.path.join('/tmp', name)
275 os.mkdir(d)
276 os.mkdir(os.path.join(d, 'wx'))
277
278 # setup sys.path to see those dirs
279 sys.path.append('/tmp')
280
281
282 # now run some tests
5029118c
RD
283 pprint.pprint( getInstalled())
284 print checkInstalled("2.4")
285 print checkInstalled("2.5-unicode")
286 print checkInstalled("2.99-bogus")
d48c1c64
RD
287 test("2.4")
288 test("2.5")
289 test("2.5-gtk2")
290 test("2.5.2")
291 test("2.5-ansi")
292 test("2.5-unicode")
293
294 # There isn't a unicode match for this one, but it will give the best
295 # available 2.4. Should it give an error instead? I don't think so...
296 test("2.4-unicode")
297
4f60dce5
RD
298 # Try asking for multiple versions
299 test(["2.6", "2.5.3", "2.5.2-gtk2"])
300
d48c1c64
RD
301 try:
302 # expecting an error on this one
303 test("2.6")
5029118c 304 except VersionError, e:
4f60dce5 305 print "Asked for 2.6:\t got Exception:", e
d48c1c64 306
4f60dce5
RD
307 # check for exception when incompatible versions are requested
308 try:
5029118c
RD
309 select("2.4")
310 select("2.5")
311 except VersionError, e:
4f60dce5 312 print "Asked for incompatible versions, got Exception:", e
d48c1c64
RD
313
314 # cleanup
315 for name in names:
316 d = os.path.join('/tmp', name)
317 os.rmdir(os.path.join(d, 'wx'))
318 os.rmdir(d)
319
320