]>
Commit | Line | Data |
---|---|---|
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 | """ | |
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'. You use it like this: | |
19 | ||
20 | import wxversion | |
21 | wxversion.require('2.4') | |
22 | import wx | |
23 | ||
24 | Of course the default wxPython version can also be controlled by | |
25 | setting PYTHONPATH or by editing the wx.pth path configuration file, | |
26 | but using wxversion will allow an application to manage the version | |
27 | selection itself rather than depend on the user to setup the | |
28 | environment correctly. | |
29 | ||
30 | It works by searching the sys.path for directories matching wx-* and | |
31 | then comparing them to what was passed to the require function. If a | |
32 | match is found then that path is inserted into sys.path. | |
33 | """ | |
34 | ||
35 | import sys, os, glob, fnmatch | |
36 | ||
37 | ||
38 | ||
39 | ||
40 | def require(versions): | |
41 | """ | |
42 | Search for a wxPython installation that matches version. | |
43 | ||
93ba536a RD |
44 | :param version: Specifies the version to look for, it can |
45 | either be a string or a list of strings. Each | |
46 | string is compared to the installed wxPythons | |
47 | and the best match is inserted into the | |
48 | sys.path, allowing an 'import wx' to find that | |
49 | version. | |
d48c1c64 RD |
50 | |
51 | The version string is composed of the dotted | |
52 | version number (at least 2 of the 4 components) | |
53 | optionally followed by hyphen ('-') separated | |
54 | options (wx port, unicode/ansi, flavour, etc.) A | |
55 | match is determined by how much of the installed | |
56 | version matches what is given in the version | |
57 | parameter. If the version number components don't | |
58 | match then the score is zero, otherwise the score | |
59 | is increased for every specified optional component | |
60 | that is specified and that matches. | |
61 | """ | |
62 | assert not sys.modules.has_key('wx') and not sys.modules.has_key('wxPython'), \ | |
63 | "wxversion.require() must be called before wxPython is imported" | |
64 | ||
65 | bestMatch = None | |
66 | bestScore = 0 | |
67 | if type(versions) == str: | |
68 | versions = [versions] | |
69 | ||
70 | packages = _find_installed() | |
71 | for pkg in packages: | |
72 | for ver in versions: | |
73 | score = pkg.Score(_wxPackageInfo(ver)) | |
74 | if score > bestScore: | |
75 | bestMatch = pkg | |
76 | bestScore = score | |
77 | ||
78 | assert bestMatch is not None, \ | |
79 | "Required version of wxPython not found" | |
80 | ||
81 | sys.path.insert(0, bestMatch.pathname) | |
82 | ||
83 | ||
84 | ||
85 | ||
86 | _pattern = "wx-[0-9].*" | |
87 | def _find_installed(): | |
88 | installed = [] | |
17f3e530 | 89 | toRemove = [] |
d48c1c64 RD |
90 | for pth in sys.path: |
91 | ||
92 | # empty means to look in the current dir | |
93 | if not pth: | |
94 | pth = '.' | |
95 | ||
96 | # skip it if it's not a package dir | |
97 | if not os.path.isdir(pth): | |
98 | continue | |
99 | ||
100 | base = os.path.basename(pth) | |
101 | ||
17f3e530 RD |
102 | # if it's a wx path that's already in the sys.path then mark |
103 | # it for removal and then skip it | |
d48c1c64 | 104 | if fnmatch.fnmatchcase(base, _pattern): |
17f3e530 | 105 | toRemove.append(pth) |
d48c1c64 RD |
106 | continue |
107 | ||
108 | # now look in the dir for matching subdirs | |
109 | for name in glob.glob(os.path.join(pth, _pattern)): | |
110 | # make sure it's a directory | |
111 | if not os.path.isdir(name): | |
112 | continue | |
113 | # and has a wx subdir | |
114 | if not os.path.exists(os.path.join(name, 'wx')): | |
115 | continue | |
116 | installed.append(_wxPackageInfo(name, True)) | |
117 | ||
17f3e530 RD |
118 | for rem in toRemove: |
119 | del sys.path[sys.path.index(rem)] | |
120 | ||
d48c1c64 RD |
121 | installed.sort() |
122 | installed.reverse() | |
123 | return installed | |
124 | ||
125 | ||
126 | class _wxPackageInfo(object): | |
127 | def __init__(self, pathname, stripFirst=False): | |
128 | self.pathname = pathname | |
129 | base = os.path.basename(pathname) | |
130 | segments = base.split('-') | |
131 | if stripFirst: | |
132 | segments = segments[1:] | |
133 | self.version = tuple([int(x) for x in segments[0].split('.')]) | |
134 | self.options = segments[1:] | |
135 | ||
136 | ||
137 | def Score(self, other): | |
138 | score = 0 | |
139 | # whatever version components given in other must match exactly | |
140 | if len(self.version) > len(other.version): | |
141 | v = self.version[:len(other.version)] | |
142 | else: | |
143 | v = self.version | |
144 | if v != other.version: | |
145 | return 0 | |
146 | score += 1 | |
147 | for opt in other.options: | |
148 | if opt in self.options: | |
149 | score += 1 | |
150 | return score | |
151 | ||
152 | ||
153 | # TODO: factor self.options into the sort order? | |
154 | def __lt__(self, other): | |
155 | return self.version < other.version | |
156 | def __gt__(self, other): | |
157 | return self.version > other.version | |
158 | def __eq__(self, other): | |
159 | return self.version == other.version | |
160 | ||
161 | ||
162 | ||
163 | ||
164 | ||
165 | if __name__ == '__main__': | |
17f3e530 | 166 | import pprint |
d48c1c64 RD |
167 | def test(version): |
168 | savepath = sys.path[:] | |
169 | require(version) | |
170 | print "Asked for %s:\t got: %s" % (version, sys.path[0]) | |
17f3e530 RD |
171 | pprint.pprint(sys.path) |
172 | ||
d48c1c64 RD |
173 | sys.path = savepath[:] |
174 | ||
175 | ||
176 | # make some test dirs | |
177 | names = ['wx-2.4', | |
178 | 'wx-2.5.2', | |
179 | 'wx-2.5.2.9-gtk2-unicode', | |
180 | 'wx-2.5.2.9-gtk-ansi', | |
181 | 'wx-2.5.1', | |
182 | 'wx-2.5.2.8-gtk2-unicode', | |
183 | 'wx-2.5.3'] | |
184 | for name in names: | |
185 | d = os.path.join('/tmp', name) | |
186 | os.mkdir(d) | |
187 | os.mkdir(os.path.join(d, 'wx')) | |
188 | ||
189 | # setup sys.path to see those dirs | |
190 | sys.path.append('/tmp') | |
191 | ||
192 | ||
193 | # now run some tests | |
194 | test("2.4") | |
195 | test("2.5") | |
196 | test("2.5-gtk2") | |
197 | test("2.5.2") | |
198 | test("2.5-ansi") | |
199 | test("2.5-unicode") | |
200 | ||
201 | # There isn't a unicode match for this one, but it will give the best | |
202 | # available 2.4. Should it give an error instead? I don't think so... | |
203 | test("2.4-unicode") | |
204 | ||
205 | try: | |
206 | # expecting an error on this one | |
207 | test("2.6") | |
208 | except AssertionError: | |
209 | print "Asked for 2.6:\t got: Assertion" | |
210 | ||
211 | # Try asking for multiple versions | |
212 | test(["2.6", "2.5.3", "2.5.2-gtk2"]) | |
213 | ||
214 | # cleanup | |
215 | for name in names: | |
216 | d = os.path.join('/tmp', name) | |
217 | os.rmdir(os.path.join(d, 'wx')) | |
218 | os.rmdir(d) | |
219 | ||
220 |