blob: 68d81945ef3e2fc87121ad8ca064bf96e166b45c [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001from __future__ import generators
2import sys, imp, marshal
3from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
4from distutils.version import StrictVersion, LooseVersion
5
6__all__ = [
7 'Require', 'find_module', 'get_module_constant', 'extract_constant'
8]
9
10class Require:
11 """A prerequisite to building or installing a distribution"""
12
13 def __init__(self,name,requested_version,module,homepage='',
14 attribute=None,format=None
15 ):
16
17 if format is None and requested_version is not None:
18 format = StrictVersion
19
20 if format is not None:
21 requested_version = format(requested_version)
22 if attribute is None:
23 attribute = '__version__'
24
25 self.__dict__.update(locals())
26 del self.self
27
28
29 def full_name(self):
30 """Return full package/distribution name, w/version"""
31 if self.requested_version is not None:
32 return '%s-%s' % (self.name,self.requested_version)
33 return self.name
34
35
36 def version_ok(self,version):
37 """Is 'version' sufficiently up-to-date?"""
38 return self.attribute is None or self.format is None or \
39 str(version)<>"unknown" and version >= self.requested_version
40
41
42 def get_version(self, paths=None, default="unknown"):
43
44 """Get version number of installed module, 'None', or 'default'
45
46 Search 'paths' for module. If not found, return 'None'. If found,
47 return the extracted version attribute, or 'default' if no version
48 attribute was specified, or the value cannot be determined without
49 importing the module. The version is formatted according to the
50 requirement's version format (if any), unless it is 'None' or the
51 supplied 'default'.
52 """
53
54 if self.attribute is None:
55 try:
56 f,p,i = find_module(self.module,paths)
57 if f: f.close()
58 return default
59 except ImportError:
60 return None
61
62 v = get_module_constant(self.module,self.attribute,default,paths)
63
64 if v is not None and v is not default and self.format is not None:
65 return self.format(v)
66
67 return v
68
69
70 def is_present(self,paths=None):
71 """Return true if dependency is present on 'paths'"""
72 return self.get_version(paths) is not None
73
74
75 def is_current(self,paths=None):
76 """Return true if dependency is present and up-to-date on 'paths'"""
77 version = self.get_version(paths)
78 if version is None:
79 return False
80 return self.version_ok(version)
81
82
83def _iter_code(code):
84
85 """Yield '(op,arg)' pair for each operation in code object 'code'"""
86
87 from array import array
88 from dis import HAVE_ARGUMENT, EXTENDED_ARG
89
90 bytes = array('b',code.co_code)
91 eof = len(code.co_code)
92
93 ptr = 0
94 extended_arg = 0
95
96 while ptr<eof:
97
98 op = bytes[ptr]
99
100 if op>=HAVE_ARGUMENT:
101
102 arg = bytes[ptr+1] + bytes[ptr+2]*256 + extended_arg
103 ptr += 3
104
105 if op==EXTENDED_ARG:
106 extended_arg = arg * 65536L
107 continue
108
109 else:
110 arg = None
111 ptr += 1
112
113 yield op,arg
114
115
116
117
118
119
120
121
122
123
124def find_module(module, paths=None):
125 """Just like 'imp.find_module()', but with package support"""
126
127 parts = module.split('.')
128
129 while parts:
130 part = parts.pop(0)
131 f, path, (suffix,mode,kind) = info = imp.find_module(part, paths)
132
133 if kind==PKG_DIRECTORY:
134 parts = parts or ['__init__']
135 paths = [path]
136
137 elif parts:
138 raise ImportError("Can't find %r in %s" % (parts,module))
139
140 return info
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165def get_module_constant(module, symbol, default=-1, paths=None):
166
167 """Find 'module' by searching 'paths', and extract 'symbol'
168
169 Return 'None' if 'module' does not exist on 'paths', or it does not define
170 'symbol'. If the module defines 'symbol' as a constant, return the
171 constant. Otherwise, return 'default'."""
172
173 try:
174 f, path, (suffix,mode,kind) = find_module(module,paths)
175 except ImportError:
176 # Module doesn't exist
177 return None
178
179 try:
180 if kind==PY_COMPILED:
181 f.read(8) # skip magic & date
182 code = marshal.load(f)
183 elif kind==PY_FROZEN:
184 code = imp.get_frozen_object(module)
185 elif kind==PY_SOURCE:
186 code = compile(f.read(), path, 'exec')
187 else:
188 # Not something we can parse; we'll have to import it. :(
189 if module not in sys.modules:
190 imp.load_module(module,f,path,(suffix,mode,kind))
191 return getattr(sys.modules[module],symbol,None)
192
193 finally:
194 if f:
195 f.close()
196
197 return extract_constant(code,symbol,default)
198
199
200
201
202
203
204
205
206def extract_constant(code,symbol,default=-1):
207
208 """Extract the constant value of 'symbol' from 'code'
209
210 If the name 'symbol' is bound to a constant value by the Python code
211 object 'code', return that value. If 'symbol' is bound to an expression,
212 return 'default'. Otherwise, return 'None'.
213
214 Return value is based on the first assignment to 'symbol'. 'symbol' must
215 be a global, or at least a non-"fast" local in the code block. That is,
216 only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
217 must be present in 'code.co_names'.
218 """
219
220 if symbol not in code.co_names:
221 # name's not there, can't possibly be an assigment
222 return None
223
224 name_idx = list(code.co_names).index(symbol)
225
226 STORE_NAME = 90
227 STORE_GLOBAL = 97
228 LOAD_CONST = 100
229
230 const = default
231
232 for op, arg in _iter_code(code):
233
234 if op==LOAD_CONST:
235 const = code.co_consts[arg]
236 elif arg==name_idx and (op==STORE_NAME or op==STORE_GLOBAL):
237 return const
238 else:
239 const = default