예제 #1
0
파일: Action.py 프로젝트: KDAB/Make-O-Matic
 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)
예제 #2
0
	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
예제 #3
0
파일: Action.py 프로젝트: KDAB/Make-O-Matic
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)
예제 #4
0
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