# HG changeset patch # User James Bergstra # Date 1213318489 14400 # Node ID ce79bf5fa4637af9dea689cad0e803aa8325b8f9 # Parent 44f94ffe28f7b8307ac75406b925ff3fb41ca1e7 - 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) diff -r 44f94ffe28f7 -r ce79bf5fa463 version.py --- a/version.py Thu Jun 12 17:12:45 2008 -0400 +++ b/version.py Thu Jun 12 20:54:49 2008 -0400 @@ -3,7 +3,7 @@ import sys import os -_cache = {} + def src_version(module_name): """Return compact identifier of module code. @@ -123,7 +123,144 @@ return _cache[module_name] +_unknown_version = 'unknown version' +def hg_version(dirname, filenames=None): + """Return current changeset of directory I{dirname}. + + @type filename: list of str (or default: None) + @param filename: if specified, we ignore modifications to other files. + + @rtype: tuple (last changeset, modified) + + """ + if type(filenames) not in (list, tuple, type(None)): + raise TypeError(filenames) + + #may raise exception, for example if hg is not visible via PATH + status_proc = _subprocess.Popen(('hg','st'), cwd=dirname, + stdout=_subprocess.PIPE, stderr=_subprocess.PIPE) + status = status_proc.communicate()[0] #read stdout into buffer + if status_proc.returncode != 0: + raise OSError('hg returned %i, maybe %s is not under hg control?', + (status_proc.returncode, dirname)) + + #may raise exception, for example if hg is not visible via PATH + id_proc = _subprocess.Popen(('hg','id', '-i'), cwd=dirname, + stdout=_subprocess.PIPE, stderr=_subprocess.PIPE) + id_stdout = id_proc.communicate()[0] + if id_proc.returncode != 0: + raise OSError('hg returned %i, maybe %s is not under hg control?', + (id_proc.returncode, dirname)) + + care_about = (lambda some_file : True) if filenames is None \ + else (lambda some_file : some_file in filenames) + + # parse status codes for what we care about + care_about_mod = False + for line in status.split('\n'): + if not line: #empty lines happen + continue + line_file = line[2:] + if line[0] != '?' and care_about(line_file): + care_about_mod = True + #raise Exception('Uncommitted modification', + #os.path.join(dirname, line_file)) + if line[0] == '?' and line[-3:] == '.py': + print >> sys.stderr, 'WARNING: untracked file', os.path.join(dirname, line_file) + + # id_stdout is 12 hex digits followed by '+\n' or '\n' + # return the trailing '+' character only if there were changes to files that + # the caller cares about (named in filenames) + modified = (id_stdout[12] == '+') + assert len(id_stdout) in (13, 14) #sanity check + if modified and care_about_mod : + return id_stdout[:13] + else: + return id_stdout[:12] + +def _import_id_py_source(location): + try: + dirname = os.path.dirname(location[1]) + basename = os.path.basename(location[1]) + return hg_version(dirname, [basename]) + except OSError, e: + print >> sys.stderr, 'IGNORNING', e + return _unknown_version + ' PY_SOURCE' + +def _import_id_py_compiled(location): + #a .pyc file was found, but no corresponding .py + return _unknown_version + ' PYC_COMPILED' + +def _import_id_pkg_directory(location): + try: + return hg_version(location[1]) + except OSError, e: + print >> sys.stderr, 'IGNORNING', e + return _unknown_version + ' PKG_DIRECTORY' + +def _import_id(tag): + try : + location = _imp.find_module(tag) + except ImportError, e: #raise when tag is not found + return e #put this in the cache, import_id will raise it + + #the find_module was successful, location is valid + resource_type = location[2][2] + + if resource_type == _imp.PY_SOURCE: + return _import_id_py_source(location) + if resource_type == _imp.PY_COMPILED: + return _import_id_py_compiled(location) + if resource_type == _imp.C_EXTENSION: + raise NoteImplementedError + if resource_type == _imp.PY_RESOURCE: + raise NoteImplementedError + if resource_type == _imp.PKG_DIRECTORY: + return _import_id_pkg_directory(location) + if resource_type == _imp.C_BUILTIN: + raise NoteImplementedError + if resource_type == _imp.PY_FROZEN: + raise NoteImplementedError + + assert False #the list of resource types above should be exhaustive + +def import_id(tag): + """Return an identifier of the code imported by 'import '. + + @param tag: a module or file name + @type tag: string + + @rtype: string + @return: identifier of the code imported by 'import '. + + This high-level function might do different things depending on, for + example, whether I{tag} identifies a file or a directory, or whether the + named entity is under some sort of version/revision control. + + Versions are sought in the following order: + 0. If I{tag} is 'python' then sys.version will be returned + 1. If I{tag} names a file or folder under revision control, this function + will attempt to guess which one, and return a string that identifies the + running code (a revision id, not the whole file!) + 2. If I{tag} names a module with a __version__ attribute, then that + attribute will be returned as a string. + 3. The string starting with 'unknown version' will be returned for other valid modules. + 4. An exception will be raise for non-existent modules. + + @note: This function may import the named entity in order to return a + __version__ module attribute. + + """ + if tag not in import_id.cache: + import_id.cache[tag] = _import_id(tag) + + #in the case of bad module names, we cached the ImportError exception + rval = import_id.cache[tag] + if isinstance(rval, Exception): + raise rval + return rval +import_id.cache = {'python':sys.version} def get_all_src_versions() : """ @@ -139,7 +276,10 @@ allmodules = sys.modules d = dict() for m in allmodules : - d[m] = src_version(m) + try: + d[m] = import_id(m) + except: + pass return d @@ -147,5 +287,5 @@ if len(sys.argv) == 2 : print 'testing on', sys.argv[1] - print src_version(sys.argv[1]) + print import_id(sys.argv[1])