#!/usr/local/bin/python
from product import *
import copy
import logging
_log = logging.getLogger('target_base.py')
class TopLevelPath:
def __init__(self, **kwargs):
ap = expandPath(os.path.abspath('.'))
self.topDir = ap
for step in StepEnum.names:
att = step + '_dir'
self.__dict__[att] = kwargs.get(att, ap)
def getTopDir(self, step):
return self.__dict__[StepEnum.toString(step) + '_dir']
class DependsOn:
def __init__(self, xmlTemplate):
self.name = str(xmlTemplate.attributes['name'].value.upper())
self.level = StepEnum.toNumber(xmlTemplate.attributes.get('level', DummyAtt('build')).value)
self.needed_for_step = map(StepEnum.toNumber, xmlTemplate.attributes.get('needed_for_step', DummyAtt('configure build')).value.split())
self.target = None
def _resolve(self, targets, parent):
self.needed_by = parent
self.target = targets[self.name]
dependsTrace = self.target._needsTarget(parent)
if dependsTrace:
dependsTrace = [parent] + dependsTrace
sys.exit('Cycle in dependencies found: \n %s' % ['->'.join(map(lambda x: x.name, dependsTrace))])
def isCompleted(self, step):
return self.target.isCompleted(step)
def __str__(self):
atts = []
if self.level != StepEnum.build:
atts.append('level="%s"' % StepEnum.toString(self.level))
if self.needed_for_step and self.needed_for_step != [StepEnum.configure, StepEnum.build]:
atts.append('needed_for_step="%s"' % ' '.join(map(StepEnum.toString, self.needed_for_step)))
return '' % (self.name, ' '.join(atts))
def addEnvVar(self, environ, addedEnv, step):
if step in self.needed_for_step:
self.target.addEnvVarForOthers(environ, addedEnv, self.level)
def prepareRequiredTarget(self, step, addedEnv):
return step in self.needed_for_step and self.target.performStep(self.level, addedEnv) or addedEnv
class Target:
absPathToTop = None
inputOrEmptyAtts = ['move_unpacked_inside', 'archive_name', 'expanded_name']
configureArgs = [] #static used to enable debugging
def serializer():
_log.warning('build data snapshot was not saved')
serializer = staticmethod(serializer)
def _needsTarget(self, other):
if other.name == self.name:
return [self, self]
for b in self.depends_on:
if b.target is not None:
if b.target is other:
return [self, other]
dependsTrace = b.target._needsTarget(other)
if dependsTrace:
return [self] + dependsTrace
return False
def __init__(self, xmlTemplate):
self.name = str(xmlTemplate.attributes['name'].value.upper())
for k in xmlTemplate.attributes.keys():
# automatically deserialize any attributes that start with x_
if k.startswith('x_'):
self.__dict__[k] = str(xmlTemplate.attributes[k].value)
for a in Target.inputOrEmptyAtts:
self.__dict__[a] = str(xmlTemplate.attributes.get(a, dummyAtt).value)
self.appears_when = StepEnum.toNumber(xmlTemplate.attributes.get('appears_when', DummyAtt('clean')).value)
self.last_used_when = StepEnum.toNumber(xmlTemplate.attributes.get('last_used_when', DummyAtt('install')).value)
self.is_pseudo_target = str(xmlTemplate.attributes.get('is_pseudo_target', DummyAtt('FALSE')).value).upper() == 'TRUE' # pseudotargets set environment vars, but dont do any callbacks in _standardStep (don't call _build, _configure...)
for stepName in StepEnum.names:
att = stepName + '_dir'
self.__dict__[att] = str(xmlTemplate.attributes.get(att, dummyAtt).value)
att = stepName + '_is_dirty'
self.__dict__[att] = str(xmlTemplate.attributes.get(att, DummyAtt('FALSE')).value).upper() == 'TRUE'
att = stepName + '_done_at'
try:
p = getValidPathAttribute(xmlTemplate, att)
except BuildDataError:
if not self.is_pseudo_target:
print 'not a pseudo_target raising error.'
raise
if p and not os.path.isabs(p):
p = os.path.join(Target.absPathToTop.topDir, p)
self.__dict__[att] = p
for tag in ProductBase.allTags:
att = stepName + '_' + tag + '_dir'
if att in xmlTemplate.attributes.keys():
self.__dict__[att] = str(xmlTemplate.attributes.get(att).value)
self.depends_on = []
for tag in ProductBase.allTags:
self.__dict__[tag + '_products'] = []
for child in xmlTemplate.childNodes:
if child.nodeName == 'depends_on':
self.depends_on.append(DependsOn(child))
elif child.nodeName.endswith('_product'):
self.__dict__[child.nodeName + 's'].append(Product(child, self))
self.products = []
for tag in ProductBase.allTags:
self.products.extend(self.__dict__[tag + '_products'])
self.currentStep = StepEnum.clean
def __str__(self):
prefix = '\t (lenTopDir + 1):
c = doneAtAttr[lenTopDir]
if c in '/:\\':
doneAtAttr = doneAtAttr[lenTopDir + 1:]
else:
doneAtAtt = '.'
atts.append('%s="%s"' % (attName, doneAtAttr))
_log.debug('%s attribute written for %s' %(attName, self.name))
for tag in ProductBase.allTags:
appendAttributeIf(self, atts, stepName + '_' + tag + '_dir')
children = map(str, self.depends_on)
children.extend(map(str, self.products))
s = '\t\n\t\t%s\n\t' % (' '.join(atts), '\n\t\t'.join(children))
_log.debug(s)
return s
def touch(self):
if not self.is_pseudo_target:
self.currentStep = min(self.currentStep, StepEnum.configure)
for stepName in StepEnum.names[StepEnum.build:]:
if self.isCompleted(StepEnum.toNumber(stepName)):
self.__dict__[stepName + '_is_dirty'] = True
att = stepName + '_done-at'
if self.__dict__.get(att):
self.__dict__[att] = None
Target.serializer()
def reconfigure(self, addedEnv):
if not self.is_pseudo_target:
self.currentStep = StepEnum.clean
for stepName in StepEnum.names[StepEnum.configure:]:
att = stepName + '_done-at'
if self.__dict__.get(att):
self.__dict__[att] = None
Target.serializer()
self.configure(addedEnv)
Target.serializer()
def _resolveDependencies(self, targets):
'''Hack to deal with the fact that targets are not instantiated in dependency ordering. Finishes the initialization of DependsOn object'''
for i in self.depends_on:
i._resolve(targets, self)
def _finishInitialization(self, targets):
for stepName in StepEnum.names:
att = stepName + '_done_at'
if self.__dict__[att]:
step = StepEnum.toNumber(stepName)
if not self.isCompleted(step):
_log.warn('Ignoring %s attribute for %s (this step has not been successfully completed)' % (att, self.name))
self.__dict__[att] = None
else:
self.currentStep = max(self.currentStep, step)
if not (self.build_dir or self.build_done_at):
self.build_dir = os.path.join(self.move_unpacked_inside, self.expanded_name)
if not (self.configure_dir or self.configure_done_at):
if self.build_done_at:
self.configure_done_at = self.build_done_at
else:
self.configure_dir = self.build_dir
def addEnvVar(self, environ, addedEnv, step):
for p in self.products:
p.addEnvVar(environ, addedEnv, step)
def getPathForStep(self, step):
'''Returns @step@_done_at attribute or @step$_dir attribute (if done_at does not exist). Will be absolute path or relative to top'''
stepName = StepEnum.toString(step)
att = stepName + '_done_at'
v = self.__dict__[att]
p = v is not None and v or expandMacro(self.__dict__[stepName + '_dir'])
return p
def getAbsPath(self, step, createIfAbsent = True):
ap = self.getPathForStep(step)
if not os.path.isabs(ap):
ap = os.path.join(Target.absPathToTop.getTopDir(step), ap)
if createIfAbsent and not os.path.exists(ap):
os.makedirs(ap)
return ap
def getAbsCurrentPath(self, createIfAbsent = True):
return self.getAbsPath(self.currentStep, createIfAbsent)
def cdToPath(self, step):
os.chdir(self.getAbsPath(step))
def isCompleted(self, step):
'''Returns True is self and dependencies have completed this stage (does NOT use self.currentStep)'''
if self.is_pseudo_target:
return True
stepName = StepEnum.toString(step)
if self.__dict__[stepName + '_is_dirty']:
return False
p = self.getAbsPath(step, createIfAbsent = False)
att = stepName + '_done_at'
if not os.path.exists(p):
_log.debug('%s has not completed the %s step. The parent directory for this step (%s) does not exist.'% (self.name, stepName, p))
self.__dict__[att] = None
return False
self.__dict__[att] = p
toCheck = self.depends_on + self.products
for i in toCheck:
if not i.isCompleted(step):
_log.debug('%s has not completed the %s step, because %s is not complete'% (self.name, stepName, i.name))
self.__dict__[att] = None
return False
return True
def extract(self, silent = False):
if self.is_pseudo_target:
return True
buildDir = self.getAbsPath(StepEnum.build, createIfAbsent = False)
logfunc = silent and _log.debug or _log.info
logfunc('In extract - looking for %s' % buildDir)
if os.path.exists(buildDir):
logfunc('%s found (NOT extracting%s archive)' % (buildDir, ' ' + self.archive_name))
else:
sArchName = self.archive_name
if not sArchName:
raise RuntimeError, 'The build directory (%s) for %s was not found, but no archive_name was specified' % (buildDir, self.name)
archive = os.path.isabs(sArchName) and sArchName or os.path.join(Target.absPathToTop.topDir, sArchName)
if not os.path.exists(archive):
raise RuntimeError, 'Neither the build directory (%s) for %s nor the tar archive (%s) were found' % (buildDir, self.name, archive)
if not self.expanded_name:
raise RuntimeError, 'The expanded_name attribute for %s was not found (should be set to the name of the tar archive after expansion)' % self.name
commandOS('tar', ['xvfz', archive], os.environ)
if not os.path.exists(self.expanded_name):
raise RuntimeError, 'The tar command for %s did not result in the creation of the expected path (%s)' % (self.name, self.expanded_name)
absBuildPath = os.path.abspath(buildDir)
if absBuildPath != os.path.abspath(self.expanded_name):
parAbsBuildPath = os.path.split(absBuildPath)[0]
if not os.path.exists(parAbsBuildPath):
os.makedirs(parAbsBuildPath)
_log.debug('renaming ' + self.expanded_name + ' to ' + absBuildPath)
os.rename(self.expanded_name, absBuildPath)
_log.info(self.archive_name, 'was expanded and the resulting directory is now', absBuildPath)
return True
def addEnvVarForSelf(self, environ, addedEnv, step):
'''Hook to define env vars need to build self (other than those set by the products).'''
pass
def _addEnvVarForOthers(self, environ, addedEnv, step):
'''Hook to define env vars needed by others to use this target (other than those set by the) '''
pass
def addEnvVarForOthers(self, environ, addedEnv, step):
self._addEnvVarForOthers(environ, addedEnv, step)
for p in self.products:
p.addEnvVar(environ, addedEnv, step)
def addEnvVar(self, environ, addedEnv, step):
'''calls addEnvVar for dependencies, then self._addNonProductEnvVar, then addEnvVar for self.products and '''
for depend in self.depends_on:
depend.addEnvVar(environ, addedEnv, step)
self.addEnvVarForSelf(environ, addedEnv, step)
for p in self.products:
p.addEnvVar(environ, addedEnv, step)
def _verifyCurrentStep(self, step):
if step >= self.currentStep:
stepName = StepEnum.toString(step)
attName = stepName + '_done_at'
if self.isCompleted(step):
self.currentStep = step
self.__dict__[attName] = self.getAbsPath(step)
_log.debug('%s is compeleted, setting %s=%s' % (stepName, attName, self.__dict__[attName]))
Target.serializer()
return True
_log.debug('%s was not compeleted.' % stepName)
self.__dict__[attName] = None
self.currentStep = StepEnum.clean
if step > StepEnum.clean:
self._verifyCurrentStep(step - 1)
return False
return True
def performStep(self, step, addedEnv):
assert(step >= StepEnum.configure)
origEnv = copy.deepcopy(os.environ)
origAddedEnv = copy.deepcopy(addedEnv)
for s in range(StepEnum.configure, step + 1):
# we want the environ, and addedEnv to reflect the last command only when we exit
os.environ.clear()
os.environ.update(origEnv)
addedEnv.clear()
addedEnv.update(origAddedEnv)
if s == StepEnum.configure:
self.configure(addedEnv)
elif s == StepEnum.build:
self.build(addedEnv)
elif s == StepEnum.stage:
self.stage(addedEnv)
elif s == StepEnum.package:
self.package(addedEnv)
return addedEnv
def _standardStep(self, step, addedEnv, callback, postCallback = None):
origAddedEnv = copy.deepcopy(addedEnv)
origEnv = copy.deepcopy(os.environ)
if self.currentStep < step:
for d in self.depends_on:
addedEnv = d.prepareRequiredTarget(step, addedEnv)
self.addEnvVar(os.environ, addedEnv, step)
if not self.is_pseudo_target:
callback()
if postCallback:
postCallback()
self.__dict__[StepEnum.names[step] + '_is_dirty'] = False
if self._verifyCurrentStep(step):
_log.info('%s for %s completed.' % (StepEnum.toString(step), self.name))
addedEnv.clear()
addedEnv.update(origAddedEnv)
os.environ.clear()
os.environ.update(origEnv)
for d in self.depends_on:
d.target.addEnvVar(os.environ, addedEnv, step)
self.addEnvVarForOthers(os.environ, addedEnv, step)
def configure(self, addedEnv):
self.extract(True)
self._standardStep(StepEnum.configure, addedEnv, self._configure, None)
def build(self, addedEnv):
self._standardStep(StepEnum.build, addedEnv, self._build, None)
def stage(self, addedEnv):
self._standardStep(StepEnum.stage, addedEnv, self._stage, self._stageProducts)
def package(self, addedEnv):
self._standardStep(StepEnum.package, addedEnv, self._package, None)
def _stageProducts(self):
if self.is_pseudo_target:
return
for p in self.products:
p.stage()
def _configure(self): pass
def _build(self): pass
def _stage(self): pass
def _package(self): pass