#!/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