import sys, os, shutil, stat import logging _log = logging.getLogger('_util.py') pyVersionDir = 'python' + str(sys.version_info[0]) + '.' + str(sys.version_info[1]) isDarwin = sys.platform.upper() == 'DARWIN' isWindows = sys.platform.upper().startswith('WIN') devenvVersion = None usingSubProcess = isWindows class BuildDataError(ValueError): pass class DummyAtt: def __init__(self, v = ''): self.value = v dummyAtt = DummyAtt() class UtilSingletons: macroExpander = None def unzip(filename, parent): import zipfile zf = zipfile.ZipFile(filename) for name in zf.namelist(): if not name.endswith('/'): fplist = [parent] + name.split('/') fp = os.path.join(*fplist) directPar = os.path.split(fp)[0] if not os.path.exists(directPar): os.makedirs(directPar) outfile = open(fp, 'wb') outfile.write(zf.read(name)) outfile.flush() outfile.close() def expandMacro(s): if UtilSingletons.macroExpander is None: assert(False) return s return UtilSingletons.macroExpander(s) def copyTreeIfNewer(source, destination, copySVNDir = False): '''copies the tree source to destination. Raises ValueError if source does not exist. Uses "copySVNDir" because .svn directories usually contain read only files that will not allow copying (and we do not generally need them copied in the build_cipres context).''' if not os.path.exists(source): raise ValueError, 'The path "%s" is not valid' % source destPar = os.path.split(destination)[0] if destPar and not os.path.exists(destPar): os.makedirs(destPar) if os.path.isdir(source): if not copySVNDir and os.path.basename(source) == '.svn': return [] if os.path.exists(destination): notReplaced = [] # can't think of a case where we replace a file with a directory, so I'll raise this exception as a safeguard if not os.path.isdir(destination): raise ValueError, 'Script will not replace a file (%s) with a directory (%s), remove the file by hand.' % (destination, source) for sub in os.listdir(source): notReplaced.extend(copyTreeIfNewer(os.path.join(source, sub), os.path.join(destination, sub))) return notReplaced shutil.copytree(source, destination) else: if os.path.exists(destination) and (os.stat(destination)[stat.ST_MTIME] >= os.stat(source)[stat.ST_MTIME]): return [destination] elif os.path.islink(destination): return [destination] shutil.copy2(source, destination) return [] def appendAttributeIf(self, attList, a): v = self.__dict__.get(a) if v: attList.append('%s="%s"' % (a, v)) class StepEnum: '''Enumeration of the steps of the installation process ['configure', 'build', 'stage', 'package', 'install'].''' clean, configure, build, stage, package, install = range(6) names = ['clean', 'configure', 'build', 'stage', 'package', 'install'] def toString(v): return StepEnum.names[v] toString = staticmethod(toString) def toNumber(s): try: return StepEnum.names.index(s) except ValueError: raise ValueError, '%s is an invalid name for a step in the build process.' % s toNumber = staticmethod(toNumber) def expandPath(s): '''returns an absolute path, expanding home dir and env vars''' if len(s) > 0: firstChar = s[0] if firstChar == os.curdir: return os.path.abspath(s) if isWindows: if firstChar == '%' or firstChar == '~': return os.path.abspath(os.path.expanduser(os.path.expandvars(s))) else: if firstChar == '$' or firstChar == '~': return os.path.abspath(os.path.expanduser(os.path.expandvars(s))) return s _envSep = isWindows and ';' or ':' '''Contains functions used in the build_cipres.py build coordination tool.''' def addEnv(environ, addedEnv, isPath = True, **kwargs): global _envSep '''Adds env variable specified as keyword args to "environ" and "addedEnv"''' for k, v in kwargs.iteritems(): if isPath: toAdd = isinstance(v, list) and _envSep.join(map(expandPath, v)) or expandPath(v) else: toAdd = isinstance(v, list) and _envSep.join(map(str, v)) or str(v) environ[k] = toAdd addedEnv[k] = toAdd def appendEnv(environ, addedEnv, isPath = True, **kwargs): '''appends values to a env setting (or defines the variable, if a value is not found)''' augmentEnv(environ, addedEnv, True, isPath, **kwargs) def prependEnv(environ, addedEnv, isPath = True, **kwargs): '''preends values to a env setting (or defines the variable, if a value is not found)''' augmentEnv(environ, addedEnv, False, isPath, **kwargs) def getArgAfterEquals(arg, name): s = arg.split('=') if len(s) != 2: sys.exit('expecting %s= arguments (with no spaces)' %name) return s[1] def augmentEnv(environ, addedEnv, push_back, isPath = True, **kwargs): '''Implementation used by appendEnv and prepend (with different values of "push_back")''' global _envSep for k, v in kwargs.iteritems(): prev = environ.get(k) if prev == None: addEnv(environ, addedEnv, isPath, **dict({k:v})) else: toAddList = isinstance(v, list) and v or [v] mapFunc = isPath and expandPath or str transformedToAddList = map(mapFunc, toAddList) assert(isinstance(prev, str)) prevList = prev.split(_envSep) toInsert = [i for i in transformedToAddList if i not in prevList] for elToInsert in toInsert: if elToInsert not in prevList: if push_back: prevList.append(elToInsert) else: prevList.insert(0, elToInsert) s = _envSep.join(prevList) environ[k] = s addedEnv[k] = s def printEnv(environ): for k, v in environ.iteritems(): _log.info('%-20s = %s' % (k,v)) def commandOS(cmd, args = [], environ = {}, dryRun = False): '''Spawns "cmd" (with "args" and current environment). Uses subprocess if the "usingSubProcess" global is True, and uses spawnvpe otherwise''' global usingSubProcess _log.info('Evironment for command:') printEnv(environ) _log.info('from %s' % expandPath('.')) _log.info('CommandOS = %s %s' % (cmd, args)) if dryRun: return if usingSubProcess: import subprocess proc = subprocess.Popen([cmd] + args, env = environ, shell=True) rc = proc.wait() else: rc = os.spawnvpe(os.P_WAIT, cmd, [cmd] + args, environ) if rc != 0: raise RuntimeError, '%s failed with code %d' % (cmd, rc) def make(dirList, environ): '''Executes "make" from every directory in "dirList"''' for dir in dirList: expanded = expandPath(dir) os.chdir(expanded) commandOS('make', [], environ) def checkVersionTuple(toCheck, required): '''Returns True if toCheck >= required at all relevant positions. >>> checkVersionTuple((2,), [1,2,5]) True >>> checkVersionTuple((1,2,4), [1,2,5]) False >>> checkVersionTuple((1,3,4), [1,2,5]) True >>> checkVersionTuple((1,2,16), [1,21,5]) False >>> checkVersionTuple([1,21,6], (1,21,5)) True >>> checkVersionTuple((1,21), [1,21,5]) False >>> checkVersionTuple((10,3,9), [10, 4]) False ''' for i in range(len(required)): n = i < len(toCheck) and toCheck[i] or 0 if n < required[i]: return False if n > required[i]: return True return True def my_sw_vers(): '''For apple (or other systems with /usr/bin/sw_vers return tuple of versions (10, 4, 2) for Mac OS 10.4.2 Taken from http://svn.red-bean.com/bob/py2app/trunk/src/bdist_mpkg/tools.py http://undefined.org/python/py2app.html ''' from distutils.version import StrictVersion, LooseVersion def Version(s): try: return StrictVersion(s) except ValueError: return LooseVersion(s) def sw_vers(_cache=[]): if not _cache: info = os.popen('/usr/bin/sw_vers').read().splitlines() for line in info: key, value = line.split(None, 1) if key == 'ProductVersion:': _cache.append(Version(value.strip())) break else: raise ValueError, "What?!" return _cache[0] return tuple(map(int, str(sw_vers()).split('.'))) def getValidPathAttribute(xmlTemplate, attName): '''reads an attribute from xmlTemplate (DOM object) and raise BuildDataError if the path does not exist.''' try: p = str(xmlTemplate.attributes[attName].value) if not os.path.exists(p): raise BuildDataError, '"%s" attribute for the target %s does not point to a valid file' % (p, xmlTemplate.attributes['name'].value) return p except KeyError: return None def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test()