def __init__(self, name=None): MObject.__init__(self, name) self.__timeKeeper = TimeKeeper() self.__workingDir = None self.__started = False self.__finished = False self.__aborted = False self.__result = None self._setStdOut(None) self._setStdErr(None) self.setIgnorePreviousFailure(False)
def __init__( self, stepName = None ): MObject.__init__( self, stepName ) self.setStatus( Step.Status.New ) self.setResult( Step.Result.NotExecuted ) self.__timeKeeper = TimeKeeper() self.__enabled = True self.__ignorePreviousFailure = False self.__preActions = [] # list of preparation actions self.__mainActions = [] # list of main actions self.__postActions = [] # list of post actions self.__logfilePath = None
class Action(MObject): """Action is the base class for executomat actions. Every action has a working directory, and an integer result. A result of zero (0) indicates success. The output is registered separately for (potentially imaginary) stdout and stderr streams, and can be saved to a log file. """ def run(self): """run() executes the workload of the action. Run must return a non-negative integer number. A return value or zero indicates success. Any value different from zero is considered an error.""" raise NotImplementedError() def getObjectDescription(self): return self.getLogDescription() def getLogDescription(self): """Provide a textual description for the Action that can be added to the execution log file.""" raise NotImplementedError() def __init__(self, name=None): MObject.__init__(self, name) self.__timeKeeper = TimeKeeper() self.__workingDir = None self.__started = False self.__finished = False self.__aborted = False self.__result = None self._setStdOut(None) self._setStdErr(None) self.setIgnorePreviousFailure(False) def setWorkingDirectory(self, workingDir): """Set the directory to execute the command in.""" check_for_path(workingDir, "The working directory parameter must be a string containing a directory name.") self.__workingDir = workingDir def getWorkingDirectory(self): """Return the working directory.""" return self.__workingDir def _aboutToStart(self): self.__started = True def wasStarted(self): return self.__started != False def setIgnorePreviousFailure(self, onOff): """If true, the action will be executed even if a previous action of the same step failed.""" self.__ignorePreviousFailure = onOff def getIgnorePreviousFailure(self): return self.__ignorePreviousFailure def _finished(self): self.__finished = True def didFinish(self): return self.__finished != False def _aborted(self): self.__aborted = True def getAborted(self): return self.__aborted def _setResult(self, result): check_for_int(result, "The result of an action must be a non-negative integer!") self.__result = result def getResult(self): return self.__result def _setStdErr(self, err): self.__stdErr = err def getStdErr(self): """Returns the stderr output of the action. Can only be called after execution.""" if not self.didFinish() and not self.getAborted(): raise MomError("getStdErr() queried before the action was finished") return self.__stdErr def _setStdOut(self, out): self.__stdOut = out def getStdOut(self): """Returns the stdout output of the action. Can only be called after execution.""" if not self.didFinish() and not self.getAborted(): raise MomError("getStdOut() queried before the action was finished") return self.__stdOut def executeAction(self, logFile=None): with self.__timeKeeper: with EnvironmentSaver(): if self.getWorkingDirectory(): mApp().debugN(self, 3, 'changing directory to "{0}"'.format(self.getWorkingDirectory())) try: os.chdir(str(self.getWorkingDirectory())) except (OSError, IOError) as e: raise BuildError(str(e)) self._aboutToStart() mApp().debugN(self, 3, "executing action {0}".format(self.getLogDescription())) try: result = self.run() if result == None or not isinstance(result, int): raise MomError( "Action {0} ({1}) did not return a valid non-negative integer return value from run()!".format( self.getName(), self.getLogDescription() ) ) self._setResult(int(result)) self._finished() except MomException as e: innerTraceback = "".join(traceback.format_tb(sys.exc_info()[2])) self._aborted() mApp().debug(self, 'execution failed: "{0}"'.format(str(e))) mApp().debugN(self, 2, innerTraceback) self._setStdErr("{0}:\n\n{1}".format(e, innerTraceback)) self._setResult(e.getReturnCode()) self._writeLog(logFile) mApp().debugN(self, 2, "{0} duration: {1}".format(self.getLogDescription(), self.__timeKeeper.deltaString())) return self.getResult() def _writeLog(self, filePath): """Write the results of this action to the specified path """ if not filePath: mApp().debugN(self, 3, "No log file specified, won't write log") return try: with codecs.open(filePath, "a", "utf-8") as f: f.writelines("*** Log from {0} ***\n".format(self.getName())) f.writelines("{0}\n".format(self.getLogDescription())) if self.getStdOut() or self.getStdErr(): if self.getStdOut(): f.writelines("\n=== Standard output ===\n" + self.getStdOut().rstrip() + "\n") if self.getStdErr(): f.writelines("\n=== Error output ===\n" + self.getStdErr().rstrip() + "\n") else: f.writelines("(The action did not generate any output.)\n") # append some separator string f.writelines("\n*** End of log ***\n\n") except Exception as e: raise MomError('cannot write to log file "{0}": {1}'.format(filePath, str(e))) def getTagName(self): return "action" def createXmlNode(self, document): node = super(Action, self).createXmlNode(document) node.attributes["finished"] = str(self.didFinish()) node.attributes["started"] = str(self.wasStarted()) node.attributes["timing"] = str(self.__timeKeeper.deltaString()) node.attributes["returncode"] = str(self.getResult()) stderr, stdout = self._getOutput() create_child_node(document, node, "stderr", to_unicode_or_bust(stderr)) create_child_node(document, node, "stdout", to_unicode_or_bust(stdout)) create_child_node(document, node, "logdescription", self.getLogDescription()) return node def _getOutput(self): try: stderr = self.getStdErr() stdout = self.getStdOut() except MomError: stderr = "" stdout = "" return stderr, stdout def describe(self, prefix, details=None, replacePatterns=True): """Describe this action.""" self._printDescribeLine(prefix + " ", details, self.getLogDescription(), replacePatterns)
class Step( MObject ): class Result( Enum ): '''Enumerated values representing the result of a step.''' NotExecuted, Success, Failure = range ( 3 ) _Descriptions = [ 'not executed', 'success', 'failure' ] class Status( Enum ): '''Enumerated values representing the status of a step.''' New, Skipped_Disabled, Started, Finished, Skipped_PreviousError = range( 5 ) _Descriptions = [ 'new', 'skipped (disabled)', 'started', 'finished', 'skipped (previous error)' ] """An individual step of an Executomat run.""" def __init__( self, stepName = None ): MObject.__init__( self, stepName ) self.setStatus( Step.Status.New ) self.setResult( Step.Result.NotExecuted ) self.__timeKeeper = TimeKeeper() self.__enabled = True self.__ignorePreviousFailure = False self.__preActions = [] # list of preparation actions self.__mainActions = [] # list of main actions self.__postActions = [] # list of post actions self.__logfilePath = None def setStatus( self, status ): if status in ( Step.Status.New, Step.Status.Skipped_Disabled, Step.Status.Started, Step.Status.Finished, Step.Status.Skipped_PreviousError ): self.__status = status else: raise MomError( 'Unknown step status {0}'.format( status ) ) def getStatus( self ): return self.__status def setResult( self, result ): if result in ( Step.Result.NotExecuted, Step.Result.Success, Step.Result.Failure ): self.__result = result else: raise MomError( 'Unknown step result {0}'.format( result ) ) def getResult( self ): return self.__result def setEnabled( self, enabled = True ): self.__enabled = enabled def isEnabled( self ): return self.__enabled def setIgnorePreviousFailure( self, doIt ): """Set execute-on-failure. If true, the command will be executed, even if a previous command of the same sequence failed.""" self.__ignorePreviousFailure = doIt def getExecuteOnFailure( self ): return self.__ignorePreviousFailure def isEmpty( self ): for actions in self.getAllActions(): if len( actions ) > 0: return False return True def setLogfilePath( self, logfileName ): check_for_string( logfileName, "The log file parameter must be a string containing a file name." ) self.__logfilePath = logfileName def getLogfilePath( self ): return self.__logfilePath def _getRelativeLogFilePath( self ): """\return Relative path of log file to the build base directory""" if not self.getLogfilePath(): return None appBaseDir = mApp().getBaseDir() return os.path.relpath( self.getLogfilePath(), appBaseDir ) def getRelativeLinkTarget( self ): unixPath = make_posixpath( self._getRelativeLogFilePath() ) return ( unixPath, "Get log file for this step" ) def getPreActions( self ): return self.__preActions def getMainActions( self ): return self.__mainActions def getPostActions( self ): return self.__postActions def getAllActions( self ): """\return 3-Element-List of List of actions ([[..],[..],[..]])""" return [self.getPreActions(), self.getMainActions(), self.getPostActions()] def addPreAction( self, action ): """Add one precommand.""" self.__addAction( self.getPreActions(), action ) def prependPreAction( self, action ): """Prepend one precommand.""" self.__addAction( self.getPreActions(), action, True ) def addMainAction( self, action ): """Add a main command.""" self.__addAction( self.getMainActions(), action ) def prependMainAction( self, action ): """Prepend a main command.""" self.__addAction( self.getMainActions(), action, True ) def addPostAction( self, action ): """Add a post command""" self.__addAction( self.getPostActions(), action ) def prependPostAction( self, action ): """Prepend a post command""" self.__addAction( self.getPostActions(), action, True ) def getTimeKeeper( self ): return self.__timeKeeper def _getPhases( self ): """\return List of three (str, [Action]) tuples Contains description and list of actions for each phase (pre, main, post)""" return [ ( 'preparatory actions', self.__preActions ), ( 'main actions', self.__mainActions ), ( 'post actions', self.__postActions ) ] def __addAction( self, container, action, prepend = False ): if not isinstance( action, Action ): raise ConfigurationError( 'An action needs to implement the Action class (got {0} instead)!' .format( repr( action ) if action else 'None' ) ) if prepend: container.insert( 0, action ) else: container.append( action ) def _logEnvironment( self, executomat ): if not mApp().getSettings().get( Settings.ScriptEnableLogEnvironment ): return mApp().debugN( self, 5, 'environment before executing step "{0}": {1}'.format( self.getName(), os.environ ) ) def execute( self, instructions ): """Execute the step""" check_for_nonempty_string( self.getName(), "Cannot execute a step with no name!" ) self.setStatus( Step.Status.Started ) if not self.isEnabled(): self.setStatus( Step.Status.Skipped_Disabled ) return True # (usually) abort if another step has failed for this Instructions object: if not instructions._stepsShouldExecute() and not self.getExecuteOnFailure(): self.setStatus( Step.Status.Skipped_PreviousError ) return True with self.getTimeKeeper(): self._logEnvironment( instructions ) logfileName = '{0}.log'.format( make_foldername_from_string( self.getName() ) ) logfilePath = os.path.join( instructions.getLogDir(), logfileName ) self.setLogfilePath( logfilePath ) self.setResult( Step.Result.Success ) # execute each action associated to this step for phase, actions in self._getPhases(): if not actions: mApp().debugN( self, 3, 'phase "{0}" is empty (no actions registered)'.format( phase ) ) continue mApp().debugN( self, 3, 'phase "{0}" has {1} actions registered, starting execution'.format( phase, len( actions ) ) ) for action in actions: resultText = 'skipped' if self.getResult() != Step.Result.Failure or action.getIgnorePreviousFailure(): result = action.executeAction( self.getLogfilePath() ) resultText = 'successful' if result == 0 else 'failed' if result != 0: self.setResult( Step.Result.Failure ) else: self.setStatus( Step.Status.Skipped_PreviousError ) mApp().debugN( self, 3, '{0}: "{1}" {2}'.format( phase, action.getLogDescription(), resultText ) ) self.setStatus( Step.Status.Finished ) return self.getResult() != Step.Result.Failure def describe( self, prefix, details = None, replacePatterns = True ): if self.isEmpty(): return super( Step, self ).describe( prefix, details, replacePatterns ) for phase in self._getPhases(): for action in phase[1]: action.describe( prefix, details = phase[0] ) def createXmlNode( self, document ): node = super( Step, self ).createXmlNode( document ) node.attributes["isEmpty"] = str ( self.isEmpty() ) node.attributes["isEnabled"] = str( self.isEnabled() ) node.attributes["timing"] = str( self.__timeKeeper.deltaString() ) node.attributes["result"] = str( self.Result.getKey( self.getResult() ) ) node.attributes["status"] = str( self.Status.getKey( self.getStatus() ) ) for actions in self.getAllActions(): if not actions: continue for action in actions: element = action.createXmlNode( document ) node.appendChild( element ) return node