Mercurial > pylearn
comparison version.py @ 324:ce79bf5fa463
- the cut and paste between file and dir conditions is always a bad thing
- i made one function (hg_version) to basically call and parse hg
- i made a function to include the cases of what might be returned by
imp.find_modules (_input_id)
- the check for a .hg folder was insufficient. Lots of things could go wrong.
Instead I use the return code from the Popen process. The return code
catches this and any other problem that hg runs into.
- its easier to offer more rcs support in future (cvs,svn,git)
author | James Bergstra <bergstrj@iro.umontreal.ca> |
---|---|
date | Thu, 12 Jun 2008 20:54:49 -0400 |
parents | 44f94ffe28f7 |
children | 7a734dba4cac |
comparison
equal
deleted
inserted
replaced
323:44f94ffe28f7 | 324:ce79bf5fa463 |
---|---|
1 import subprocess as _subprocess | 1 import subprocess as _subprocess |
2 import imp as _imp | 2 import imp as _imp |
3 import sys | 3 import sys |
4 import os | 4 import os |
5 | 5 |
6 _cache = {} | 6 |
7 | 7 |
8 def src_version(module_name): | 8 def src_version(module_name): |
9 """Return compact identifier of module code. | 9 """Return compact identifier of module code. |
10 | 10 |
11 @return: compact identifier of module code. | 11 @return: compact identifier of module code. |
121 _cache[module_name] = tokens[0] | 121 _cache[module_name] = tokens[0] |
122 | 122 |
123 | 123 |
124 return _cache[module_name] | 124 return _cache[module_name] |
125 | 125 |
126 | 126 _unknown_version = 'unknown version' |
127 | |
128 def hg_version(dirname, filenames=None): | |
129 """Return current changeset of directory I{dirname}. | |
130 | |
131 @type filename: list of str (or default: None) | |
132 @param filename: if specified, we ignore modifications to other files. | |
133 | |
134 @rtype: tuple (last changeset, modified) | |
135 | |
136 """ | |
137 if type(filenames) not in (list, tuple, type(None)): | |
138 raise TypeError(filenames) | |
139 | |
140 #may raise exception, for example if hg is not visible via PATH | |
141 status_proc = _subprocess.Popen(('hg','st'), cwd=dirname, | |
142 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE) | |
143 status = status_proc.communicate()[0] #read stdout into buffer | |
144 if status_proc.returncode != 0: | |
145 raise OSError('hg returned %i, maybe %s is not under hg control?', | |
146 (status_proc.returncode, dirname)) | |
147 | |
148 #may raise exception, for example if hg is not visible via PATH | |
149 id_proc = _subprocess.Popen(('hg','id', '-i'), cwd=dirname, | |
150 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE) | |
151 id_stdout = id_proc.communicate()[0] | |
152 if id_proc.returncode != 0: | |
153 raise OSError('hg returned %i, maybe %s is not under hg control?', | |
154 (id_proc.returncode, dirname)) | |
155 | |
156 care_about = (lambda some_file : True) if filenames is None \ | |
157 else (lambda some_file : some_file in filenames) | |
158 | |
159 # parse status codes for what we care about | |
160 care_about_mod = False | |
161 for line in status.split('\n'): | |
162 if not line: #empty lines happen | |
163 continue | |
164 line_file = line[2:] | |
165 if line[0] != '?' and care_about(line_file): | |
166 care_about_mod = True | |
167 #raise Exception('Uncommitted modification', | |
168 #os.path.join(dirname, line_file)) | |
169 if line[0] == '?' and line[-3:] == '.py': | |
170 print >> sys.stderr, 'WARNING: untracked file', os.path.join(dirname, line_file) | |
171 | |
172 # id_stdout is 12 hex digits followed by '+\n' or '\n' | |
173 # return the trailing '+' character only if there were changes to files that | |
174 # the caller cares about (named in filenames) | |
175 modified = (id_stdout[12] == '+') | |
176 assert len(id_stdout) in (13, 14) #sanity check | |
177 if modified and care_about_mod : | |
178 return id_stdout[:13] | |
179 else: | |
180 return id_stdout[:12] | |
181 | |
182 def _import_id_py_source(location): | |
183 try: | |
184 dirname = os.path.dirname(location[1]) | |
185 basename = os.path.basename(location[1]) | |
186 return hg_version(dirname, [basename]) | |
187 except OSError, e: | |
188 print >> sys.stderr, 'IGNORNING', e | |
189 return _unknown_version + ' PY_SOURCE' | |
190 | |
191 def _import_id_py_compiled(location): | |
192 #a .pyc file was found, but no corresponding .py | |
193 return _unknown_version + ' PYC_COMPILED' | |
194 | |
195 def _import_id_pkg_directory(location): | |
196 try: | |
197 return hg_version(location[1]) | |
198 except OSError, e: | |
199 print >> sys.stderr, 'IGNORNING', e | |
200 return _unknown_version + ' PKG_DIRECTORY' | |
201 | |
202 def _import_id(tag): | |
203 try : | |
204 location = _imp.find_module(tag) | |
205 except ImportError, e: #raise when tag is not found | |
206 return e #put this in the cache, import_id will raise it | |
207 | |
208 #the find_module was successful, location is valid | |
209 resource_type = location[2][2] | |
210 | |
211 if resource_type == _imp.PY_SOURCE: | |
212 return _import_id_py_source(location) | |
213 if resource_type == _imp.PY_COMPILED: | |
214 return _import_id_py_compiled(location) | |
215 if resource_type == _imp.C_EXTENSION: | |
216 raise NoteImplementedError | |
217 if resource_type == _imp.PY_RESOURCE: | |
218 raise NoteImplementedError | |
219 if resource_type == _imp.PKG_DIRECTORY: | |
220 return _import_id_pkg_directory(location) | |
221 if resource_type == _imp.C_BUILTIN: | |
222 raise NoteImplementedError | |
223 if resource_type == _imp.PY_FROZEN: | |
224 raise NoteImplementedError | |
225 | |
226 assert False #the list of resource types above should be exhaustive | |
227 | |
228 def import_id(tag): | |
229 """Return an identifier of the code imported by 'import <tag>'. | |
230 | |
231 @param tag: a module or file name | |
232 @type tag: string | |
233 | |
234 @rtype: string | |
235 @return: identifier of the code imported by 'import <tag>'. | |
236 | |
237 This high-level function might do different things depending on, for | |
238 example, whether I{tag} identifies a file or a directory, or whether the | |
239 named entity is under some sort of version/revision control. | |
240 | |
241 Versions are sought in the following order: | |
242 0. If I{tag} is 'python' then sys.version will be returned | |
243 1. If I{tag} names a file or folder under revision control, this function | |
244 will attempt to guess which one, and return a string that identifies the | |
245 running code (a revision id, not the whole file!) | |
246 2. If I{tag} names a module with a __version__ attribute, then that | |
247 attribute will be returned as a string. | |
248 3. The string starting with 'unknown version' will be returned for other valid modules. | |
249 4. An exception will be raise for non-existent modules. | |
250 | |
251 @note: This function may import the named entity in order to return a | |
252 __version__ module attribute. | |
253 | |
254 """ | |
255 if tag not in import_id.cache: | |
256 import_id.cache[tag] = _import_id(tag) | |
257 | |
258 #in the case of bad module names, we cached the ImportError exception | |
259 rval = import_id.cache[tag] | |
260 if isinstance(rval, Exception): | |
261 raise rval | |
262 return rval | |
263 import_id.cache = {'python':sys.version} | |
127 | 264 |
128 def get_all_src_versions() : | 265 def get_all_src_versions() : |
129 """ | 266 """ |
130 Get the version of all loaded module. | 267 Get the version of all loaded module. |
131 Calls src_version on all loaded modules. These modules are found | 268 Calls src_version on all loaded modules. These modules are found |
137 @SEE src_version | 274 @SEE src_version |
138 """ | 275 """ |
139 allmodules = sys.modules | 276 allmodules = sys.modules |
140 d = dict() | 277 d = dict() |
141 for m in allmodules : | 278 for m in allmodules : |
142 d[m] = src_version(m) | 279 try: |
280 d[m] = import_id(m) | |
281 except: | |
282 pass | |
143 return d | 283 return d |
144 | 284 |
145 | 285 |
146 if __name__ == "__main__" : | 286 if __name__ == "__main__" : |
147 | 287 |
148 if len(sys.argv) == 2 : | 288 if len(sys.argv) == 2 : |
149 print 'testing on', sys.argv[1] | 289 print 'testing on', sys.argv[1] |
150 print src_version(sys.argv[1]) | 290 print import_id(sys.argv[1]) |
151 | 291 |