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